summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Deutschmann <whissi@gentoo.org>2021-03-30 10:59:39 +0200
committerThomas Deutschmann <whissi@gentoo.org>2021-04-01 00:04:14 +0200
commit5ff1d6955496b3cf9a35042c9ac35db43bc336b1 (patch)
tree6d470f7eb448f59f53e8df1010aec9dad8ce1f72 /leptonica
parentImport Ghostscript 9.53.1 (diff)
downloadghostscript-gpl-patches-5ff1d6955496b3cf9a35042c9ac35db43bc336b1.tar.gz
ghostscript-gpl-patches-5ff1d6955496b3cf9a35042c9ac35db43bc336b1.tar.bz2
ghostscript-gpl-patches-5ff1d6955496b3cf9a35042c9ac35db43bc336b1.zip
Import Ghostscript 9.54ghostscript-9.54
Signed-off-by: Thomas Deutschmann <whissi@gentoo.org>
Diffstat (limited to 'leptonica')
-rw-r--r--leptonica/.github/workflows/sw.yml23
-rw-r--r--leptonica/.travis.yml65
-rw-r--r--leptonica/CMakeLists.txt294
-rw-r--r--leptonica/Doxyfile2432
-rw-r--r--leptonica/Makefile.am85
-rw-r--r--leptonica/README.html1233
-rw-r--r--leptonica/README.md82
-rw-r--r--leptonica/appveyor.yml39
-rwxr-xr-xleptonica/autogen.sh15
-rw-r--r--leptonica/cmake/Configure.cmake164
-rw-r--r--leptonica/cmake/templates/LeptonicaConfig-version.cmake.in14
-rw-r--r--leptonica/cmake/templates/LeptonicaConfig.cmake.in45
-rw-r--r--leptonica/cmake/templates/cmake_uninstall.cmake.in22
-rw-r--r--leptonica/configure.ac276
-rw-r--r--leptonica/lept.pc.cmake11
-rw-r--r--leptonica/lept.pc.in12
-rw-r--r--leptonica/leptonica-license.txt26
-rw-r--r--leptonica/lok.lua157
-rw-r--r--leptonica/m4/ax_split_version.m438
-rwxr-xr-xleptonica/make-for-auto8
-rwxr-xr-xleptonica/make-for-local8
-rw-r--r--leptonica/moller52.jpgbin0 -> 9610 bytes
-rw-r--r--leptonica/prog/1555.003.jpgbin0 -> 198621 bytes
-rw-r--r--leptonica/prog/1555.007.jpgbin0 -> 209558 bytes
-rw-r--r--leptonica/prog/19-colors.pngbin0 -> 627 bytes
-rw-r--r--leptonica/prog/CMakeLists.txt322
-rw-r--r--leptonica/prog/Leptonica.jpgbin0 -> 6723 bytes
-rw-r--r--leptonica/prog/Makefile.am138
-rw-r--r--leptonica/prog/adaptmap_dark.c198
-rw-r--r--leptonica/prog/adaptmap_reg.c208
-rw-r--r--leptonica/prog/adaptnorm_reg.c154
-rw-r--r--leptonica/prog/affine_reg.c425
-rw-r--r--leptonica/prog/alltests_reg.c282
-rw-r--r--leptonica/prog/alphaops_reg.c337
-rw-r--r--leptonica/prog/alphaxform_reg.c230
-rw-r--r--leptonica/prog/amoris.2.150.jpgbin0 -> 256907 bytes
-rw-r--r--leptonica/prog/aneurisms8.jpgbin0 -> 13702 bytes
-rw-r--r--leptonica/prog/arabic.pngbin0 -> 128175 bytes
-rw-r--r--leptonica/prog/arabic2.pngbin0 -> 107581 bytes
-rw-r--r--leptonica/prog/arabic_lines.c166
-rw-r--r--leptonica/prog/arithtest.c83
-rw-r--r--leptonica/prog/autogen.137.c98
-rw-r--r--leptonica/prog/autogen.137.h306
-rw-r--r--leptonica/prog/autogentest1.c80
-rw-r--r--leptonica/prog/autogentest2.c69
-rw-r--r--leptonica/prog/barcode-128-300.pngbin0 -> 2667 bytes
-rw-r--r--leptonica/prog/barcode-2of5-300.pngbin0 -> 2446 bytes
-rw-r--r--leptonica/prog/barcode-39-300.pngbin0 -> 3608 bytes
-rw-r--r--leptonica/prog/barcode-93-300.pngbin0 -> 2405 bytes
-rw-r--r--leptonica/prog/barcode-codabar-300.pngbin0 -> 2929 bytes
-rw-r--r--leptonica/prog/barcode-digits.pngbin0 -> 619 bytes
-rw-r--r--leptonica/prog/barcode-i2of5-300.pngbin0 -> 2325 bytes
-rw-r--r--leptonica/prog/barcode-upc-300.pngbin0 -> 5351 bytes
-rw-r--r--leptonica/prog/barcodetest.c90
-rw-r--r--leptonica/prog/barcodetest1.jpgbin0 -> 19862 bytes
-rw-r--r--leptonica/prog/barcodetest2.jpgbin0 -> 8320 bytes
-rw-r--r--leptonica/prog/baseline_reg.c135
-rw-r--r--leptonica/prog/bigweasel2.4c.pngbin0 -> 1206 bytes
-rw-r--r--leptonica/prog/bilateral1_reg.c136
-rw-r--r--leptonica/prog/bilateral2_reg.c105
-rw-r--r--leptonica/prog/bilinear_reg.c263
-rw-r--r--leptonica/prog/binarize_reg.c180
-rw-r--r--leptonica/prog/binarize_set.c177
-rw-r--r--leptonica/prog/binarizefiles.c120
-rw-r--r--leptonica/prog/bincompare.c98
-rw-r--r--leptonica/prog/binding-example.45.jpgbin0 -> 73283 bytes
-rw-r--r--leptonica/prog/binmorph1_reg.c578
-rw-r--r--leptonica/prog/binmorph2_reg.c227
-rw-r--r--leptonica/prog/binmorph3_reg.c404
-rw-r--r--leptonica/prog/binmorph4_reg.c544
-rw-r--r--leptonica/prog/binmorph5_reg.c329
-rw-r--r--leptonica/prog/binmorph6_reg.c89
-rw-r--r--leptonica/prog/blackwhite_reg.c114
-rw-r--r--leptonica/prog/blend-green1.jpgbin0 -> 13979 bytes
-rw-r--r--leptonica/prog/blend-green2.pngbin0 -> 7770 bytes
-rw-r--r--leptonica/prog/blend-green3.pngbin0 -> 4570 bytes
-rw-r--r--leptonica/prog/blend-orange.jpgbin0 -> 15094 bytes
-rw-r--r--leptonica/prog/blend-red.pngbin0 -> 3945 bytes
-rw-r--r--leptonica/prog/blend-yellow.jpgbin0 -> 17918 bytes
-rw-r--r--leptonica/prog/blend1_reg.c328
-rw-r--r--leptonica/prog/blend2_reg.c182
-rw-r--r--leptonica/prog/blend3_reg.c216
-rw-r--r--leptonica/prog/blend4_reg.c102
-rw-r--r--leptonica/prog/blend5_reg.c182
-rw-r--r--leptonica/prog/blendcmaptest.c112
-rw-r--r--leptonica/prog/blender1.tifbin0 -> 496 bytes
-rw-r--r--leptonica/prog/blender8.pngbin0 -> 1811 bytes
-rw-r--r--leptonica/prog/blendtext.tifbin0 -> 496 bytes
-rw-r--r--leptonica/prog/bois-2.tifbin0 -> 31732 bytes
-rw-r--r--leptonica/prog/bois-3.tifbin0 -> 40290 bytes
-rw-r--r--leptonica/prog/bois-4.tifbin0 -> 48922 bytes
-rw-r--r--leptonica/prog/bois-5.tifbin0 -> 48784 bytes
-rw-r--r--leptonica/prog/books_logo.pngbin0 -> 23078 bytes
-rw-r--r--leptonica/prog/boxa1.ba47
-rw-r--r--leptonica/prog/boxa1_reg.c161
-rw-r--r--leptonica/prog/boxa2.ba379
-rw-r--r--leptonica/prog/boxa2_reg.c196
-rw-r--r--leptonica/prog/boxa3.ba379
-rw-r--r--leptonica/prog/boxa3_reg.c202
-rw-r--r--leptonica/prog/boxa4.ba55
-rw-r--r--leptonica/prog/boxa4_reg.c226
-rw-r--r--leptonica/prog/boxa5.ba21
-rw-r--r--leptonica/prog/boxap1.ba129
-rw-r--r--leptonica/prog/boxap2.ba303
-rw-r--r--leptonica/prog/boxap3.ba15
-rw-r--r--leptonica/prog/boxap4.ba53
-rw-r--r--leptonica/prog/boxap5.ba553
-rw-r--r--leptonica/prog/boxedpage.jpgbin0 -> 61244 bytes
-rw-r--r--leptonica/prog/brev.06.75.jpgbin0 -> 33364 bytes
-rw-r--r--leptonica/prog/brev.10.75.jpgbin0 -> 42274 bytes
-rw-r--r--leptonica/prog/brev.14.75.jpgbin0 -> 37460 bytes
-rw-r--r--leptonica/prog/brev.20.75.jpgbin0 -> 38733 bytes
-rw-r--r--leptonica/prog/brev.36.75.jpgbin0 -> 41245 bytes
-rw-r--r--leptonica/prog/brev.53.75.jpgbin0 -> 28953 bytes
-rw-r--r--leptonica/prog/brev.56.75.jpgbin0 -> 41320 bytes
-rw-r--r--leptonica/prog/breviar.38.150.jpgbin0 -> 146456 bytes
-rw-r--r--leptonica/prog/brothers.150.jpgbin0 -> 81310 bytes
-rw-r--r--leptonica/prog/buffertest.c114
-rw-r--r--leptonica/prog/bytea_reg.c180
-rw-r--r--leptonica/prog/candelabrum.011.jpgbin0 -> 59408 bytes
-rw-r--r--leptonica/prog/cat-and-mouse.pngbin0 -> 15550 bytes
-rw-r--r--leptonica/prog/cat.007.jpgbin0 -> 139783 bytes
-rw-r--r--leptonica/prog/cat.035.jpgbin0 -> 140334 bytes
-rw-r--r--leptonica/prog/cavalerie.11.jpgbin0 -> 240511 bytes
-rw-r--r--leptonica/prog/cavalerie.29.jpgbin0 -> 198691 bytes
-rw-r--r--leptonica/prog/ccbord_reg.c231
-rw-r--r--leptonica/prog/ccbordtest.c246
-rw-r--r--leptonica/prog/cctest1.c125
-rw-r--r--leptonica/prog/ccthin1_reg.c202
-rw-r--r--leptonica/prog/ccthin2_reg.c192
-rw-r--r--leptonica/prog/char.tifbin0 -> 232 bytes
-rw-r--r--leptonica/prog/chars-10.tifbin0 -> 2788 bytes
-rw-r--r--leptonica/prog/chars-12.tifbin0 -> 3250 bytes
-rw-r--r--leptonica/prog/chars-14.tifbin0 -> 3612 bytes
-rw-r--r--leptonica/prog/chars-16.tifbin0 -> 4066 bytes
-rw-r--r--leptonica/prog/chars-18.tifbin0 -> 4530 bytes
-rw-r--r--leptonica/prog/chars-20.tifbin0 -> 5004 bytes
-rw-r--r--leptonica/prog/chars-4.tifbin0 -> 1446 bytes
-rw-r--r--leptonica/prog/chars-6.tifbin0 -> 1860 bytes
-rw-r--r--leptonica/prog/chars-8.tifbin0 -> 2340 bytes
-rw-r--r--leptonica/prog/checkerboard1.tifbin0 -> 1228 bytes
-rw-r--r--leptonica/prog/checkerboard2.tifbin0 -> 3086 bytes
-rw-r--r--leptonica/prog/checkerboard_reg.c94
-rw-r--r--leptonica/prog/church.pngbin0 -> 65442 bytes
-rw-r--r--leptonica/prog/circle_reg.c140
-rw-r--r--leptonica/prog/circles.pabin0 -> 4334 bytes
-rw-r--r--leptonica/prog/cleanpdf.c278
-rw-r--r--leptonica/prog/cmapquant_reg.c136
-rw-r--r--leptonica/prog/coffeebeans.pngbin0 -> 2449 bytes
-rw-r--r--leptonica/prog/color-wheel-hue.jpgbin0 -> 7684 bytes
-rw-r--r--leptonica/prog/colorcontent_reg.c170
-rw-r--r--leptonica/prog/colorfill_reg.c167
-rw-r--r--leptonica/prog/coloring_reg.c158
-rw-r--r--leptonica/prog/colorize_reg.c290
-rw-r--r--leptonica/prog/colormask_reg.c158
-rw-r--r--leptonica/prog/colormorph_reg.c101
-rw-r--r--leptonica/prog/colorpage.030.jpgbin0 -> 57025 bytes
-rw-r--r--leptonica/prog/colorquant_reg.c241
-rw-r--r--leptonica/prog/colorseg.jpgbin0 -> 55238 bytes
-rw-r--r--leptonica/prog/colorseg_reg.c113
-rw-r--r--leptonica/prog/colorsegtest.c109
-rw-r--r--leptonica/prog/colorspace_reg.c208
-rw-r--r--leptonica/prog/comap.063.jpgbin0 -> 34357 bytes
-rw-r--r--leptonica/prog/comap.068.jpgbin0 -> 30426 bytes
-rw-r--r--leptonica/prog/comap.073.jpgbin0 -> 29973 bytes
-rw-r--r--leptonica/prog/comap.100.jpgbin0 -> 38859 bytes
-rw-r--r--leptonica/prog/comap.110.jpgbin0 -> 47121 bytes
-rw-r--r--leptonica/prog/comap.118.jpgbin0 -> 42039 bytes
-rw-r--r--leptonica/prog/compare_reg.c143
-rw-r--r--leptonica/prog/comparepages.c116
-rw-r--r--leptonica/prog/comparepixa.c101
-rw-r--r--leptonica/prog/comparetest.c159
-rw-r--r--leptonica/prog/compfilter_reg.c346
-rw-r--r--leptonica/prog/concatpdf.c177
-rw-r--r--leptonica/prog/conncomp_reg.c163
-rw-r--r--leptonica/prog/contrast-orig-60.jpgbin0 -> 107226 bytes
-rw-r--r--leptonica/prog/contrast1.jpgbin0 -> 23371 bytes
-rw-r--r--leptonica/prog/contrasttest.c94
-rw-r--r--leptonica/prog/conversion_reg.c424
-rw-r--r--leptonica/prog/convertfilestopdf.c109
-rw-r--r--leptonica/prog/convertfilestops.c85
-rw-r--r--leptonica/prog/convertformat.c181
-rw-r--r--leptonica/prog/convertsegfilestopdf.c162
-rw-r--r--leptonica/prog/convertsegfilestops.c137
-rw-r--r--leptonica/prog/converttogray.c123
-rw-r--r--leptonica/prog/converttopdf.c78
-rw-r--r--leptonica/prog/converttops.c68
-rw-r--r--leptonica/prog/convolve_reg.c211
-rw-r--r--leptonica/prog/cootoots.pngbin0 -> 12806 bytes
-rw-r--r--leptonica/prog/copernicus.pngbin0 -> 53027 bytes
-rw-r--r--leptonica/prog/cornertest.c104
-rw-r--r--leptonica/prog/corrupttest.c271
-rw-r--r--leptonica/prog/crop_reg.c320
-rw-r--r--leptonica/prog/croptext.c99
-rw-r--r--leptonica/prog/dave-orig.pngbin0 -> 188112 bytes
-rw-r--r--leptonica/prog/deskew_it.c140
-rw-r--r--leptonica/prog/dewarp_reg.c211
-rw-r--r--leptonica/prog/dewarprules.c176
-rw-r--r--leptonica/prog/dewarptest1.c182
-rw-r--r--leptonica/prog/dewarptest2.c123
-rw-r--r--leptonica/prog/dewarptest3.c167
-rw-r--r--leptonica/prog/dewarptest4.c123
-rw-r--r--leptonica/prog/dewarptest5.c140
-rw-r--r--leptonica/prog/digitprep1.c106
-rw-r--r--leptonica/prog/dinos.pacbin0 -> 244424 bytes
-rw-r--r--leptonica/prog/displayboxa.c91
-rw-r--r--leptonica/prog/displayboxes_on_pixa.c98
-rw-r--r--leptonica/prog/displaypix.c61
-rw-r--r--leptonica/prog/displaypixa.c176
-rw-r--r--leptonica/prog/distance_reg.c158
-rw-r--r--leptonica/prog/dither_reg.c86
-rw-r--r--leptonica/prog/dna_reg.c123
-rw-r--r--leptonica/prog/dreyfus1.pngbin0 -> 10341 bytes
-rw-r--r--leptonica/prog/dreyfus2.pngbin0 -> 17827 bytes
-rw-r--r--leptonica/prog/dreyfus4.pngbin0 -> 22525 bytes
-rw-r--r--leptonica/prog/dreyfus8.pngbin0 -> 34971 bytes
-rw-r--r--leptonica/prog/dwalinear.3.c343
-rw-r--r--leptonica/prog/dwalineargen.c79
-rw-r--r--leptonica/prog/dwalinearlow.3.c16986
-rw-r--r--leptonica/prog/dwamorph1_reg.c244
-rw-r--r--leptonica/prog/dwamorph2_reg.c321
-rw-r--r--leptonica/prog/edge_reg.c90
-rw-r--r--leptonica/prog/encoding_reg.c107
-rw-r--r--leptonica/prog/enhance_reg.c298
-rw-r--r--leptonica/prog/equal_reg.c139
-rw-r--r--leptonica/prog/expand_reg.c165
-rw-r--r--leptonica/prog/extrema_reg.c102
-rw-r--r--leptonica/prog/falsecolor_reg.c93
-rw-r--r--leptonica/prog/fcombautogen.c80
-rw-r--r--leptonica/prog/feyn-fract.tifbin0 -> 11520 bytes
-rw-r--r--leptonica/prog/feyn-fract2.tifbin0 -> 4952 bytes
-rw-r--r--leptonica/prog/feyn-word.tifbin0 -> 426 bytes
-rw-r--r--leptonica/prog/feyn.tifbin0 -> 104796 bytes
-rw-r--r--leptonica/prog/feynman-stamp.jpgbin0 -> 14394 bytes
-rw-r--r--leptonica/prog/fhmtauto_reg.c90
-rw-r--r--leptonica/prog/fhmtautogen.c74
-rw-r--r--leptonica/prog/fileinfo.c52
-rw-r--r--leptonica/prog/files_reg.c290
-rw-r--r--leptonica/prog/find_colorregions.c343
-rw-r--r--leptonica/prog/findbinding.c164
-rw-r--r--leptonica/prog/findcorners_reg.c210
-rw-r--r--leptonica/prog/findpattern1.c145
-rw-r--r--leptonica/prog/findpattern2.c161
-rw-r--r--leptonica/prog/findpattern3.c160
-rw-r--r--leptonica/prog/findpattern_reg.c173
-rw-r--r--leptonica/prog/fish24.jpgbin0 -> 103695 bytes
-rw-r--r--leptonica/prog/flipdetect_reg.c119
-rw-r--r--leptonica/prog/flipselgen.c.notused124
-rw-r--r--leptonica/prog/flipsels.txt35
-rw-r--r--leptonica/prog/fmorphauto_reg.c163
-rw-r--r--leptonica/prog/fmorphautogen.c74
-rw-r--r--leptonica/prog/fonts/chars-10.pabin0 -> 17399 bytes
-rw-r--r--leptonica/prog/fonts/chars-10.ps21
-rw-r--r--leptonica/prog/fonts/chars-10.tifbin0 -> 2858 bytes
-rw-r--r--leptonica/prog/fonts/chars-12.pabin0 -> 18554 bytes
-rw-r--r--leptonica/prog/fonts/chars-12.ps21
-rw-r--r--leptonica/prog/fonts/chars-12.tifbin0 -> 3342 bytes
-rw-r--r--leptonica/prog/fonts/chars-14.pabin0 -> 19414 bytes
-rw-r--r--leptonica/prog/fonts/chars-14.ps21
-rw-r--r--leptonica/prog/fonts/chars-14.tifbin0 -> 3680 bytes
-rw-r--r--leptonica/prog/fonts/chars-16.pabin0 -> 20343 bytes
-rw-r--r--leptonica/prog/fonts/chars-16.ps21
-rw-r--r--leptonica/prog/fonts/chars-16.tifbin0 -> 4142 bytes
-rw-r--r--leptonica/prog/fonts/chars-18.pabin0 -> 21457 bytes
-rw-r--r--leptonica/prog/fonts/chars-18.ps21
-rw-r--r--leptonica/prog/fonts/chars-18.tifbin0 -> 4606 bytes
-rw-r--r--leptonica/prog/fonts/chars-20.pabin0 -> 22376 bytes
-rw-r--r--leptonica/prog/fonts/chars-20.ps21
-rw-r--r--leptonica/prog/fonts/chars-20.tifbin0 -> 5070 bytes
-rw-r--r--leptonica/prog/fonts/chars-4.pabin0 -> 13693 bytes
-rw-r--r--leptonica/prog/fonts/chars-4.ps21
-rw-r--r--leptonica/prog/fonts/chars-4.tifbin0 -> 1508 bytes
-rw-r--r--leptonica/prog/fonts/chars-6.pabin0 -> 14988 bytes
-rw-r--r--leptonica/prog/fonts/chars-6.ps21
-rw-r--r--leptonica/prog/fonts/chars-6.tifbin0 -> 1928 bytes
-rw-r--r--leptonica/prog/fonts/chars-8.pabin0 -> 16217 bytes
-rw-r--r--leptonica/prog/fonts/chars-8.ps21
-rw-r--r--leptonica/prog/fonts/chars-8.tifbin0 -> 2416 bytes
-rw-r--r--leptonica/prog/form1.tifbin0 -> 6074 bytes
-rw-r--r--leptonica/prog/form2.tifbin0 -> 5116 bytes
-rw-r--r--leptonica/prog/fpix1_reg.c364
-rw-r--r--leptonica/prog/fpix2_reg.c116
-rw-r--r--leptonica/prog/fpixcontours.c76
-rw-r--r--leptonica/prog/fuzzing/README.md25
-rw-r--r--leptonica/prog/fuzzing/adaptmap_fuzzer.cc75
-rw-r--r--leptonica/prog/fuzzing/affine_fuzzer.cc40
-rw-r--r--leptonica/prog/fuzzing/barcode_fuzzer.cc22
-rw-r--r--leptonica/prog/fuzzing/barcode_fuzzer_seed_corpus.zipbin0 -> 51247 bytes
-rw-r--r--leptonica/prog/fuzzing/baseline_fuzzer.cc24
-rw-r--r--leptonica/prog/fuzzing/bilateral_fuzzer.cc25
-rw-r--r--leptonica/prog/fuzzing/bilinear_fuzzer.cc37
-rw-r--r--leptonica/prog/fuzzing/binarize_fuzzer.cc55
-rw-r--r--leptonica/prog/fuzzing/blend_fuzzer.cc93
-rw-r--r--leptonica/prog/fuzzing/boxfunc3_fuzzer.cc107
-rw-r--r--leptonica/prog/fuzzing/boxfunc4_fuzzer.cc39
-rw-r--r--leptonica/prog/fuzzing/boxfunc5_fuzzer.cc51
-rw-r--r--leptonica/prog/fuzzing/boxfunc_fuzzer.cc23
-rw-r--r--leptonica/prog/fuzzing/ccbord_fuzzer.cc27
-rw-r--r--leptonica/prog/fuzzing/ccthin_fuzzer.cc28
-rw-r--r--leptonica/prog/fuzzing/checkerboard_fuzzer.cc25
-rw-r--r--leptonica/prog/fuzzing/classapp_fuzzer.cc24
-rw-r--r--leptonica/prog/fuzzing/colorfill_fuzzer.cc17
-rw-r--r--leptonica/prog/fuzzing/colorquant_fuzzer.cc83
-rw-r--r--leptonica/prog/fuzzing/compare_fuzzer.cc49
-rw-r--r--leptonica/prog/fuzzing/dewarp_fuzzer.cc29
-rw-r--r--leptonica/prog/fuzzing/edge_fuzzer.cc18
-rw-r--r--leptonica/prog/fuzzing/enhance_fuzzer.cc101
-rw-r--r--leptonica/prog/fuzzing/fhmtgen_fuzzer.cc33
-rw-r--r--leptonica/prog/fuzzing/finditalic_fuzzer.cc18
-rw-r--r--leptonica/prog/fuzzing/flipdetect_fuzzer.cc31
-rw-r--r--leptonica/prog/fuzzing/fpix2_fuzzer.cc111
-rw-r--r--leptonica/prog/fuzzing/general_corpus.zipbin0 -> 99986 bytes
-rw-r--r--leptonica/prog/fuzzing/graphics_fuzzer.cc81
-rw-r--r--leptonica/prog/fuzzing/graymorph_fuzzer.cc36
-rw-r--r--leptonica/prog/fuzzing/grayquant_fuzzer.cc47
-rw-r--r--leptonica/prog/fuzzing/jpegiostub_fuzzer.cc15
-rw-r--r--leptonica/prog/fuzzing/kernel_fuzzer.cc16
-rw-r--r--leptonica/prog/fuzzing/leptfuzz.h16
-rw-r--r--leptonica/prog/fuzzing/mask_fuzzer.cc42
-rw-r--r--leptonica/prog/fuzzing/maze_fuzzer.cc24
-rw-r--r--leptonica/prog/fuzzing/morph_fuzzer.cc48
-rw-r--r--leptonica/prog/fuzzing/morphapp_fuzzer.cc65
-rwxr-xr-xleptonica/prog/fuzzing/oss-fuzz-build.sh128
-rw-r--r--leptonica/prog/fuzzing/pageseg_fuzzer.cc42
-rw-r--r--leptonica/prog/fuzzing/paintcmap_fuzzer.cc27
-rw-r--r--leptonica/prog/fuzzing/pix1_fuzzer.cc19
-rw-r--r--leptonica/prog/fuzzing/pix3_fuzzer.cc133
-rw-r--r--leptonica/prog/fuzzing/pix4_fuzzer.cc109
-rw-r--r--leptonica/prog/fuzzing/pixMirrorDetectDwa_fuzzer.cc20
-rw-r--r--leptonica/prog/fuzzing/pix_orient_fuzzer.cc19
-rw-r--r--leptonica/prog/fuzzing/pix_rotate_shear_fuzzer.cc67
-rw-r--r--leptonica/prog/fuzzing/pixa_recog_fuzzer.cc49
-rw-r--r--leptonica/prog/fuzzing/pixa_recog_fuzzer_seed_corpus.zipbin0 -> 43179 bytes
-rw-r--r--leptonica/prog/fuzzing/pixconv_fuzzer.cc23
-rw-r--r--leptonica/prog/fuzzing/recog_basic_fuzzer.cc25
-rw-r--r--leptonica/prog/gammatest.c98
-rw-r--r--leptonica/prog/genfonts_reg.c173
-rw-r--r--leptonica/prog/german.pngbin0 -> 46306 bytes
-rw-r--r--leptonica/prog/gifio_reg.c232
-rw-r--r--leptonica/prog/google-searchbox.pngbin0 -> 3456 bytes
-rw-r--r--leptonica/prog/gplotdata.example82
-rw-r--r--leptonica/prog/graphicstest.c100
-rw-r--r--leptonica/prog/gray-alpha.tifbin0 -> 410 bytes
-rw-r--r--leptonica/prog/grayfill_reg.c207
-rw-r--r--leptonica/prog/graymorph1_reg.c332
-rw-r--r--leptonica/prog/graymorph2_reg.c153
-rw-r--r--leptonica/prog/graymorphtest.c103
-rw-r--r--leptonica/prog/grayquant_reg.c398
-rw-r--r--leptonica/prog/graytext.pngbin0 -> 4071 bytes
-rw-r--r--leptonica/prog/greencover.jpgbin0 -> 21727 bytes
-rw-r--r--leptonica/prog/hardlight1_1.jpgbin0 -> 2932 bytes
-rw-r--r--leptonica/prog/hardlight1_2.jpgbin0 -> 1731 bytes
-rw-r--r--leptonica/prog/hardlight2_1.jpgbin0 -> 2967 bytes
-rw-r--r--leptonica/prog/hardlight2_2.jpgbin0 -> 3475 bytes
-rw-r--r--leptonica/prog/hardlight_reg.c140
-rw-r--r--leptonica/prog/harmoniam-11.tifbin0 -> 35522 bytes
-rw-r--r--leptonica/prog/harmoniam100-11.pngbin0 -> 72398 bytes
-rw-r--r--leptonica/prog/hash_reg.c471
-rw-r--r--leptonica/prog/heap_reg.c155
-rw-r--r--leptonica/prog/histoduptest.c267
-rw-r--r--leptonica/prog/histotest.c102
-rw-r--r--leptonica/prog/hmttemplate1.txt170
-rw-r--r--leptonica/prog/hmttemplate2.txt103
-rw-r--r--leptonica/prog/hole-filler.pngbin0 -> 30776 bytes
-rw-r--r--leptonica/prog/htmlviewer.c286
-rw-r--r--leptonica/prog/imagetops.c101
-rw-r--r--leptonica/prog/insert_reg.c163
-rw-r--r--leptonica/prog/invertedtext.tifbin0 -> 694 bytes
-rw-r--r--leptonica/prog/ioformats_reg.c921
-rw-r--r--leptonica/prog/iomisc_reg.c300
-rw-r--r--leptonica/prog/italic.pngbin0 -> 20761 bytes
-rw-r--r--leptonica/prog/italic_reg.c116
-rw-r--r--leptonica/prog/jbclass_reg.c211
-rw-r--r--leptonica/prog/jbcorrelation.c226
-rw-r--r--leptonica/prog/jbrankhaus.c224
-rw-r--r--leptonica/prog/jbwords.c135
-rw-r--r--leptonica/prog/jp2kio_reg.c184
-rw-r--r--leptonica/prog/jpeg-coded.tifbin0 -> 7064 bytes
-rw-r--r--leptonica/prog/jpegio_reg.c265
-rw-r--r--leptonica/prog/juditharismax.jpgbin0 -> 149756 bytes
-rw-r--r--leptonica/prog/karen8.jpgbin0 -> 15966 bytes
-rw-r--r--leptonica/prog/kernel_reg.c356
-rw-r--r--leptonica/prog/keystone.pngbin0 -> 39141 bytes
-rw-r--r--leptonica/prog/label_reg.c220
-rw-r--r--leptonica/prog/lapide.052.100.jpgbin0 -> 289835 bytes
-rw-r--r--leptonica/prog/leptonica-license.txt26
-rw-r--r--leptonica/prog/lightcolortest.c128
-rw-r--r--leptonica/prog/lighttext.jpgbin0 -> 82345 bytes
-rw-r--r--leptonica/prog/lineremoval_reg.c124
-rw-r--r--leptonica/prog/lion-mask.00010.tifbin0 -> 422 bytes
-rw-r--r--leptonica/prog/lion-mask.00016.tifbin0 -> 410 bytes
-rw-r--r--leptonica/prog/lion-page.00010.jpgbin0 -> 53313 bytes
-rw-r--r--leptonica/prog/lion-page.00011.pngbin0 -> 18202 bytes
-rw-r--r--leptonica/prog/lion-page.00012.pngbin0 -> 3489 bytes
-rw-r--r--leptonica/prog/lion-page.00013.pngbin0 -> 15806 bytes
-rw-r--r--leptonica/prog/lion-page.00016.jpgbin0 -> 49650 bytes
-rw-r--r--leptonica/prog/lion-page.00017.pngbin0 -> 19845 bytes
-rw-r--r--leptonica/prog/listtest.c279
-rw-r--r--leptonica/prog/livre_adapt.c105
-rw-r--r--leptonica/prog/livre_hmt.c151
-rw-r--r--leptonica/prog/livre_makefigs.c107
-rw-r--r--leptonica/prog/livre_orient.c94
-rw-r--r--leptonica/prog/livre_pageseg.c311
-rw-r--r--leptonica/prog/livre_seedgen.c75
-rw-r--r--leptonica/prog/livre_tophat.c75
-rw-r--r--leptonica/prog/locminmax_reg.c102
-rw-r--r--leptonica/prog/logicops_reg.c178
-rw-r--r--leptonica/prog/lowaccess_reg.c304
-rw-r--r--leptonica/prog/lowsat_reg.c107
-rw-r--r--leptonica/prog/lucasta-frag.jpgbin0 -> 54841 bytes
-rw-r--r--leptonica/prog/lucasta.047.jpgbin0 -> 240757 bytes
-rw-r--r--leptonica/prog/lucasta.1.300.tifbin0 -> 28952 bytes
-rw-r--r--leptonica/prog/lucasta.150.jpgbin0 -> 103707 bytes
-rw-r--r--leptonica/prog/lyra.005.jpgbin0 -> 103429 bytes
-rw-r--r--leptonica/prog/lyra.036.jpgbin0 -> 122737 bytes
-rw-r--r--leptonica/prog/lyra.5.na725
-rw-r--r--leptonica/prog/makefile.static1236
-rw-r--r--leptonica/prog/maketile.c118
-rw-r--r--leptonica/prog/map.057.jpgbin0 -> 45042 bytes
-rw-r--r--leptonica/prog/map1.jpgbin0 -> 118180 bytes
-rw-r--r--leptonica/prog/maptest.c426
-rw-r--r--leptonica/prog/marge.jpgbin0 -> 33094 bytes
-rw-r--r--leptonica/prog/maze_reg.c110
-rw-r--r--leptonica/prog/messagetest.c181
-rw-r--r--leptonica/prog/minisblack.tifbin0 -> 746 bytes
-rw-r--r--leptonica/prog/miniswhite.tifbin0 -> 742 bytes
-rw-r--r--leptonica/prog/misctest1.c331
-rw-r--r--leptonica/prog/modifyhuesat.c107
-rw-r--r--leptonica/prog/morphseq_reg.c121
-rw-r--r--leptonica/prog/morphtemplate1.txt224
-rw-r--r--leptonica/prog/morphtemplate2.txt104
-rw-r--r--leptonica/prog/morphtest1.c140
-rw-r--r--leptonica/prog/mtiff_reg.c378
-rw-r--r--leptonica/prog/multitype_reg.c483
-rw-r--r--leptonica/prog/nearline_reg.c189
-rw-r--r--leptonica/prog/newspaper_reg.c171
-rw-r--r--leptonica/prog/numa1_reg.c321
-rw-r--r--leptonica/prog/numa2_reg.c490
-rw-r--r--leptonica/prog/numa3_reg.c215
-rw-r--r--leptonica/prog/numaranktest.c97
-rw-r--r--leptonica/prog/ortiz-02.tifbin0 -> 59428 bytes
-rw-r--r--leptonica/prog/ortiz-03.tifbin0 -> 68303 bytes
-rw-r--r--leptonica/prog/ortiz-04.tifbin0 -> 67339 bytes
-rw-r--r--leptonica/prog/ortiz-05.tifbin0 -> 69301 bytes
-rw-r--r--leptonica/prog/otsutest1.c161
-rw-r--r--leptonica/prog/otsutest2.c129
-rw-r--r--leptonica/prog/overlap_reg.c261
-rw-r--r--leptonica/prog/pageseg1.tifbin0 -> 133362 bytes
-rw-r--r--leptonica/prog/pageseg2-mask.pngbin0 -> 36764 bytes
-rw-r--r--leptonica/prog/pageseg2-seed.pngbin0 -> 2352 bytes
-rw-r--r--leptonica/prog/pageseg2.tifbin0 -> 258864 bytes
-rw-r--r--leptonica/prog/pageseg3.tifbin0 -> 122112 bytes
-rw-r--r--leptonica/prog/pageseg4.tifbin0 -> 114878 bytes
-rw-r--r--leptonica/prog/pageseg_reg.c237
-rw-r--r--leptonica/prog/pagesegtest1.c71
-rw-r--r--leptonica/prog/pagesegtest2.c131
-rw-r--r--leptonica/prog/paint_reg.c364
-rw-r--r--leptonica/prog/paintmask_reg.c215
-rw-r--r--leptonica/prog/pancrazi.15.jpgbin0 -> 91677 bytes
-rw-r--r--leptonica/prog/partifytest.c53
-rw-r--r--leptonica/prog/partitiontest.c177
-rw-r--r--leptonica/prog/patent.pngbin0 -> 75966 bytes
-rw-r--r--leptonica/prog/pdf2jpeg58
-rwxr-xr-xleptonica/prog/pdf2mtiff51
-rw-r--r--leptonica/prog/pdf2png46
-rw-r--r--leptonica/prog/pdf2png-binary38
-rw-r--r--leptonica/prog/pdf2png-color46
-rw-r--r--leptonica/prog/pdf2png-gray46
-rw-r--r--leptonica/prog/pdf2tiff47
-rw-r--r--leptonica/prog/pdfio1_reg.c318
-rw-r--r--leptonica/prog/pdfio2_reg.c376
-rw-r--r--leptonica/prog/pdfseg_reg.c183
-rw-r--r--leptonica/prog/pedante.079.jpgbin0 -> 70343 bytes
-rw-r--r--leptonica/prog/percolate-4cc.pngbin0 -> 5844 bytes
-rw-r--r--leptonica/prog/percolate-8cc.pngbin0 -> 5571 bytes
-rw-r--r--leptonica/prog/percolatetest.c322
-rw-r--r--leptonica/prog/pixa1_reg.c170
-rw-r--r--leptonica/prog/pixa2_reg.c121
-rw-r--r--leptonica/prog/pixaatest.c109
-rw-r--r--leptonica/prog/pixadisp_reg.c166
-rw-r--r--leptonica/prog/pixafileinfo.c82
-rw-r--r--leptonica/prog/pixalloc_reg.c208
-rw-r--r--leptonica/prog/pixcomp_reg.c236
-rw-r--r--leptonica/prog/pixmem_reg.c152
-rw-r--r--leptonica/prog/pixserial_reg.c142
-rw-r--r--leptonica/prog/pixtile_reg.c105
-rw-r--r--leptonica/prog/plottest.c156
-rw-r--r--leptonica/prog/pngio_reg.c477
-rw-r--r--leptonica/prog/pnmio_reg.c183
-rw-r--r--leptonica/prog/printimage.c158
-rw-r--r--leptonica/prog/printsplitimage.c150
-rw-r--r--leptonica/prog/printtiff.c98
-rw-r--r--leptonica/prog/projection_reg.c155
-rw-r--r--leptonica/prog/projectionstats.jpgbin0 -> 3315 bytes
-rw-r--r--leptonica/prog/projective_reg.c234
-rw-r--r--leptonica/prog/ps2jpeg32
-rw-r--r--leptonica/prog/ps2png43
-rw-r--r--leptonica/prog/ps2png-binary35
-rw-r--r--leptonica/prog/ps2png-color33
-rw-r--r--leptonica/prog/ps2png-gray33
-rw-r--r--leptonica/prog/ps2tiff42
-rw-r--r--leptonica/prog/psio_reg.c250
-rw-r--r--leptonica/prog/psioseg_reg.c174
-rw-r--r--leptonica/prog/pta_reg.c228
-rw-r--r--leptonica/prog/ptra1_reg.c488
-rw-r--r--leptonica/prog/ptra2_reg.c266
-rw-r--r--leptonica/prog/quadtree_reg.c141
-rw-r--r--leptonica/prog/rabi-tiny.pngbin0 -> 2408 bytes
-rw-r--r--leptonica/prog/rabi.pngbin0 -> 232718 bytes
-rw-r--r--leptonica/prog/raggededge.pngbin0 -> 452 bytes
-rw-r--r--leptonica/prog/rank_reg.c226
-rw-r--r--leptonica/prog/rankbin_reg.c204
-rw-r--r--leptonica/prog/rankhisto_reg.c135
-rw-r--r--leptonica/prog/rasterop_reg.c103
-rw-r--r--leptonica/prog/rasteropip_reg.c81
-rw-r--r--leptonica/prog/rasteroptest.c143
-rw-r--r--leptonica/prog/rbtreetest.c111
-rw-r--r--leptonica/prog/recog/digits/bootnum1.pabin0 -> 25134 bytes
-rw-r--r--leptonica/prog/recog/digits/bootnum2.pabin0 -> 24608 bytes
-rw-r--r--leptonica/prog/recog/digits/bootnum3.pabin0 -> 31903 bytes
-rw-r--r--leptonica/prog/recog/digits/bootnum4.pabin0 -> 39338 bytes
-rw-r--r--leptonica/prog/recog/digits/digit0.comp.tifbin0 -> 6354 bytes
-rw-r--r--leptonica/prog/recog/digits/digit1.comp.tifbin0 -> 4266 bytes
-rw-r--r--leptonica/prog/recog/digits/digit2.comp.tifbin0 -> 7370 bytes
-rw-r--r--leptonica/prog/recog/digits/digit3.comp.tifbin0 -> 7638 bytes
-rw-r--r--leptonica/prog/recog/digits/digit4.comp.tifbin0 -> 6154 bytes
-rw-r--r--leptonica/prog/recog/digits/digit5.comp.tifbin0 -> 7240 bytes
-rw-r--r--leptonica/prog/recog/digits/digit5.orig-25.pabin0 -> 13207 bytes
-rw-r--r--leptonica/prog/recog/digits/digit6.comp.tifbin0 -> 7748 bytes
-rw-r--r--leptonica/prog/recog/digits/digit7.comp.tifbin0 -> 5354 bytes
-rw-r--r--leptonica/prog/recog/digits/digit8.comp.tifbin0 -> 8310 bytes
-rw-r--r--leptonica/prog/recog/digits/digit9.comp.tifbin0 -> 7840 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set01.pabin0 -> 31628 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set02.pabin0 -> 27769 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set03.pabin0 -> 30837 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set04.pabin0 -> 30919 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set05.pabin0 -> 31727 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set06.pabin0 -> 31833 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set07.pabin0 -> 30147 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set08.pabin0 -> 31465 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set09.pabin0 -> 31646 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set10.pabin0 -> 31606 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set11.pabin0 -> 30785 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set12.pabin0 -> 28035 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set13.pabin0 -> 29991 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set14.pabin0 -> 22769 bytes
-rw-r--r--leptonica/prog/recog/digits/digit_set15.pabin0 -> 9339 bytes
-rw-r--r--leptonica/prog/recog/digits/page.306.pngbin0 -> 1673 bytes
-rw-r--r--leptonica/prog/recog/digits/page.590.pngbin0 -> 4181 bytes
-rw-r--r--leptonica/prog/recog/sets/samples06.pngbin0 -> 17243 bytes
-rw-r--r--leptonica/prog/recog/sets/test01.pabin0 -> 176457 bytes
-rw-r--r--leptonica/prog/recog/sets/test02.pabin0 -> 162495 bytes
-rw-r--r--leptonica/prog/recog/sets/test03.pabin0 -> 11701 bytes
-rw-r--r--leptonica/prog/recog/sets/test05.pabin0 -> 100922 bytes
-rw-r--r--leptonica/prog/recog/sets/test06.pabin0 -> 82665 bytes
-rw-r--r--leptonica/prog/recog/sets/train01.pabin0 -> 7199 bytes
-rw-r--r--leptonica/prog/recog/sets/train02.pabin0 -> 25789 bytes
-rw-r--r--leptonica/prog/recog/sets/train03.pabin0 -> 30837 bytes
-rw-r--r--leptonica/prog/recog/sets/train04.pabin0 -> 41903 bytes
-rw-r--r--leptonica/prog/recog/sets/train05.pabin0 -> 30785 bytes
-rw-r--r--leptonica/prog/recog/sets/train06.pabin0 -> 31423 bytes
-rw-r--r--leptonica/prog/recog_bootnum1.c320
-rw-r--r--leptonica/prog/recog_bootnum2.c183
-rw-r--r--leptonica/prog/recog_bootnum3.c90
-rw-r--r--leptonica/prog/recogsort.c124
-rw-r--r--leptonica/prog/recogtest1.c176
-rw-r--r--leptonica/prog/recogtest2.c193
-rw-r--r--leptonica/prog/recogtest3.c182
-rw-r--r--leptonica/prog/recogtest4.c131
-rw-r--r--leptonica/prog/recogtest5.c115
-rw-r--r--leptonica/prog/recogtest6.c134
-rw-r--r--leptonica/prog/recogtest7.c144
-rw-r--r--leptonica/prog/rectangle_reg.c182
-rw-r--r--leptonica/prog/redcover.jpgbin0 -> 21690 bytes
-rw-r--r--leptonica/prog/reducetest.c75
-rwxr-xr-xleptonica/prog/reg_wrapper.sh48
-rw-r--r--leptonica/prog/removecmap.c80
-rw-r--r--leptonica/prog/renderfonts.c103
-rw-r--r--leptonica/prog/replacebytes.c82
-rw-r--r--leptonica/prog/rgb16.tifbin0 -> 64396 bytes
-rw-r--r--leptonica/prog/rock.pngbin0 -> 84115 bytes
-rw-r--r--leptonica/prog/rotate1_reg.c195
-rw-r--r--leptonica/prog/rotate2_reg.c175
-rw-r--r--leptonica/prog/rotate_it.c113
-rw-r--r--leptonica/prog/rotatefastalt.c351
-rw-r--r--leptonica/prog/rotateorth_reg.c146
-rw-r--r--leptonica/prog/rotateorthtest1.c145
-rw-r--r--leptonica/prog/rotatetest1.c224
-rw-r--r--leptonica/prog/runlengthtest.c106
-rw-r--r--leptonica/prog/scale_it.c143
-rw-r--r--leptonica/prog/scale_reg.c307
-rw-r--r--leptonica/prog/scaleandtile.c103
-rw-r--r--leptonica/prog/scaletest1.c92
-rw-r--r--leptonica/prog/scaletest2.c363
-rw-r--r--leptonica/prog/scots-frag.tifbin0 -> 210976 bytes
-rw-r--r--leptonica/prog/seedfilltest.c170
-rw-r--r--leptonica/prog/seedspread_reg.c160
-rw-r--r--leptonica/prog/selio_reg.c133
-rw-r--r--leptonica/prog/settest.c146
-rw-r--r--leptonica/prog/sevens.tifbin0 -> 742 bytes
-rw-r--r--leptonica/prog/sharptest.c70
-rw-r--r--leptonica/prog/shear1_reg.c283
-rw-r--r--leptonica/prog/shear2_reg.c185
-rw-r--r--leptonica/prog/shearer.148.tifbin0 -> 81910 bytes
-rw-r--r--leptonica/prog/sheartest.c169
-rw-r--r--leptonica/prog/showboxes.pacbin0 -> 153286 bytes
-rw-r--r--leptonica/prog/showboxes1.baa27
-rw-r--r--leptonica/prog/showboxes2.baa51
-rw-r--r--leptonica/prog/showedges.c71
-rw-r--r--leptonica/prog/singlecc.tifbin0 -> 398 bytes
-rw-r--r--leptonica/prog/skew_reg.c132
-rw-r--r--leptonica/prog/skewtest.c192
-rw-r--r--leptonica/prog/smallpix_reg.c231
-rw-r--r--leptonica/prog/smoothedge_reg.c100
-rw-r--r--leptonica/prog/sorttest.c100
-rw-r--r--leptonica/prog/speckle.pngbin0 -> 358 bytes
-rw-r--r--leptonica/prog/speckle2.pngbin0 -> 482 bytes
-rw-r--r--leptonica/prog/speckle4.pngbin0 -> 1319 bytes
-rw-r--r--leptonica/prog/speckle_reg.c119
-rw-r--r--leptonica/prog/splitcomp_reg.c158
-rw-r--r--leptonica/prog/splitimage2pdf.c71
-rw-r--r--leptonica/prog/stampede2.jpgbin0 -> 75390 bytes
-rw-r--r--leptonica/prog/string_reg.c229
-rw-r--r--leptonica/prog/stringtemplate1.txt96
-rw-r--r--leptonica/prog/stringtemplate2.txt61
-rw-r--r--leptonica/prog/subpixel_reg.c209
-rw-r--r--leptonica/prog/sudoku1.dat12
-rw-r--r--leptonica/prog/sudoku2.dat11
-rw-r--r--leptonica/prog/sudoku3.dat14
-rw-r--r--leptonica/prog/sudoku4.dat13
-rw-r--r--leptonica/prog/sudoku5.dat14
-rw-r--r--leptonica/prog/sudoku6.dat11
-rw-r--r--leptonica/prog/sudoku7.dat11
-rw-r--r--leptonica/prog/sudokutest.c93
-rw-r--r--leptonica/prog/table.15.tifbin0 -> 24694 bytes
-rw-r--r--leptonica/prog/table.150.pngbin0 -> 18003 bytes
-rw-r--r--leptonica/prog/table.27.tifbin0 -> 25888 bytes
-rw-r--r--leptonica/prog/test-1bit-alpha.pngbin0 -> 16598 bytes
-rw-r--r--leptonica/prog/test-87220.59.pngbin0 -> 7039 bytes
-rw-r--r--leptonica/prog/test-cmap-alpha.pngbin0 -> 830 bytes
-rw-r--r--leptonica/prog/test-cmap-alpha2.pngbin0 -> 557 bytes
-rw-r--r--leptonica/prog/test-fulltrans-alpha.pngbin0 -> 260 bytes
-rw-r--r--leptonica/prog/test-gray-alpha.pngbin0 -> 561 bytes
-rw-r--r--leptonica/prog/test1.bmpbin0 -> 30734 bytes
-rw-r--r--leptonica/prog/test1.pngbin0 -> 14824 bytes
-rw-r--r--leptonica/prog/test16.pngbin0 -> 25630 bytes
-rw-r--r--leptonica/prog/test16.tifbin0 -> 23570 bytes
-rw-r--r--leptonica/prog/test24.jpgbin0 -> 248613 bytes
-rw-r--r--leptonica/prog/test32-alpha.pngbin0 -> 1284 bytes
-rw-r--r--leptonica/prog/test8.jpgbin0 -> 55081 bytes
-rw-r--r--leptonica/prog/testangle.na19
-rw-r--r--leptonica/prog/testbuffer.tifbin0 -> 14570 bytes
-rw-r--r--leptonica/prog/testfile1.pdfbin0 -> 1735 bytes
-rw-r--r--leptonica/prog/testfile2.pdfbin0 -> 2396 bytes
-rw-r--r--leptonica/prog/testscore.na19
-rw-r--r--leptonica/prog/tetons.jpgbin0 -> 30174 bytes
-rw-r--r--leptonica/prog/textorient.c95
-rw-r--r--leptonica/prog/texturefill_reg.c194
-rw-r--r--leptonica/prog/threshnorm_reg.c105
-rw-r--r--leptonica/prog/tickets.tifbin0 -> 138180 bytes
-rw-r--r--leptonica/prog/tiffpdftest.c142
-rw-r--r--leptonica/prog/toc.99.tifbin0 -> 7776 bytes
-rw-r--r--leptonica/prog/topotest.pngbin0 -> 22899 bytes
-rw-r--r--leptonica/prog/translate_reg.c163
-rw-r--r--leptonica/prog/trctest.c66
-rw-r--r--leptonica/prog/tribune-page-4x.pngbin0 -> 84393 bytes
-rw-r--r--leptonica/prog/tribune-t.pngbin0 -> 1793 bytes
-rw-r--r--leptonica/prog/tribune-word.pngbin0 -> 7015 bytes
-rw-r--r--leptonica/prog/turingtest.pngbin0 -> 13930 bytes
-rw-r--r--leptonica/prog/two-peak-histo.na260
-rw-r--r--leptonica/prog/underline1.jpgbin0 -> 33885 bytes
-rw-r--r--leptonica/prog/underline2.jpgbin0 -> 27295 bytes
-rw-r--r--leptonica/prog/underline3.jpgbin0 -> 31659 bytes
-rw-r--r--leptonica/prog/underline4.jpgbin0 -> 25106 bytes
-rw-r--r--leptonica/prog/underline5.jpgbin0 -> 18646 bytes
-rw-r--r--leptonica/prog/underline6.jpgbin0 -> 21221 bytes
-rw-r--r--leptonica/prog/underline7.jpgbin0 -> 33031 bytes
-rw-r--r--leptonica/prog/underlinetest.c95
-rw-r--r--leptonica/prog/w91frag.jpgbin0 -> 137531 bytes
-rw-r--r--leptonica/prog/warped_paper.jpgbin0 -> 84631 bytes
-rw-r--r--leptonica/prog/warped_sudoku.jpgbin0 -> 49133 bytes
-rw-r--r--leptonica/prog/warper_reg.c138
-rw-r--r--leptonica/prog/warpertest.c256
-rw-r--r--leptonica/prog/watershed_reg.c157
-rw-r--r--leptonica/prog/weasel-113c.pngbin0 -> 2470 bytes
-rw-r--r--leptonica/prog/weasel-44c.pngbin0 -> 1540 bytes
-rw-r--r--leptonica/prog/weasel-4c.2.pngbin0 -> 571 bytes
-rw-r--r--leptonica/prog/weasel-64g.pngbin0 -> 2387 bytes
-rw-r--r--leptonica/prog/weasel-8g.pngbin0 -> 1066 bytes
-rw-r--r--leptonica/prog/weasel2.4c.bmpbin0 -> 3414 bytes
-rw-r--r--leptonica/prog/weasel2.4c.pngbin0 -> 965 bytes
-rw-r--r--leptonica/prog/weasel2.4g.pngbin0 -> 744 bytes
-rw-r--r--leptonica/prog/weasel2.pngbin0 -> 720 bytes
-rw-r--r--leptonica/prog/weasel32.pngbin0 -> 3383 bytes
-rw-r--r--leptonica/prog/weasel4.11c.pngbin0 -> 985 bytes
-rw-r--r--leptonica/prog/weasel4.16c.pngbin0 -> 1559 bytes
-rw-r--r--leptonica/prog/weasel4.16g.pngbin0 -> 1524 bytes
-rw-r--r--leptonica/prog/weasel4.5g.pngbin0 -> 852 bytes
-rw-r--r--leptonica/prog/weasel4.8g.pngbin0 -> 1028 bytes
-rw-r--r--leptonica/prog/weasel4.pngbin0 -> 1461 bytes
-rw-r--r--leptonica/prog/weasel8.149g.pngbin0 -> 3117 bytes
-rw-r--r--leptonica/prog/weasel8.16g.nocmap.pngbin0 -> 1601 bytes
-rw-r--r--leptonica/prog/weasel8.16g.pngbin0 -> 1539 bytes
-rw-r--r--leptonica/prog/weasel8.240c.pngbin0 -> 3628 bytes
-rw-r--r--leptonica/prog/weasel8.5g.nocmap.pngbin0 -> 1083 bytes
-rw-r--r--leptonica/prog/weasel8.5g.pngbin0 -> 927 bytes
-rw-r--r--leptonica/prog/weasel8.pngbin0 -> 1593 bytes
-rw-r--r--leptonica/prog/webpanimio_reg.c96
-rw-r--r--leptonica/prog/webpio_reg.c147
-rw-r--r--leptonica/prog/wet-day.jpgbin0 -> 192734 bytes
-rw-r--r--leptonica/prog/witten.tifbin0 -> 108326 bytes
-rw-r--r--leptonica/prog/wordboxes_reg.c288
-rw-r--r--leptonica/prog/words.15.tifbin0 -> 14630 bytes
-rw-r--r--leptonica/prog/words.44.tifbin0 -> 19040 bytes
-rw-r--r--leptonica/prog/wordsinorder.c145
-rw-r--r--leptonica/prog/writemtiff.c61
-rw-r--r--leptonica/prog/writetext_reg.c177
-rw-r--r--leptonica/prog/wyom.jpgbin0 -> 122842 bytes
-rw-r--r--leptonica/prog/xformbox_reg.c325
-rw-r--r--leptonica/prog/xtractprotos.c263
-rw-r--r--leptonica/prog/yuvtest.c221
-rw-r--r--leptonica/prog/zanotti-78.jpgbin0 -> 154657 bytes
-rw-r--r--leptonica/prog/zier.jpgbin0 -> 25502 bytes
-rw-r--r--leptonica/src/CMakeLists.txt99
-rw-r--r--leptonica/src/Makefile.am102
-rw-r--r--leptonica/src/adaptmap.c2948
-rw-r--r--leptonica/src/affine.c1624
-rw-r--r--leptonica/src/affinecompose.c665
-rw-r--r--leptonica/src/allheaders.h2779
-rw-r--r--leptonica/src/allheaders_bot.txt5
-rw-r--r--leptonica/src/allheaders_top.txt37
-rw-r--r--leptonica/src/alltypes.h67
-rw-r--r--leptonica/src/array.h158
-rw-r--r--leptonica/src/arrayaccess.c364
-rw-r--r--leptonica/src/arrayaccess.h270
-rw-r--r--leptonica/src/bardecode.c1047
-rw-r--r--leptonica/src/baseline.c600
-rw-r--r--leptonica/src/bbuffer.c485
-rw-r--r--leptonica/src/bbuffer.h60
-rw-r--r--leptonica/src/bilateral.c829
-rw-r--r--leptonica/src/bilateral.h136
-rw-r--r--leptonica/src/bilinear.c912
-rw-r--r--leptonica/src/binarize.c1101
-rw-r--r--leptonica/src/binexpand.c304
-rw-r--r--leptonica/src/binreduce.c412
-rw-r--r--leptonica/src/blend.c2295
-rw-r--r--leptonica/src/bmf.c876
-rw-r--r--leptonica/src/bmf.h64
-rw-r--r--leptonica/src/bmfdata.h636
-rw-r--r--leptonica/src/bmp.h124
-rw-r--r--leptonica/src/bmpio.c646
-rw-r--r--leptonica/src/bmpiostub.c72
-rw-r--r--leptonica/src/bootnumgen1.c308
-rw-r--r--leptonica/src/bootnumgen2.c291
-rw-r--r--leptonica/src/bootnumgen3.c368
-rw-r--r--leptonica/src/bootnumgen4.c823
-rw-r--r--leptonica/src/boxbasic.c2440
-rw-r--r--leptonica/src/boxfunc1.c2737
-rw-r--r--leptonica/src/boxfunc2.c1933
-rw-r--r--leptonica/src/boxfunc3.c1629
-rw-r--r--leptonica/src/boxfunc4.c1426
-rw-r--r--leptonica/src/boxfunc5.c2212
-rw-r--r--leptonica/src/bytearray.c653
-rw-r--r--leptonica/src/ccbord.c2631
-rw-r--r--leptonica/src/ccbord.h121
-rw-r--r--leptonica/src/ccthin.c476
-rw-r--r--leptonica/src/checkerboard.c316
-rw-r--r--leptonica/src/classapp.c1050
-rw-r--r--leptonica/src/colorcontent.c2051
-rw-r--r--leptonica/src/colorfill.c913
-rw-r--r--leptonica/src/colorfill.h67
-rw-r--r--leptonica/src/coloring.c1106
-rw-r--r--leptonica/src/colormap.c2433
-rw-r--r--leptonica/src/colormorph.c128
-rw-r--r--leptonica/src/colorquant1.c4157
-rw-r--r--leptonica/src/colorquant2.c1692
-rw-r--r--leptonica/src/colorseg.c658
-rw-r--r--leptonica/src/colorspace.c2471
-rw-r--r--leptonica/src/compare.c3611
-rw-r--r--leptonica/src/conncomp.c1243
-rw-r--r--leptonica/src/convertfiles.c149
-rw-r--r--leptonica/src/convolve.c2582
-rw-r--r--leptonica/src/correlscore.c883
-rw-r--r--leptonica/src/dewarp.h191
-rw-r--r--leptonica/src/dewarp1.c1715
-rw-r--r--leptonica/src/dewarp2.c2017
-rw-r--r--leptonica/src/dewarp3.c1016
-rw-r--r--leptonica/src/dewarp4.c1179
-rw-r--r--leptonica/src/dnabasic.c1728
-rw-r--r--leptonica/src/dnafunc1.c452
-rw-r--r--leptonica/src/dnahash.c605
-rw-r--r--leptonica/src/dwacomb.2.c299
-rw-r--r--leptonica/src/dwacomblow.2.c4970
-rw-r--r--leptonica/src/edge.c647
-rw-r--r--leptonica/src/encoding.c738
-rw-r--r--leptonica/src/endianness.h.in11
-rw-r--r--leptonica/src/enhance.c2360
-rw-r--r--leptonica/src/environ.h586
-rw-r--r--leptonica/src/fhmtauto.c823
-rw-r--r--leptonica/src/fhmtgen.1.c177
-rw-r--r--leptonica/src/fhmtgenlow.1.c445
-rw-r--r--leptonica/src/finditalic.c241
-rw-r--r--leptonica/src/flipdetect.c821
-rw-r--r--leptonica/src/flipdetectdwa.c.notused668
-rw-r--r--leptonica/src/fmorphauto.c879
-rw-r--r--leptonica/src/fmorphgen.1.c277
-rw-r--r--leptonica/src/fmorphgenlow.1.c5862
-rw-r--r--leptonica/src/fpix1.c2190
-rw-r--r--leptonica/src/fpix2.c2447
-rw-r--r--leptonica/src/gifio.c687
-rw-r--r--leptonica/src/gifiostub.c72
-rw-r--r--leptonica/src/gplot.c1364
-rw-r--r--leptonica/src/gplot.h96
-rw-r--r--leptonica/src/graphics.c2910
-rw-r--r--leptonica/src/graymorph.c1376
-rw-r--r--leptonica/src/grayquant.c2913
-rw-r--r--leptonica/src/heap.c589
-rw-r--r--leptonica/src/heap.h87
-rw-r--r--leptonica/src/hmttemplate1.txt170
-rw-r--r--leptonica/src/hmttemplate2.txt103
-rw-r--r--leptonica/src/imageio.h245
-rw-r--r--leptonica/src/jbclass.c2569
-rw-r--r--leptonica/src/jbclass.h142
-rw-r--r--leptonica/src/jp2kheader.c321
-rw-r--r--leptonica/src/jp2kheaderstub.c75
-rw-r--r--leptonica/src/jp2kio.c949
-rw-r--r--leptonica/src/jp2kiostub.c98
-rw-r--r--leptonica/src/jpegio.c1303
-rw-r--r--leptonica/src/jpegiostub.c151
-rw-r--r--leptonica/src/kernel.c1286
-rw-r--r--leptonica/src/leptonica-license.txt26
-rw-r--r--leptonica/src/leptwin.c368
-rw-r--r--leptonica/src/leptwin.h45
-rw-r--r--leptonica/src/libversions.c208
-rw-r--r--leptonica/src/list.c814
-rw-r--r--leptonica/src/list.h90
-rw-r--r--leptonica/src/mainpage.txt47
-rw-r--r--leptonica/src/makefile.static401
-rw-r--r--leptonica/src/map.c268
-rw-r--r--leptonica/src/maze.c912
-rw-r--r--leptonica/src/morph.c1915
-rw-r--r--leptonica/src/morph.h225
-rw-r--r--leptonica/src/morphapp.c1636
-rw-r--r--leptonica/src/morphdwa.c1599
-rw-r--r--leptonica/src/morphseq.c1243
-rw-r--r--leptonica/src/morphtemplate1.txt224
-rw-r--r--leptonica/src/morphtemplate2.txt104
-rw-r--r--leptonica/src/numabasic.c2097
-rw-r--r--leptonica/src/numafunc1.c3697
-rw-r--r--leptonica/src/numafunc2.c3319
-rw-r--r--leptonica/src/pageseg.c2473
-rw-r--r--leptonica/src/paintcmap.c765
-rw-r--r--leptonica/src/parseprotos.c978
-rw-r--r--leptonica/src/partify.c315
-rw-r--r--leptonica/src/partition.c662
-rw-r--r--leptonica/src/pdfio1.c2255
-rw-r--r--leptonica/src/pdfio1stub.c309
-rw-r--r--leptonica/src/pdfio2.c2608
-rw-r--r--leptonica/src/pdfio2stub.c172
-rw-r--r--leptonica/src/pix.h1302
-rw-r--r--leptonica/src/pix1.c2039
-rw-r--r--leptonica/src/pix2.c3573
-rw-r--r--leptonica/src/pix3.c3716
-rw-r--r--leptonica/src/pix4.c3568
-rw-r--r--leptonica/src/pix5.c3221
-rw-r--r--leptonica/src/pixabasic.c3283
-rw-r--r--leptonica/src/pixacc.c355
-rw-r--r--leptonica/src/pixafunc1.c3112
-rw-r--r--leptonica/src/pixafunc2.c2610
-rw-r--r--leptonica/src/pixalloc.c532
-rw-r--r--leptonica/src/pixarith.c1633
-rw-r--r--leptonica/src/pixcomp.c2481
-rw-r--r--leptonica/src/pixconv.c4297
-rw-r--r--leptonica/src/pixlabel.c637
-rw-r--r--leptonica/src/pixtiling.c422
-rw-r--r--leptonica/src/pngio.c2135
-rw-r--r--leptonica/src/pngiostub.c143
-rw-r--r--leptonica/src/pnmio.c1534
-rw-r--r--leptonica/src/pnmiostub.c120
-rw-r--r--leptonica/src/projective.c932
-rw-r--r--leptonica/src/psio1.c1077
-rw-r--r--leptonica/src/psio1stub.c137
-rw-r--r--leptonica/src/psio2.c2044
-rw-r--r--leptonica/src/psio2stub.c160
-rw-r--r--leptonica/src/ptabasic.c1607
-rw-r--r--leptonica/src/ptafunc1.c2667
-rw-r--r--leptonica/src/ptafunc2.c899
-rw-r--r--leptonica/src/ptra.c1010
-rw-r--r--leptonica/src/ptra.h97
-rw-r--r--leptonica/src/quadtree.c701
-rw-r--r--leptonica/src/queue.c326
-rw-r--r--leptonica/src/queue.h77
-rw-r--r--leptonica/src/rank.c544
-rw-r--r--leptonica/src/rbtree.c905
-rw-r--r--leptonica/src/rbtree.h91
-rw-r--r--leptonica/src/readbarcode.c1532
-rw-r--r--leptonica/src/readbarcode.h239
-rw-r--r--leptonica/src/readfile.c1633
-rw-r--r--leptonica/src/recog.h264
-rw-r--r--leptonica/src/recogbasic.c1231
-rw-r--r--leptonica/src/recogdid.c1078
-rw-r--r--leptonica/src/recogident.c1883
-rw-r--r--leptonica/src/recogtrain.c2482
-rw-r--r--leptonica/src/regutils.c887
-rw-r--r--leptonica/src/regutils.h141
-rw-r--r--leptonica/src/rop.c572
-rw-r--r--leptonica/src/roplow.c2506
-rw-r--r--leptonica/src/rotate.c598
-rw-r--r--leptonica/src/rotateam.c1132
-rw-r--r--leptonica/src/rotateorth.c715
-rw-r--r--leptonica/src/rotateshear.c498
-rw-r--r--leptonica/src/runlength.c814
-rw-r--r--leptonica/src/sarray1.c2070
-rw-r--r--leptonica/src/sarray2.c730
-rw-r--r--leptonica/src/scale1.c3764
-rw-r--r--leptonica/src/scale2.c2347
-rw-r--r--leptonica/src/seedfill.c3456
-rw-r--r--leptonica/src/sel1.c2446
-rw-r--r--leptonica/src/sel2.c897
-rw-r--r--leptonica/src/selgen.c987
-rw-r--r--leptonica/src/shear.c854
-rw-r--r--leptonica/src/skew.c1247
-rw-r--r--leptonica/src/spixio.c518
-rw-r--r--leptonica/src/stack.c293
-rw-r--r--leptonica/src/stack.h70
-rw-r--r--leptonica/src/stringcode.c806
-rw-r--r--leptonica/src/stringcode.h61
-rw-r--r--leptonica/src/stringtemplate1.txt96
-rw-r--r--leptonica/src/stringtemplate2.txt61
-rw-r--r--leptonica/src/strokes.c439
-rw-r--r--leptonica/src/sudoku.c881
-rw-r--r--leptonica/src/sudoku.h77
-rw-r--r--leptonica/src/textops.c1129
-rw-r--r--leptonica/src/tiffio.c2895
-rw-r--r--leptonica/src/tiffiostub.c242
-rw-r--r--leptonica/src/utils1.c1352
-rw-r--r--leptonica/src/utils2.c3485
-rw-r--r--leptonica/src/warper.c1394
-rw-r--r--leptonica/src/watershed.c1126
-rw-r--r--leptonica/src/watershed.h64
-rw-r--r--leptonica/src/webpanimio.c273
-rw-r--r--leptonica/src/webpanimiostub.c71
-rw-r--r--leptonica/src/webpio.c417
-rw-r--r--leptonica/src/webpiostub.c99
-rw-r--r--leptonica/src/writefile.c1209
-rw-r--r--leptonica/src/zlibmem.c282
-rw-r--r--leptonica/src/zlibmemstub.c59
-rw-r--r--leptonica/style-guide.txt230
-rw-r--r--leptonica/sw.cpp391
-rw-r--r--leptonica/version-notes.html1600
950 files changed, 340126 insertions, 0 deletions
diff --git a/leptonica/.github/workflows/sw.yml b/leptonica/.github/workflows/sw.yml
new file mode 100644
index 00000000..d8b3ba22
--- /dev/null
+++ b/leptonica/.github/workflows/sw.yml
@@ -0,0 +1,23 @@
+name: sw
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [windows-latest, ubuntu-20.04, macOS-latest]
+
+ steps:
+ - uses: actions/checkout@v1
+ - uses: egorpugin/sw-action@master
+
+ - name: build
+ if: matrix.os == 'windows-latest'
+ run: ./sw -static -shared -config d,r build
+
+ - name: build
+ if: matrix.os != 'windows-latest'
+ run: ./sw -static -shared -config d,r build
diff --git a/leptonica/.travis.yml b/leptonica/.travis.yml
new file mode 100644
index 00000000..a48f348d
--- /dev/null
+++ b/leptonica/.travis.yml
@@ -0,0 +1,65 @@
+language: c
+
+notifications:
+ email: false
+
+sudo: required
+dist: trusty
+osx_image: xcode12.2
+
+os:
+ - linux
+ - osx
+
+branches:
+ only:
+ - master
+
+addons:
+ apt:
+ sources:
+ - ubuntu-toolchain-r-test
+ packages:
+ - cmake
+ # - libwebp-dev
+ #- libpng12-dev
+ #- libjpeg8-dev
+ #- libtiff5-dev
+ #- libgif-dev
+ #- libopenjp2-7-dev
+ update: true
+ homebrew:
+ packages:
+ - webp
+ - openjpeg
+ update: true
+
+
+before_install:
+ - if [[ $TRAVIS_OS_NAME == linux ]]; then LINUX=true; fi
+ - if [[ $TRAVIS_OS_NAME == osx ]]; then OSX=true; fi
+
+install:
+ - if [[ $LINUX ]]; then sudo apt-get update; fi
+ - if [[ $LINUX ]]; then sudo apt-get install -y libpng12-dev libjpeg8-dev libtiff5-dev libopenjpeg-dev; fi
+ #- if [[ $LINUX ]]; then sudo apt-get install -y libwebp-dev libpng12-dev libjpeg8-dev libtiff5-dev libgif-dev libopenjpeg-dev; fi
+ # install giflib5
+ - if [[ $LINUX ]]; then wget https://sourceforge.net/projects/giflib/files/giflib-5.2.1.tar.gz; fi
+ - if [[ $LINUX ]]; then tar -xf giflib-5.2.1.tar.gz; fi
+ - if [[ $LINUX ]]; then cd giflib-5.2.1; fi
+ - if [[ $LINUX ]]; then make && sudo make install; fi
+ - if [[ $LINUX ]]; then cd -; fi
+ - if [[ $LINUX ]]; then wget https://github.com/webmproject/libwebp/archive/v1.0.2.tar.gz; fi
+ - if [[ $LINUX ]]; then tar -xf v1.0.2.tar.gz; fi
+ - if [[ $LINUX ]]; then cd libwebp-1.0.2; fi
+ - if [[ $LINUX ]]; then ./autogen.sh; fi
+ - if [[ $LINUX ]]; then ./configure --enable-libwebpmux --enable-libwebpdecoder && make && sudo make install; fi
+ - if [[ $LINUX ]]; then cd -; fi
+ #- if [[ $LINUX ]]; then wget https://www.cmake.org/files/v3.6/cmake-3.6.1-Linux-x86_64.sh; fi
+ #- if [[ $LINUX ]]; then sudo sh cmake-3.6.1-Linux-x86_64.sh --skip-license --prefix=/usr; fi
+
+script:
+ - mkdir build
+ - cd build
+ - cmake .. -DBUILD_PROG=1
+ - make
diff --git a/leptonica/CMakeLists.txt b/leptonica/CMakeLists.txt
new file mode 100644
index 00000000..919b3c33
--- /dev/null
+++ b/leptonica/CMakeLists.txt
@@ -0,0 +1,294 @@
+#
+# leptonica
+#
+
+###############################################################################
+#
+# cmake settings
+#
+###############################################################################
+
+cmake_minimum_required(VERSION 3.1.3)
+cmake_policy(SET CMP0054 NEW)
+
+# In-source builds are disabled.
+if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_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_CURRENT_SOURCE_DIR}/cmake")
+
+set(EXECUTABLE_OUTPUT_PATH "${CMAKE_CURRENT_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 81)
+set(VERSION_PATCH 0)
+set(VERSION_PLAIN ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
+set(MINIMUM_WEBPMUX_VERSION 0.5.0)
+
+option(BUILD_PROG "Build utility programs" OFF)
+if (WIN32)
+ option(SW_BUILD "Build with sw" ON)
+else()
+ option(SW_BUILD "Build with sw" OFF)
+endif()
+if(UNIX)
+ option(SYM_LINK "Create symlink leptonica -> lept on UNIX" OFF)
+endif()
+
+if(NOT SW_BUILD)
+ find_package(GIF)
+ find_package(JPEG)
+ find_package(PNG)
+ find_package(TIFF)
+ find_package(ZLIB)
+ find_package(PkgConfig)
+ if (PKG_CONFIG_FOUND)
+ pkg_check_modules(WEBP libwebp QUIET)
+ pkg_check_modules(WEBPMUX libwebpmux>=${MINIMUM_WEBPMUX_VERSION} QUIET)
+ pkg_check_modules(JP2K libopenjp2>=2.0 QUIET)
+ endif()
+ if(NOT WEBP)
+ find_path(WEBP_INCLUDE_DIR /webp/decode.h)
+ find_library(WEBP_LIBRARY NAMES webp)
+ if (WEBP_INCLUDE_DIR AND WEBP_LIBRARY)
+ set(WEBP 1)
+ set(WEBP_FOUND TRUE)
+ set(WEBP_LIBRARIES ${WEBP_LIBRARY})
+ set(WEBP_INCLUDE_DIRS ${WEBP_INCLUDE_DIR})
+ endif()
+ endif()
+ if(NOT WEBPMUX)
+ find_path(WEBPMUX_INCLUDE_DIR /webp/mux.h)
+ #TODO: check minimal required version
+ if(NOT WEBPMUX_INCLUDE_DIR)
+ message(STATUS "Can not find: /webp/mux.h")
+ endif()
+ if(NOT "${WEBPMUX_INCLUDE_DIR}" STREQUAL "${WEBP_INCLUDE_DIR}")
+ set(WEBP_INCLUDE_DIRS ${WEBP_INCLUDE_DIRS} ${WEBPMUX_INCLUDE_DIR})
+ endif()
+ find_library(WEBPMUX_LIBRARY NAMES webpmux)
+ if (WEBPMUX_INCLUDE_DIR AND WEBPMUX_LIBRARY)
+ set(WEBPMUX 1)
+ set(HAVE_LIBWEBP_ANIM 1)
+ set(WEBPMUX_FOUND TRUE)
+ set(WEBP_LIBRARIES ${WEBP_LIBRARIES} ${WEBPMUX_LIBRARY})
+ endif()
+ endif()
+ if(NOT JP2K)
+ find_path(JP2K_INCLUDE_DIR /openjpeg-2.3/openjpeg.h)
+ find_library(JP2K_LIBRARY NAMES openjp2)
+ if (JP2K_INCLUDE_DIR AND JP2K_LIBRARY)
+ set(JP2K 1)
+ set(JP2K_FOUND TRUE)
+ set(JP2K_LIBRARIES ${JP2K_LIBRARY})
+ set(JP2K_INCLUDE_DIRS ${JP2K_INCLUDE_DIR})
+ set(HAVE_LIBJP2K 1)
+ endif()
+ endif()
+ if(NOT JP2K)
+ find_path(JP2K_INCLUDE_DIR /openjpeg-2.4/openjpeg.h)
+ find_library(JP2K_LIBRARY NAMES openjp2)
+ if (JP2K_INCLUDE_DIR AND JP2K_LIBRARY)
+ set(JP2K 1)
+ set(JP2K_FOUND TRUE)
+ set(JP2K_LIBRARIES ${JP2K_LIBRARY})
+ set(JP2K_INCLUDE_DIRS ${JP2K_INCLUDE_DIR})
+ set(HAVE_LIBJP2K 1)
+ endif()
+ endif()
+else() # SW_BUILD=ON
+ find_package(SW REQUIRED)
+ sw_add_package(
+ org.sw.demo.gif
+ org.sw.demo.jpeg
+ org.sw.demo.glennrp.png
+ org.sw.demo.tiff
+ org.sw.demo.webmproject.webp
+ org.sw.demo.uclouvain.openjpeg.openjp2
+ )
+ sw_execute()
+
+ add_definitions(
+ -DHAVE_LIBGIF=1
+ -DHAVE_LIBJPEG=1
+ -DHAVE_LIBPNG=1
+ -DHAVE_LIBTIFF=1
+ -DHAVE_LIBWEBP=1
+ -DHAVE_LIBWEBP_ANIM=1
+ -DHAVE_LIBZ=1
+
+ -DHAVE_LIBJP2K=1
+ -DLIBJP2K_HEADER="openjpeg.h"
+ )
+endif()
+
+###############################################################################
+#
+# compiler and linker
+#
+###############################################################################
+
+if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ set(CLANG 1)
+endif()
+
+# Set a default build type if none was specified
+if(NOT CMAKE_BUILD_TYPE)
+ message(STATUS "Setting build type to 'Release' as none was specified.")
+ set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
+else()
+ message("CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}")
+endif()
+
+include(CheckCCompilerFlag)
+include(CheckCXXCompilerFlag)
+
+macro(CHECK_CCXX_COMPILER_FLAG _FLAG _RESULT)
+ check_c_compiler_flag(${_FLAG} C_HAS${_RESULT})
+ check_cxx_compiler_flag(${_FLAG} CXX_HAS${_RESULT})
+endmacro()
+
+if (MSVC)
+ add_definitions(-D_CRT_SECURE_NO_WARNINGS)
+ add_compile_options("$<$<CONFIG:Release>:/W1;/Ox>")
+ add_compile_options("$<$<CONFIG:Debug>:/W4;/DDEBUG>")
+ if (NOT CLANG)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
+ endif()
+else()
+ if (CMAKE_BUILD_TYPE MATCHES "[Dd][Ee][Bb][Uu][Gg]")
+ # Enable warnings and Optimize for Debugging
+ foreach(FLAG -Wall -Wextra -Werror=format-security -pedantic -Og -g3 "-fdebug-prefix-map=${CMAKE_SOURCE_DIR}=.")
+ string(REGEX REPLACE "[^A-Za-z0-9]" "_" flag_var "${FLAG}")
+ check_ccxx_compiler_flag(${FLAG} ${flag_var})
+ if(C_HAS${flag_var})
+ set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${FLAG}")
+ endif()
+ if(CXX_HAS${flag_var})
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${FLAG}")
+ endif()
+ endforeach()
+ add_definitions(-DDEBUG)
+ endif()
+endif()
+
+###############################################################################
+#
+# configure
+#
+###############################################################################
+
+set(AUTOCONFIG_SRC ${CMAKE_CURRENT_BINARY_DIR}/config_auto.h.in)
+set(AUTOCONFIG ${CMAKE_CURRENT_BINARY_DIR}/src/config_auto.h)
+if (HAVE_LIBJP2K)
+ set(ADDITIONAL_INCLUDE_DIRS "${JP2K_INCLUDE_DIR}")
+endif()
+set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} "${CMAKE_PREFIX_PATH}/include" "${CMAKE_INSTALL_PREFIX}/include" "${ADDITIONAL_INCLUDE_DIRS}")
+
+
+include(Configure)
+
+configure_file(${AUTOCONFIG_SRC} ${AUTOCONFIG} @ONLY)
+
+set(INCLUDE_DIR "${CMAKE_INSTALL_PREFIX}/include" "${CMAKE_INSTALL_PREFIX}/include/leptonica")
+
+###############################################################################
+#
+# Show summary
+#
+###############################################################################
+message( STATUS )
+message( STATUS "General configuration for Leptonica ${VERSION_PLAIN}")
+message( STATUS "--------------------------------------------------------")
+message( STATUS "Build type: ${CMAKE_BUILD_TYPE}")
+message( STATUS "Compiler: ${CMAKE_C_COMPILER_ID}")
+message( STATUS "C compiler options: ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE_UP}} ${CMAKE_C_FLAGS}")
+message( STATUS "Linker options: ${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE_UP}}")
+message( STATUS "Install directory: ${CMAKE_INSTALL_PREFIX}")
+message( STATUS )
+message( STATUS "Build with sw [SW_BUILD]: ${SW_BUILD}")
+message( STATUS "Build utility programs [BUILD_PROG]: ${BUILD_PROG}")
+if(NOT SW_BUILD)
+ message( STATUS "Used ZLIB library: ${ZLIB_LIBRARIES}")
+ message( STATUS "Used PNG library: ${PNG_LIBRARIES}")
+ message( STATUS "Used JPEG library: ${JPEG_LIBRARIES}")
+ message( STATUS "Used JP2K library: ${JP2K_LIBRARIES}")
+ message( STATUS "Used TIFF library: ${TIFF_LIBRARIES}")
+ message( STATUS "Used GIF library: ${GIF_LIBRARIES}")
+ message( STATUS "Used WEBP library: ${WEBP_LIBRARIES}")
+endif()
+message( STATUS "--------------------------------------------------------")
+message( STATUS )
+
+###############################################################################
+#
+# build
+#
+###############################################################################
+
+add_definitions(-DHAVE_CONFIG_H)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/src)
+
+if (BUILD_PROG)
+add_subdirectory(prog)
+endif()
+
+add_subdirectory(src)
+
+get_target_property(leptonica_NAME leptonica NAME)
+get_target_property(leptonica_VERSION leptonica VERSION)
+get_target_property(leptonica_OUTPUT_NAME leptonica OUTPUT_NAME)
+
+configure_file(lept.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/lept.pc @ONLY)
+configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/LeptonicaConfig-version.cmake.in
+ ${CMAKE_CURRENT_BINARY_DIR}/LeptonicaConfig-version.cmake @ONLY)
+configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/LeptonicaConfig.cmake.in
+ ${CMAKE_CURRENT_BINARY_DIR}/LeptonicaConfig.cmake @ONLY)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lept.pc DESTINATION lib/pkgconfig)
+install(FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/LeptonicaConfig.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/LeptonicaConfig-version.cmake
+ DESTINATION lib/cmake)
+
+########################################
+# uninstall target
+########################################
+if(NOT TARGET uninstall)
+ configure_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/cmake_uninstall.cmake.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
+ IMMEDIATE @ONLY)
+
+ add_custom_target(uninstall
+ COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
+endif()
+
+###############################################################################
diff --git a/leptonica/Doxyfile b/leptonica/Doxyfile
new file mode 100644
index 00000000..5b8b0983
--- /dev/null
+++ b/leptonica/Doxyfile
@@ -0,0 +1,2432 @@
+# Doxyfile 1.8.14
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = Leptonica
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER = 1.80.0
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF = "Image processing and image analysis suite"
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO = moller52.jpg
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = NO
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines (in the resulting output). You can put ^^ in the value part of an
+# alias to insert a newline as if a physical newline was in the original file.
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING = inc=C
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 0.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS = 0
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = YES
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE = doxygen.log
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = src
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
+# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
+
+FILE_PATTERNS = *.c \
+ *.h \
+ mainpage.txt
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH = prog
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS = *.c \
+ *.h
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH = prog
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES = moller52.jpg
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 60
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via Javascript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have Javascript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = YES
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: https://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = YES
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment.
+# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP = YES
diff --git a/leptonica/Makefile.am b/leptonica/Makefile.am
new file mode 100644
index 00000000..fda474e4
--- /dev/null
+++ b/leptonica/Makefile.am
@@ -0,0 +1,85 @@
+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 autogen.sh Doxyfile
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = lept.pc
+
+# Cmake configs:
+lept_cmakeconfigdir = $(libdir)/cmake/
+lept_cmakeconfig_DATA = \
+ cmake/templates/LeptonicaConfig.cmake \
+ cmake/templates/LeptonicaConfig-version.cmake
+
+SUBDIRS = src prog
+
+# The fuzzing tests are run by OSS-Fuzz https://oss-fuzz.com/,
+# but can also be run locally.
+# Note: -fsanitize=fuzzer currently requires the clang++ compiler.
+
+# LIB_FUZZING_ENGINE can be overridden by the caller.
+# This is used by OSS-Fuzz.
+LIB_FUZZING_ENGINE ?= -fsanitize=fuzzer
+
+FUZZERS :=
+FUZZERS += adaptmap_fuzzer
+FUZZERS += affine_fuzzer
+FUZZERS += barcode_fuzzer
+FUZZERS += baseline_fuzzer
+FUZZERS += bilateral_fuzzer
+FUZZERS += binarize_fuzzer
+FUZZERS += blend_fuzzer
+FUZZERS += boxfunc_fuzzer
+FUZZERS += boxfunc3_fuzzer
+FUZZERS += boxfunc4_fuzzer
+FUZZERS += boxfunc5_fuzzer
+FUZZERS += ccbord_fuzzer
+FUZZERS += checkerboard_fuzzer
+FUZZERS += classapp_fuzzer
+FUZZERS += colorfill_fuzzer
+FUZZERS += colorquant_fuzzer
+FUZZERS += compare_fuzzer
+FUZZERS += dewarp_fuzzer
+FUZZERS += edge_fuzzer
+FUZZERS += enhance_fuzzer
+FUZZERS += fhmtgen_fuzzer
+FUZZERS += finditalic_fuzzer
+FUZZERS += flipdetect_fuzzer
+FUZZERS += fpix2_fuzzer
+FUZZERS += graphics_fuzzer
+FUZZERS += graymorph_fuzzer
+FUZZERS += grayquant_fuzzer
+FUZZERS += jpegiostub_fuzzer
+FUZZERS += kernel_fuzzer
+FUZZERS += mask_fuzzer
+FUZZERS += maze_fuzzer
+FUZZERS += morph_fuzzer
+FUZZERS += morphapp_fuzzer
+FUZZERS += pageseg_fuzzer
+FUZZERS += paintcmap_fuzzer
+FUZZERS += pix1_fuzzer
+FUZZERS += pix3_fuzzer
+FUZZERS += pix4_fuzzer
+FUZZERS += pixa_recog_fuzzer
+FUZZERS += pixconv_fuzzer
+FUZZERS += pix_orient_fuzzer
+FUZZERS += pix_rotate_shear_fuzzer
+FUZZERS += recog_basic_fuzzer
+
+fuzzers: $(FUZZERS)
+
+$(FUZZERS): all
+
+%_fuzzer: $(top_srcdir)/prog/fuzzing/%_fuzzer.cc
+ $(CXX) $(CXXFLAGS) -g $(LIB_FUZZING_ENGINE) \
+ -I $(top_srcdir)/src \
+ -I $(builddir)/src \
+ $< \
+ $(builddir)/src/.libs/liblept.a \
+ $(GIFLIB_LIBS) \
+ $(JPEG_LIBS) \
+ $(LIBJP2K_LIBS) \
+ $(LIBPNG_LIBS) \
+ $(LIBTIFF_LIBS) \
+ $(LIBWEBP_LIBS) \
+ -o $@
diff --git a/leptonica/README.html b/leptonica/README.html
new file mode 100644
index 00000000..9409ee41
--- /dev/null
+++ b/leptonica/README.html
@@ -0,0 +1,1233 @@
+<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 (version 1.80.0)
+File update: 6 Oct 2020
+---------------------------
+
+gunzip leptonica-1.80.0.tar.gz
+tar -xvf leptonica-1.80.0.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="#DOXYGEN">Generating documentation using doxygen</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.80.0'. 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
+
+ The software can be downloaded from either a release tar file or
+ from the current head of the source. For the latter, go to a directory
+ and clone the tree into it (note the '.' at the end):
+ cd [some directory]
+ git clone https://github.com/DanBloomberg/leptonica.git .
+
+ 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 details below.
+ Note: if you are going to develop with leptonica, the static
+ makefiles are useful.
+
+ (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 the root directory, do this:
+ mkdir build
+ cd build
+ Then to make only the library:
+ cmake ..
+ make
+ To make both the library and the programs:
+ cmake .. -DBUILD_PROG=1
+ make
+ To clean out the current build, just remove everything in
+ the build subdirectory.
+
+ In more detail for these three methods:
+
+ (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 image I/O functions that depend on external
+ libraries, such as libpng. Use environment variables in
+ src/environ.h, such as HAVE_LIBPNG.
+ (b) Disabling the GNU functions for redirecting formatted stream I/O
+ to memory. By default, HAVE_FMEMOPEN is enabled in src/environ.h.
+ (c) Using special memory allocators (see src/environ.h).
+ (d) Changing compile and runtime defaults for messages to stderr.
+ The default in src/environ.h is to output info, warning and
+ error messages.
+ (e) 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. For example,
+ after you have built programs with 'make SHARED=yes' in the
+ prog directory, you need to tell the programs where the shared
+ libraries are:
+ export LD_LIBRARY_PATH=../lib/shared:$LD_LIBRARY_PATH
+
+ * Make the programs in prog/ (after you have make liblept):
+ - Customize the makefile by setting ALL_LIBS to link the
+ external image I/O libraries. By default, ALL_LIBS assumes that
+ libtiff, libjpeg and libpng are available.
+ - To make an optimized version of all programs (in prog):
+ make
+ - To make a debug version of all programs (in prog):
+ make DEBUG=yes
+ - To make a shared library version of all programs (in prog):
+ make SHARED=yes
+ - To run the programs, be sure to set
+ export LD_LIBRARY_PATH=../lib/shared:$LD_LIBRARY_PATH
+
+ (2) Building using autoconf (Thanks to James Le Cuirot)
+
+ * If you downloaded from a release tar, it will be "configure ready".
+ * If you cloned from the git master tree, you need to make the
+ configure executable. To do this, run
+ autogen.sh.
+
+ 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 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 supports installing in a local directory (e.g., one that
+ doesn't require root access). For example, to install in $HOME/local,
+ ./configure --prefix=$HOME/local/
+ make install
+ For different ways to build and link leptonica with tesseract, see
+ https://github.com/tesseract-ocr/tesseract/wiki/Compiling
+ In brief, using autotools to build tesseract and then install it
+ in $HOME/local (after installing leptonica there), do the
+ following from your tesseract root source directory:
+ ./autogen.sh
+ LIBLEPT_HEADERSDIR=$HOME/local/include ./configure \
+ --prefix=$HOME/local/ --with-extra-libraries=$HOME/local/lib
+ make install
+
+ Configure also supports building in a separate directory from the
+ source. Run "/(path-to)/leptonica-1.80.0/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
+
+ The usual method is to build in a directory that is a subdirectory
+ of the root. First do this from the root directory:
+ mkdir build
+ cd build
+
+ The default build (shared libraries, no debug, only the library)
+ is made with
+ cmake ..
+ For other options, you can use these flags on the cmake line:
+ * To make a static library:
+ cmake .. -DBUILD_SHARED_LIBS=OFF
+ make
+ * To build with debug:
+ cmake .. -DCMAKE_BUILD_TYPE=Debug
+ make
+ * To make both the library and the programs:
+ cmake .. -DBUILD_PROG=1
+ make
+
+ The programs are put in build/bin/
+ To run these (e.g., for testing), move them to the prog
+ directory and run them from there:
+ cd bin
+ mv * ../../prog/
+ cd ../../prog
+ alltests_reg generate
+ alltests_reg compare
+
+ To build the library directly from the root directory instead of
+ the build subdirectory:
+ mkdir build
+ cmake -H . -Bbuild (-H means the source directory,
+ -B means the directory for the build
+ make
+
+3. Building on Windows
+
+ (a) Building with Visual Studio
+
+ 1. Download the latest SW
+ (Software Network https://software-network.org/)
+ client from https://software-network.org/client/
+ 2. Unpack it, add to PATH.
+ 3. Run once to perform cmake integration:
+ sw setup
+ 4. Run:
+ git clone https://github.com/danbloomberg/leptonica
+ cd leptonica
+ mkdir build
+ cd build
+ cmake ..
+ 5. Build a solution (leptonica.sin) in your Visual Studio version.
+
+ (b) Building for mingw32 with <a href="http://www.mingw.org/">MSYS</a>
+ (Thanks to David Bryan)
+
+ MSYS is a Unix-compatible build environment for the Windows-native
+ mingw32 compiler. Selecting the "mingw-developer-toolkit",
+ "mingw32-base", and "msys-base" packages during installation 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 used
+ in the make command line:
+
+ CC='gcc -std=c99 -U__STRICT_ANSI__'
+
+ 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 the <a
+ href="http://sourceforge.net/projects/ezwinports/">ezwinports</a> project.
+
+ (c) Building for <a href="http://www.cygwin.com/">Cygwin</a>
+ (Thanks to David Bryan)
+
+ Cygwin is a Unix-compatible build and runtime environment. Adding the
+ "binutils", "gcc-core", and "make" packages from the "Devel" category and
+ the "diffutils" package from the "Utils" category to the packages
+ installed by default will allow building the library with autoconf as in
+ (2) above. Pre-built external image libraries are available in the
+ "Graphics" and "Libs" categories and may be selected for installation.
+ If the libraries are not installed into the /lib, /usr/lib, or
+ /usr/local/lib directories, you must run make with the
+ "LDFLAGS=-L/(path-to-image)/lib" option. Building may also be performed
+ with the static makefile as in (1) above if this option is used in the
+ make command:
+
+ CC='gcc -std=c99 -U__STRICT_ANSI__'
+
+ Only the static library may be built this way; the autoconf method must
+ be used if a shared (DLL) library is desired.
+
+4. Building and running oss-fuzz programs
+
+ The oss-fuzz programs are in prog/fuzzing/. They are run by oss-fuzz
+ on a continual basis with random inputs. clang-10, which is required
+ to build these programs, can be installed using the command
+ sudo apt-get install clang-10
+
+ Stefan Weil has provided this method for building the fuzzing programs.
+ From your github root:
+ ./autogen.sh (to make configure)
+ mkdir -p bin/fuzzer
+ cd bin/fuzzer
+ Then run configure to generate the Makefiles:
+ ../../configure CC=clang-10 CXX=clang++-10 CFLAGS="-g -O2 \
+ -D_GLIBCXX_DEBUG -fsanitize=fuzzer-no-link,address,undefined" \
+ CXXFLAGS="-g -O2 -D_GLIBCXX_DEBUG \
+ -fsanitize=fuzzer-no-link,address,undefined"
+ And build:
+ make fuzzers CXX=clang++-10 CXXFLAGS="-g -O2 -D_GLIBCXX_DEBUG \
+ -fsanitize=fuzzer,address,undefined"
+
+ When an oss-fuzz issue has been created, download the Reproducer
+ Testcase file (e.g, name it "/tmp/payload"). To run one of the
+ fuzzing executables in bin/fuzzer, e.g., pix4_fuzzer:
+ cd ../../prog/fuzzing
+ ../../bin/fuzzer/pix4_fuzzer /tmp/payload
+
+5. The 270+ programs in the prog directory are an integral part of this
+ package. They 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, imagetops, printimage and printsplitimage.
+
+ (2) Programs that are used as regression tests in alltests_reg.
+ These are named *_reg, and 100 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.
+</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, libwebp, libgif, libopenjp2
+
+ These libraries are easy to obtain. For example, using the
+ Debian package manager:
+ sudo apt-get install <package>
+ where <package> = {libpng-dev, libjpeg62-turbo-dev, libtiff5-dev,
+ libwebp-dev, libopenjp2-7-dev, libgif-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.3, from their distribution.
+ (2) #define HAVE_LIBJP2K 1 (in environ.h)
+ (2a) If you have version 2.X, X != 3, 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.1.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.
+</pre>
+
+
+<h2> <A NAME="DOXYGEN">
+Generating documentation using doxygen
+</h2>
+
+<pre>
+The source code is set up to allow generation of documentation using doxygen.
+To do this:
+(1) Download the Debian doxygen package:
+ sudo apt-get install doxygen
+(2) In the root client directory containing Doxyfile:
+ doxygen Doxyfile
+The documentation will be generated in a 'doc' subdirectory, accessible
+from this file (relative to the root)
+ ./doc/html/index.html
+</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. Global parameter to enable development and testing
+
+ For security reasons, with the exception of the regression utility
+ (regutils.c), leptonica as shipped (starting with 1.77) does not allow:
+ * 'system(3)' fork/exec
+ * writes to temp files with compiled-in names
+ System calls are used either to run gnuplot or display an image on
+ the screen.
+
+ This is enforced with a global parameter, LeptDebugOK, initialized to 0.
+ It can be overridden either at compile time by changing the initialization
+ (in writefile.c), or at runtime, using setLeptDebugOK().
+ The programs in the prog directory, which mostly test functions in
+ the library, are not subject to this restriction.
+
+3. 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.
+
+4. Runtime functions not available on all platforms
+
+ Some functions are not available on all systems. One example of such a
+ function is fstatat(). If possible, such functions will be replaced by
+ wrappers, stubs or behavioral equivalent functions. By default, such
+ functions are disabled; enable them by setting #define HAVE_FUNC 1 (in
+ environ.h).
+
+ If you're building with the autoconf or cmake programs, these functions are
+ automatically enabled if available.
+
+5. 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).
+
+6. Compile and runtime control over stderr output (see environ.h and utils1.c)
+
+ 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 runtime 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.
+
+ Runtime redirection of stderr output is also possible, using a
+ callback mechanism. The callback function is registered using
+ leptSetStderrHandler(). See utils1.c for details.
+
+7. 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.
+
+8. 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.
+
+9. 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.
+ Some of the low-level code is in roplow.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.
+
+ 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 also called "antialiasing".
+ Very fast color area mapping rotation is provided.
+
+ 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
+
+ Some simple image enhancement routines for grayscale and color
+ images have been provided. These include intensity mapping with
+ gamma correction and contrast enhancement, histogram equalization,
+ edge sharpening, smoothing, and various color-shifting operations.
+
+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. See webpio.c.
+
+ - JP2 (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. See jp2kio.c.
+
+ - 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). Leptonica supports versions 5.1+. 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.
+ - JP2 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 JP2
+
+ 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 the PS compression level (1, 2, or 3).
+ * prog/imagetops.c for a special image --> PS conversion
+ for printing at maximum size on 8.5 x 11 paper. You can
+ specify the PS 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. The gnu C runtime library (glibc) supports
+ two I/O functions, open_memstream() and fmemopen(), which read
+ and write directly to memory as if writing to a file stream.
+ * On all platforms, leptonica supports direct read/write with memory
+ for TIFF, PNG, BMP, GIF and WEBP formats.
+ * On linux, leptonica uses the special gnu libraries to enable
+ direct read/write with memory for JPEG, PNM and JP2.
+ * On platforms without the gnu libraries, such as OSX, Windows
+ and Solaris, read/write with memory for JPEG, PNM and JP2 goes
+ through temp files.
+ 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, pixDisplay() and pixDisplayWithTitle() in writefile.c
+ 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. Another common debug method is to
+ accumulate intermediate images in a pixa, and then either view these
+ as a mosaic (using functions in pixafunc2.c), or gather them into
+ a compressed PDF or PostScript file for viewing with evince. Common
+ image display programs are: xzgv, xli, xv, display, gthumb, gqview,
+ evince, gv and acroread.
+
+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. Other data structures
+ are provided for handling ordered sets and maps, as well as hash sets
+ and hash maps.
+
+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 and generator, including a 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
+ following files:
+ src/makefile.static
+ CMakeLists.txt
+ configure.ac
+ allheaders_top.txt (and propagated to allheaders.h)
+ All even versions from 1.42 to 1.60 were originally archived at
+ http://code.google.com/p/leptonica, as well as all versions after 1.60.
+ These have now been transferred by Egor Pugin to github:
+ github.com/danbloomberg/leptonica
+ where all releases (1.42 - 1.80.0) are available; e.g.,
+ https://github.com/DanBloomberg/leptonica/releases/tag/1.80.0
+ The more recent releases, from 1.74, are also available at
+ leptonica.org/download.html
+ Note that if you are downloading from github, the releases are more
+ likely to be stable than the master. Also, if you download from
+ the master and use autotools (e.g., Makefile.am), you must first run
+ autogen.sh to generate the configure program and the Makefiles.
+
+ 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. Jeff Breidenbach has built all
+ the Debian releases, which require release version numbers.
+ The Debian binary release versions to date are:
+ 1.69 : 3.0.0
+ 1.70 : 4.0.0
+ 1.71 : 4.2.0
+ 1.72 : 4.3.0
+ 1.73 : 5.0.0
+ 1.74 : 5.1.0
+ 1.75 : 5.2.0
+ 1.76 : 5.3.0
+ 1.77 : 5.3.0
+ 1.78 : 5.3.0
+ 1.79 : 5.4.0
+ 1.80 : 5.4.0
+ 1.81 : 5.4.0 (not released as of 10/06/2020)
+
+ 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/leptonica/README.md b/leptonica/README.md
new file mode 100644
index 00000000..f9d9aebe
--- /dev/null
+++ b/leptonica/README.md
@@ -0,0 +1,82 @@
+# Leptonica Library #
+
+[![Build Status](https://travis-ci.org/DanBloomberg/leptonica.svg?branch=master)](https://travis-ci.org/DanBloomberg/leptonica)
+[![Build status](https://ci.appveyor.com/api/projects/status/vsk607rr6n4j2tmk?svg=true)](https://ci.appveyor.com/project/DanBloomberg/leptonica)
+![Build status](https://github.com/DanBloomberg/leptonica/workflows/sw/badge.svg)<br>
+[![Coverity Scan Build Status](https://scan.coverity.com/projects/leptonica/badge.svg)](https://scan.coverity.com/projects/leptonica)
+[![Code Quality: Cpp](https://img.shields.io/lgtm/grade/cpp/g/DanBloomberg/leptonica.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/DanBloomberg/leptonica/context:cpp)
+[![Total Alerts](https://img.shields.io/lgtm/alerts/g/DanBloomberg/leptonica.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/DanBloomberg/leptonica/alerts)
+
+www.leptonica.org
+
+## 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 bilinear transforms
+ * Binary and grayscale morphology, rank order filters, and convolution
+ * Seedfill 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.
+
+## Ancillary utilities ##
+
+ * I/O for standard image formats (_jpg_, _png_, _tiff_, _webp_, _jp2_, _bmp_, _pnm_, _gif_, _ps_, _pdf_)
+ * Utilities to handle arrays of image-related data types (e.g., _pixa_, _boxa_, _pta_)
+ * Utilities for stacks, generic arrays, queues, heaps, lists; number and string arrays; etc.
+
+## Examples of some applications enabled and implemented ##
+
+ * Octcube-based color quantization (w/ and w/out dithering)
+ * Modified median cut color quantization (w/ and w/out dithering)
+ * Skew determination of text images
+ * Adaptive normalization and binarization
+ * Segmentation of page images with mixed text and images
+ * Location of baselines and local skew determination
+ * jbig2 unsupervised classifier
+ * Border representations of 1 bpp images and raster conversion for SVG
+ * Postscript generation (levels 1, 2 and 3) of images for device-independent output
+ * PDF generation (G4, DCT, FLATE) of images for device-independent output
+ * Connectivity-preserving thinning and thickening of 1 bpp images
+ * Image warping (captcha, stereoscopic)
+ * Image dewarping based on content (textlines)
+ * Watershed transform
+ * Greedy splitting of components into rectangles
+ * Location of largest fg or bg rectangles in 1 bpp images
+ * Search for least-cost paths on binary and grayscale images
+ * Barcode reader for 1D barcodes (very early version as of 1.55)
+
+## Implementation characteristics ##
+
+ * _Efficient_: image data is packed binary (into 32-bit words); operations on 32-bit data whenever possible
+ * _Simple_: small number of data structures; simplest implementations provided that are efficient
+ * _Consistent_: data allocated on the heap with simple ownership rules; function names usually begin with primary data structure (e.g., _pix_); simple code patterns throughout
+ * _Robust_: all ptr args checked; extensive use of accessors; exit not permitted
+ * _Tested_: thorough regression tests provided for most basic functions; valgrind tested
+ * _ANSI C_: automatically generated prototype header file
+ * _Portable_: endian-independent; builds in Linux, macOS, MinGW, Cygwin, Windows
+ * _Nearly thread-safe_: ref counting on some structs
+ * _Documentation_: large number of in-line comments; doxygen; web pages for further background
+ * _Examples_: many programs provided to test and show usage of approx. 2600 functions in the library
+
+
+## Open Source Projects that use Leptonica ##
+ * [tesseract](https://github.com/tesseract-ocr/tesseract/) (optical character recognition)
+ * [OpenCV](https://github.com/opencv/opencv) (computer vision library)
+ * [jbig2enc](https://github.com/agl/jbig2enc) (encodes multipage binary image documents with jbig2 compression)
+
+## Major contributors to Leptonica ##
+ * Tom Powers: Tom has supported Leptonica on Windows for many years. He has made many contributions to code quality and documentation, including the beautiful "unofficial documentation" on the web site. Without his effort, Leptonica would not run today on Windows.
+ * David Bryan: David has worked for years to support Leptonica on multiple platforms. He designed many nice features in Leptonica, such as the severity-based error messaging system, and has identified and fixed countless bugs. And he has built and tested each distribution many times on cross-compilers.
+ * James Le Cuirot: James has written and supported the autotools scripts on Leptonica distributions for many years, and has helped test every distribution since 1.67.
+ * Jeff Breidenbach: Jeff has built every Debian distribution for Leptonica. He has also made many improvements to formatted image I/O, including tiff, png and pdf. He is a continuous advocate for simplification.
+ * Egor Pugin: Egor is co-maintainer of Leptonica on GitHub. He ported everything, including all the old distributions, from Google Code when it shut down. He set Leptonica up for appveyor and travis testing, and has implemented the sw project, which simplifies building executables on Windows.
+ * Jürgen Buchmüller: Jürgen wrote text converters to modify Leptonica source code so that it generates documentation using doxygen. He also wrote tiff wrappers for memory I/O.
+ * Stefan Weil: Stefan has worked from the beginning to clean up the Leptonica GitHub distribution, including removing errors in the source code. He also suggested and implemented the use of Coverity Scan.
+ * Zdenko Podobny: Zdenko has worked, mostly behind the scenes as a primary maintainer of tesseract, to help with leptonica builds on all platforms, and coordinate with its use in tesseract.
+
diff --git a/leptonica/appveyor.yml b/leptonica/appveyor.yml
new file mode 100644
index 00000000..2cc3b51b
--- /dev/null
+++ b/leptonica/appveyor.yml
@@ -0,0 +1,39 @@
+environment:
+ matrix:
+ # does not work with sw now
+ #- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+ #platform: Win32
+ #- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+ #platform: Win32
+ #- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+ #platform: Win64
+ - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
+ platform: Win64
+
+cache:
+ - c:/Users/appveyor/.sw -> appveyor.yml
+
+configuration:
+ - Release
+
+# for curl
+install:
+ - set PATH=C:\Program Files\Git\mingw64\bin;%PATH%
+
+before_build:
+ - curl -fsS -L -o sw.zip https://github.com/SoftwareNetwork/binaries/raw/master/sw-master-windows-client.zip
+ - 7z x sw.zip
+ - set PATH=%PATH%;%cd%
+
+build_script:
+ - sw -version
+ - sw -show-output -platform %platform% build
+
+after_build:
+ - 7z a leptonica.zip %APPVEYOR_BUILD_FOLDER%\.sw\out\**\*.exe %APPVEYOR_BUILD_FOLDER%\.sw\out\**\*.dll
+ #- 7z a leptonica.zip %APPVEYOR_BUILD_FOLDER%\.sw\Windows_*_Shared_Release_MSVC_*\*.exe %APPVEYOR_BUILD_FOLDER%\.sw\Windows_*_Shared_Release_MSVC_*\*.dll
+
+artifacts:
+ - path: leptonica.zip
+ name: leptonica-$(APPVEYOR_BUILD_VERSION)
+
diff --git a/leptonica/autogen.sh b/leptonica/autogen.sh
new file mode 100755
index 00000000..62cfe724
--- /dev/null
+++ b/leptonica/autogen.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# autogen.sh
+
+libtoolize -c -f || glibtoolize -c -f
+aclocal
+autoheader -f
+autoconf
+if grep -q PKG_CHECK_MODULES configure; then
+ # The generated configure is invalid because pkg-config is unavailable.
+ rm configure
+ echo "Error, missing pkg-config. Check the build requirements."
+ exit 1
+fi
+automake -a -c -f
diff --git a/leptonica/cmake/Configure.cmake b/leptonica/cmake/Configure.cmake
new file mode 100644
index 00000000..f8d79773
--- /dev/null
+++ b/leptonica/cmake/Configure.cmake
@@ -0,0 +1,164 @@
+################################################################################
+#
+# configure
+#
+################################################################################
+
+########################################
+# FUNCTION check_includes
+########################################
+function(check_includes files)
+ foreach(F ${${files}})
+ set(name ${F})
+ string(REPLACE "-" "_" name ${name})
+ string(REPLACE "." "_" name ${name})
+ string(REPLACE "/" "_" name ${name})
+ string(TOUPPER ${name} name)
+ check_include_files(${F} HAVE_${name})
+ file(APPEND ${AUTOCONFIG_SRC} "/* Define to 1 if you have the <${F}> header file. */\n")
+ file(APPEND ${AUTOCONFIG_SRC} "#cmakedefine HAVE_${name} 1\n")
+ file(APPEND ${AUTOCONFIG_SRC} "\n")
+ endforeach()
+endfunction(check_includes)
+
+########################################
+# FUNCTION check_functions
+########################################
+function(check_functions functions)
+ foreach(F ${${functions}})
+ set(name ${F})
+ string(TOUPPER ${name} name)
+ check_function_exists(${F} HAVE_${name})
+ file(APPEND ${AUTOCONFIG_SRC} "/* Define to 1 if you have the `${F}' function. */\n")
+ file(APPEND ${AUTOCONFIG_SRC} "#cmakedefine HAVE_${name} 1\n")
+ file(APPEND ${AUTOCONFIG_SRC} "\n")
+ endforeach()
+endfunction(check_functions)
+
+########################################
+
+file(WRITE ${AUTOCONFIG_SRC})
+
+include(CheckCSourceCompiles)
+include(CheckCSourceRuns)
+include(CheckCXXSourceCompiles)
+include(CheckCXXSourceRuns)
+include(CheckFunctionExists)
+include(CheckIncludeFiles)
+include(CheckLibraryExists)
+include(CheckPrototypeDefinition)
+include(CheckStructHasMember)
+include(CheckSymbolExists)
+include(CheckTypeSize)
+include(TestBigEndian)
+
+set(include_files_list
+ dlfcn.h
+ inttypes.h
+ memory.h
+ stdint.h
+ stdlib.h
+ strings.h
+ string.h
+ sys/stat.h
+ sys/types.h
+ unistd.h
+
+ openjpeg-2.0/openjpeg.h
+ openjpeg-2.1/openjpeg.h
+ openjpeg-2.2/openjpeg.h
+ openjpeg-2.3/openjpeg.h
+ openjpeg-2.4/openjpeg.h
+)
+check_includes(include_files_list)
+
+set(functions_list
+ fmemopen
+ fstatat
+)
+check_functions(functions_list)
+
+test_big_endian(BIG_ENDIAN)
+
+if(BIG_ENDIAN)
+ set(ENDIANNESS L_BIG_ENDIAN)
+else()
+ set(ENDIANNESS L_LITTLE_ENDIAN)
+endif()
+
+set(APPLE_UNIVERSAL_BUILD "defined (__APPLE_CC__)")
+configure_file(
+ ${PROJECT_SOURCE_DIR}/src/endianness.h.in
+ ${PROJECT_BINARY_DIR}/src/endianness.h
+ @ONLY)
+
+if (GIF_FOUND)
+ set(HAVE_LIBGIF 1)
+endif()
+
+if (JPEG_FOUND)
+ set(HAVE_LIBJPEG 1)
+endif()
+
+if (JP2K_FOUND)
+ set(HAVE_LIBJP2K 1)
+endif()
+
+if (PNG_FOUND)
+ set(HAVE_LIBPNG 1)
+endif()
+
+if (TIFF_FOUND)
+ set(HAVE_LIBTIFF 1)
+endif()
+
+if (WEBP_FOUND)
+ set(HAVE_LIBWEBP 1)
+ set(HAVE_LIBWEBP_ANIM 1)
+endif()
+
+if (ZLIB_FOUND)
+ set(HAVE_LIBZ 1)
+endif()
+
+file(APPEND ${AUTOCONFIG_SRC} "
+/* Define to 1 if you have giflib. */
+#cmakedefine HAVE_LIBGIF 1
+
+/* Define to 1 if you have libopenjp2. */
+#cmakedefine HAVE_LIBJP2K 1
+
+/* Define to 1 if you have jpeg. */
+#cmakedefine HAVE_LIBJPEG 1
+
+/* Define to 1 if you have libpng. */
+#cmakedefine HAVE_LIBPNG 1
+
+/* Define to 1 if you have libtiff. */
+#cmakedefine HAVE_LIBTIFF 1
+
+/* Define to 1 if you have libwebp. */
+#cmakedefine HAVE_LIBWEBP 1
+
+/* Define to 1 if you have libwebpmux. */
+#cmakedefine HAVE_LIBWEBP_ANIM 1
+
+/* Define to 1 if you have zlib. */
+#cmakedefine HAVE_LIBZ 1
+
+#if defined(HAVE_OPENJPEG_2_4_OPENJPEG_H)
+#define LIBJP2K_HEADER <openjpeg-2.4/openjpeg.h>
+#elif defined(HAVE_OPENJPEG_2_3_OPENJPEG_H)
+#define LIBJP2K_HEADER <openjpeg-2.3/openjpeg.h>
+#elif defined(HAVE_OPENJPEG_2_2_OPENJPEG_H)
+#define LIBJP2K_HEADER <openjpeg-2.2/openjpeg.h>
+#elif defined(HAVE_OPENJPEG_2_1_OPENJPEG_H)
+#define LIBJP2K_HEADER <openjpeg-2.1/openjpeg.h>
+#elif defined(HAVE_OPENJPEG_2_0_OPENJPEG_H)
+#define LIBJP2K_HEADER <openjpeg-2.0/openjpeg.h>
+#endif
+")
+
+########################################
+
+################################################################################
diff --git a/leptonica/cmake/templates/LeptonicaConfig-version.cmake.in b/leptonica/cmake/templates/LeptonicaConfig-version.cmake.in
new file mode 100644
index 00000000..bfc29385
--- /dev/null
+++ b/leptonica/cmake/templates/LeptonicaConfig-version.cmake.in
@@ -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/leptonica/cmake/templates/LeptonicaConfig.cmake.in b/leptonica/cmake/templates/LeptonicaConfig.cmake.in
new file mode 100644
index 00000000..342c37db
--- /dev/null
+++ b/leptonica/cmake/templates/LeptonicaConfig.cmake.in
@@ -0,0 +1,45 @@
+# ===================================================================================
+# 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@"
+# - Leptonica_VERSION_PATCH : Patch version part of Leptonica_VERSION: "@VERSION_PATCH@"
+#
+# ===================================================================================
+
+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@)
+SET(Leptonica_VERSION_PATCH @VERSION_PATCH@)
+
+# ======================================================
+# 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_NAME@)
diff --git a/leptonica/cmake/templates/cmake_uninstall.cmake.in b/leptonica/cmake/templates/cmake_uninstall.cmake.in
new file mode 100644
index 00000000..4137fcb8
--- /dev/null
+++ b/leptonica/cmake/templates/cmake_uninstall.cmake.in
@@ -0,0 +1,22 @@
+# https://gitlab.kitware.com/cmake/community/wikis/FAQ#can-i-do-make-uninstall-with-cmake
+if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
+ message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt")
+endif(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
+
+file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files)
+string(REGEX REPLACE "\n" ";" files "${files}")
+foreach(file ${files})
+ message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
+ if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
+ exec_program(
+ "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
+ OUTPUT_VARIABLE rm_out
+ RETURN_VALUE rm_retval
+ )
+ if(NOT "${rm_retval}" STREQUAL 0)
+ message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
+ endif(NOT "${rm_retval}" STREQUAL 0)
+ else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
+ message(STATUS "File $ENV{DESTDIR}${file} does not exist.")
+ endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
+endforeach(file)
diff --git a/leptonica/configure.ac b/leptonica/configure.ac
new file mode 100644
index 00000000..faf744d9
--- /dev/null
+++ b/leptonica/configure.ac
@@ -0,0 +1,276 @@
+AC_PREREQ([2.50])
+AC_INIT([leptonica], [1.81.0])
+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([libwebpmux], AS_HELP_STRING([--without-libwebpmux], [do not include libwebpmux 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], [
+ zlib_missing=untried
+], [
+ zlib_missing=
+
+ PKG_CHECK_MODULES([ZLIB], [zlib], [
+ AC_DEFINE([HAVE_LIBZ], 1, [Define to 1 if you have zlib.])
+ ], [
+ AC_CHECK_LIB([z], [deflate], [:], [zlib_missing=library])
+ AC_CHECK_HEADER([zlib.h], [:], [zlib_missing=header])
+
+ AS_IF([test "x$zlib_missing" = x], [
+ 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 $zlib_missing not found]))
+ ])
+ ])
+])
+
+AS_IF([test "x$with_libpng" = xno], [
+ libpng_missing=untried
+], [
+ libpng_missing=
+
+ PKG_CHECK_MODULES([LIBPNG], [libpng], [
+ AC_DEFINE([HAVE_LIBPNG], 1, [Define to 1 if you have libpng.])
+ ], [
+ AC_CHECK_LIB([png], [png_read_png], [:], [libpng_missing=library], [$LIBM $ZLIB_LIBS])
+ AC_CHECK_HEADER([png.h], [:], [libpng_missing=headers])
+
+ AS_IF([test "x$libpng_missing" = x], [
+ 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 $libpng_missing not found]))
+ ])
+ ])
+])
+
+AS_IF([test "x$with_jpeg" = xno], [
+ jpeg_missing=untried
+], [
+ jpeg_missing=
+
+ PKG_CHECK_MODULES([JPEG], [libjpeg], [
+ AC_DEFINE([HAVE_LIBJPEG], 1, [Define to 1 if you have jpeg.])
+ ], [
+ AC_CHECK_LIB([jpeg], [jpeg_read_scanlines], [:], [jpeg_missing=library])
+ AC_CHECK_HEADER([jpeglib.h], [:], [jpeg_missing=headers])
+
+ AS_IF([test "x$jpeg_missing" = x], [
+ 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 $jpeg_missing not found]))
+ ])
+ ])
+])
+
+AS_IF([test "x$with_giflib" = xno], [
+ giflib_missing=untried
+], [
+ giflib_missing=
+
+ AC_CHECK_LIB([gif], [DGifOpenFileHandle], [:], [giflib_missing=library])
+ AC_CHECK_HEADER([gif_lib.h], [:], [giflib_missing=header])
+
+ AS_IF([test "x$giflib_missing" = x], [
+ AC_MSG_CHECKING([giflib is at least version 5.1 (but not 5.1.2)])
+ AC_PREPROC_IFELSE([
+ AC_LANG_SOURCE([[
+#include <gif_lib.h>
+#if !defined GIFLIB_MAJOR || GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0)
+#error giflib version too old
+#elif GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 1 && GIFLIB_RELEASE == 2
+#error giflib 5.1.2 is broken
+#endif
+ ]])
+ ], [
+ AC_MSG_RESULT([yes])
+ AC_DEFINE([HAVE_LIBGIF], 1, [Define to 1 if you have giflib.]) AC_SUBST([GIFLIB_LIBS], [-lgif])
+ ], [
+ AC_MSG_RESULT([no])
+ AS_IF([test "x$with_giflib" = xyes], AC_MSG_ERROR([giflib version too old or broken]))
+ giflib_missing=old
+ ])
+ ], [
+ AS_IF([test "x$with_giflib" = xyes], AC_MSG_ERROR([giflib support requested but $giflib_missing not found]))
+ ])
+])
+
+AM_CONDITIONAL([HAVE_LIBGIF], [test "x$giflib_missing" = x])
+
+AS_IF([test "x$with_libtiff" = xno], [
+ libtiff_missing=untried
+], [
+ libtiff_missing=
+
+ PKG_CHECK_MODULES([LIBTIFF], [libtiff-4], [
+ AC_DEFINE([HAVE_LIBTIFF], 1, [Define to 1 if you have libtiff.])
+ ], [
+ AC_CHECK_LIB([tiff], [TIFFOpen], [:], [libtiff_missing=library], [$LIBM $ZLIB_LIBS $JPEG_LIBS])
+ AC_CHECK_HEADER([tiff.h], [:], [libtiff_missing=headers])
+
+ AS_IF([test "x$libtiff_missing" = x], [
+ 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 $libtiff_missing not found]))
+ ])
+ ])
+])
+
+AS_IF([test "x$with_libwebp" = xno], [
+ libwebp_missing=untried
+], [
+ libwebp_missing=
+
+ PKG_CHECK_MODULES([LIBWEBP], [libwebp], [
+ AC_DEFINE([HAVE_LIBWEBP], 1, [Define to 1 if you have libwebp.])
+ ], [
+ AC_CHECK_LIB([webp], [WebPGetInfo], [:], [libwebp_missing=library], [$LIBM])
+ AC_CHECK_HEADER([webp/encode.h], [:], [libwebp_missing=headers])
+
+ AS_IF([test "x$libwebp_missing" = x], [
+ 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 $libwebp_missing not found]))
+ ])
+ ])
+])
+
+AM_CONDITIONAL([HAVE_LIBWEBP], [test "x$libwebp_missing" = x])
+
+AS_IF([test "x$with_libwebpmux" = xno], [
+ libwebpmux_missing=untried
+], [
+ libwebpmux_missing=
+
+ PKG_CHECK_MODULES([LIBWEBPMUX], [libwebpmux >= 0.5.0], [
+ AC_DEFINE([HAVE_LIBWEBP_ANIM], 1, [Define to 1 if you have libwebpmux.])
+ ], [
+ AC_CHECK_LIB([webpmux], [WebPAnimEncoderOptionsInit], [:], [libwebpmux_missing=library], [$LIBM])
+
+ AS_IF([test "x$libwebpmux_missing" = x], [
+ AC_DEFINE([HAVE_LIBWEBP_ANIM], 1, [Define to 1 if you have libwebpmux.]) AC_SUBST([LIBWEBPMUX_LIBS], [-lwebpmux])
+ ], [
+ AS_IF([test "x$with_libwebpmux" = xyes], AC_MSG_ERROR([libwebpmux support requested but $libwebpmux_missing not found]))
+ ])
+ ])
+])
+
+AM_CONDITIONAL([HAVE_LIBWEBP_ANIM], [test "x$libwebpmux_missing" = x])
+
+AS_IF([test "x$with_libopenjpeg" = xno], [
+ libopenjpeg_missing=untried
+], [
+ libopenjpeg_missing=
+
+ PKG_CHECK_MODULES([LIBJP2K], [libopenjp2 >= 2.0.0], [
+ AC_DEFINE([HAVE_LIBJP2K], 1, [Define to 1 if you have libopenjp2.])
+ ], [
+ AC_CHECK_LIB([openjp2], [opj_create_decompress], [:], [libopenjpeg_missing=library])
+ AC_CHECK_HEADERS([openjpeg-2.3/openjpeg.h 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],
+ [libopenjpeg_missing=headers]
+ )
+
+ AS_IF([test "x$libopenjpeg_missing" = x], [
+ AC_DEFINE([HAVE_LIBJP2K], 1, [Define to 1 if you have libopenjp2.]) AC_SUBST([LIBJP2K_LIBS], [-lopenjp2])
+ ], [
+ AS_IF([test "x$with_libopenjpeg" = xyes], AC_MSG_ERROR([libopenjp2 support requested but $libopenjpeg_missing not found]))
+ ])
+ ])
+])
+
+AM_CONDITIONAL([HAVE_LIBJP2K], [test "x$libopenjpeg_missing" = x])
+
+# Check whether to enable debugging
+AC_MSG_CHECKING([whether to enable debugging])
+AC_ARG_ENABLE([debug],
+ AS_HELP_STRING([--enable-debug], [turn on debugging [default=no]]))
+AC_MSG_RESULT([$enable_debug])
+if test x"$enable_debug" = x"yes"; then
+ CFLAGS="-g -Wall -DDEBUG -pedantic -Og -g3"
+fi
+
+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_CHECK_FUNC([fstatat])
+
+# Configuration files
+AC_CONFIG_FILES([Makefile src/endianness.h src/Makefile prog/Makefile lept.pc])
+
+# cmake configuration. Get versions from PACKAGE_VERSION.
+AX_SPLIT_VERSION
+AC_SUBST([VERSION_PLAIN], ["${PACKAGE_VERSION}"])
+AC_SUBST([VERSION_MAJOR], [$(echo "$AX_MAJOR_VERSION" | $SED 's/^[[^0-9]]*//')])
+AC_SUBST([VERSION_MINOR], [$AX_MINOR_VERSION])
+AC_SUBST([VERSION_PATCH], [$(echo "$AX_POINT_VERSION" | $SED 's/^\([[0-9]][[0-9]]*\).*/\1/')])
+INCLUDE_PATH=`eval echo $includedir`
+AC_SUBST([INCLUDE_DIR], ["${INCLUDE_PATH}/leptonica"])
+AC_SUBST([leptonica_OUTPUT_NAME], ["lept"])
+AC_CONFIG_FILES(cmake/templates/LeptonicaConfig.cmake)
+AC_CONFIG_FILES(cmake/templates/LeptonicaConfig-version.cmake)
+
+# Create symlink
+AC_PROG_LN_S
+
+AC_OUTPUT
diff --git a/leptonica/lept.pc.cmake b/leptonica/lept.pc.cmake
new file mode 100644
index 00000000..e420a756
--- /dev/null
+++ b/leptonica/lept.pc.cmake
@@ -0,0 +1,11 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}/bin
+libdir=${prefix}/lib
+includedir=${prefix}/include
+
+Name: @leptonica_NAME@
+Description: An open source C library for efficient image processing and image analysis operations
+Version: @leptonica_VERSION@
+Libs: -L${libdir} -l@leptonica_OUTPUT_NAME@
+Libs.private:
+Cflags: -I${includedir} -I${includedir}/leptonica
diff --git a/leptonica/lept.pc.in b/leptonica/lept.pc.in
new file mode 100644
index 00000000..867b33be
--- /dev/null
+++ b/leptonica/lept.pc.in
@@ -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} -l@leptonica_OUTPUT_NAME@
+Libs.private: @ZLIB_LIBS@ @LIBPNG_LIBS@ @JPEG_LIBS@ @LIBJP2K_LIBS@ @GIFLIB_LIBS@ @LIBTIFF_LIBS@ @LIBWEBP_LIBS@ @LIBWEBPMUX_LIBS@
+Cflags: -I${includedir}
+
diff --git a/leptonica/leptonica-license.txt b/leptonica/leptonica-license.txt
new file mode 100644
index 00000000..73d44c60
--- /dev/null
+++ b/leptonica/leptonica-license.txt
@@ -0,0 +1,26 @@
+/*====================================================================*
+ - Copyright (C) 2001-2020 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/lok.lua b/leptonica/lok.lua
new file mode 100644
index 00000000..e700dccb
--- /dev/null
+++ b/leptonica/lok.lua
@@ -0,0 +1,157 @@
+--[[
+ Modify Leptonica's *.c file functions returning
+ "0 if OK, 1 on error" to use the l_ok typedef.
+--]]
+
+debug = false
+
+---
+-- copy a file src to dst
+-- \param src source filename
+-- \param dst destination filename
+-- \param blocksize block size for copying (default 1M)
+-- \return true on success; nil,error on error
+function copyfile(src, dst, blocksize)
+ blocksize = blocksize or 1024*1024
+ local fs, fd, err
+ -- return after closing the file descriptors
+ local function ret(...)
+ if fs then fs:close() end
+ if fd then fd:close() end
+ return ...
+ end
+ fs, err = io.open(src)
+ if not fs then return ret(nil, err) end
+ fd, err = io.open(dst, "wb")
+ if not fd then return ret(nil, err) end
+ while true do
+ local ok, data
+ data = fs:read(blocksize)
+ if not data then break end
+ ok, err = fd:write(data)
+ if not ok then return ret(nil, err) end
+ end
+ return ret(true)
+end
+
+---
+-- List the array table (t) contents to fd.
+-- \param fd io file descriptor
+-- \param t array table to list
+function list(fd, t)
+ if t ~= nil then
+ for _,l in ipairs(t) do
+ fd:write(l .. '\n')
+ end
+ end
+end
+
+---
+--- Modify the file fs and write the result to fd.
+-- \param fs source file stream
+-- \param fd destination file stream
+-- \return true on success
+function modify(fs, fd)
+ local state = 0 -- parsing state
+ local to_l_ok = false -- true, if the l_int32 return type should be changed
+ local b_file = false -- true, have seen a \file comment
+
+ while true do
+ line = fs:read()
+ if line == nil then
+ -- end of file
+ break
+ end
+
+ if line:match('^/%*[!*]$') then
+ -- start of Doxygen comment
+ -- introduces a new function
+ -- go to state 1 (inside doxygen comment)
+ state = 1
+ end
+
+ if state == 3 then
+ -- 2nd line after a comment
+ -- contains the name of the function
+ -- go to state 4 (skip until end of function)
+ state = 4
+ end
+
+ if state == 2 then
+ -- 1st line after a comment
+ -- contains the return type
+ if to_l_ok and line == 'l_int32' then
+ line = 'l_ok'
+ end
+ if b_file then
+ -- back to state 0 (look for doxygen comment)
+ state = 0
+ to_l_ok = false
+ b_file = false
+ else
+ -- go to state 3 (2nd line after doxygen comment)
+ state = 3
+ end
+ end
+
+ if line == ' */' then
+ -- end of Doxygen comment
+ -- go to state 2 (1st line after doxygen comment)
+ state = 2
+ end
+
+ if state == 1 then
+ -- inside doxygen comment
+ if line:match("%\\return%s+0 if OK") then
+ -- magic words that indicate l_ok return type
+ to_l_ok = true
+ end
+ if line:match("%\\file%s+") then
+ -- this is a file comment
+ b_file = true
+ end
+ end
+
+ if debug then
+ if to_l_ok then
+ print("[" .. state .. ";1] " .. line)
+ else
+ print("[" .. state .. ";0] " .. line)
+ end
+ end
+
+ if state == 4 and line == '}' then
+ -- end of a function
+ -- print("name='" .. name .. "'")
+ -- back to state 0 (look for doxygen comment)
+ state = 0
+ to_l_ok = false
+ b_file = false
+ end
+ fd:write(line .. '\n')
+ end
+ return true
+end
+
+---
+-- Main function.
+-- \param arg table array of command line parameters
+
+script = arg[0] or ""
+if #arg < 1 then
+ print("Usage: lua " .. script .." src/*.c")
+end
+
+for i = 1, #arg do
+ local input = arg[i]
+ local backup = input .. "~"
+ local ok, err = copyfile(input, backup)
+ if not ok then
+ print("Error copying " .. input .." to " .. backup .. ": " ..err)
+ end
+ local fs = io.open(backup)
+ local fd = io.open(input, "wb")
+ modify(fs, fd)
+ fd:close()
+ fs:close()
+end
diff --git a/leptonica/m4/ax_split_version.m4 b/leptonica/m4/ax_split_version.m4
new file mode 100644
index 00000000..2176eb0a
--- /dev/null
+++ b/leptonica/m4/ax_split_version.m4
@@ -0,0 +1,38 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_split_version.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_SPLIT_VERSION
+#
+# DESCRIPTION
+#
+# Splits a version number in the format MAJOR.MINOR.POINT into its
+# separate components.
+#
+# Sets the variables.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Tom Howard <tomhoward@users.sf.net>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 10
+
+AC_DEFUN([AX_SPLIT_VERSION],[
+ AC_REQUIRE([AC_PROG_SED])
+ AX_MAJOR_VERSION=`echo "$VERSION" | $SED 's/\([[^.]][[^.]]*\).*/\1/'`
+ AX_MINOR_VERSION=`echo "$VERSION" | $SED 's/[[^.]][[^.]]*.\([[^.]][[^.]]*\).*/\1/'`
+ AX_POINT_VERSION=`echo "$VERSION" | $SED 's/[[^.]][[^.]]*.[[^.]][[^.]]*.\(.*\)/\1/'`
+ AC_MSG_CHECKING([Major version])
+ AC_MSG_RESULT([$AX_MAJOR_VERSION])
+ AC_MSG_CHECKING([Minor version])
+ AC_MSG_RESULT([$AX_MINOR_VERSION])
+ AC_MSG_CHECKING([Point version])
+ AC_MSG_RESULT([$AX_POINT_VERSION])
+])
diff --git a/leptonica/make-for-auto b/leptonica/make-for-auto
new file mode 100755
index 00000000..b8ac88b1
--- /dev/null
+++ b/leptonica/make-for-auto
@@ -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/leptonica/make-for-local b/leptonica/make-for-local
new file mode 100755
index 00000000..e1968319
--- /dev/null
+++ b/leptonica/make-for-local
@@ -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/leptonica/moller52.jpg b/leptonica/moller52.jpg
new file mode 100644
index 00000000..49464dec
--- /dev/null
+++ b/leptonica/moller52.jpg
Binary files differ
diff --git a/leptonica/prog/1555.003.jpg b/leptonica/prog/1555.003.jpg
new file mode 100644
index 00000000..e35fa013
--- /dev/null
+++ b/leptonica/prog/1555.003.jpg
Binary files differ
diff --git a/leptonica/prog/1555.007.jpg b/leptonica/prog/1555.007.jpg
new file mode 100644
index 00000000..c00c18b8
--- /dev/null
+++ b/leptonica/prog/1555.007.jpg
Binary files differ
diff --git a/leptonica/prog/19-colors.png b/leptonica/prog/19-colors.png
new file mode 100644
index 00000000..5555d569
--- /dev/null
+++ b/leptonica/prog/19-colors.png
Binary files differ
diff --git a/leptonica/prog/CMakeLists.txt b/leptonica/prog/CMakeLists.txt
new file mode 100644
index 00000000..eb533816
--- /dev/null
+++ b/leptonica/prog/CMakeLists.txt
@@ -0,0 +1,322 @@
+########################################
+# 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 (BUILD_SHARED_LIBS)
+ target_compile_definitions (${target} PRIVATE -DLIBLEPT_IMPORTS)
+ endif()
+ target_link_libraries (${target} leptonica)
+ set_target_properties (${target} PROPERTIES FOLDER prog)
+endfunction(add_prog_target)
+########################################
+
+add_prog_target(adaptmap_reg adaptmap_reg.c)
+add_prog_target(adaptnorm_reg adaptnorm_reg.c)
+add_prog_target(affine_reg affine_reg.c)
+add_prog_target(alltests_reg alltests_reg.c)
+add_prog_target(alphaops_reg alphaops_reg.c)
+add_prog_target(alphaxform_reg alphaxform_reg.c)
+add_prog_target(baseline_reg baseline_reg.c)
+add_prog_target(bilateral1_reg bilateral1_reg.c)
+add_prog_target(bilateral2_reg bilateral2_reg.c)
+add_prog_target(bilinear_reg bilinear_reg.c)
+add_prog_target(binarize_reg binarize_reg.c)
+add_prog_target(binmorph1_reg binmorph1_reg.c)
+add_prog_target(binmorph2_reg binmorph2_reg.c)
+add_prog_target(binmorph3_reg binmorph3_reg.c)
+add_prog_target(binmorph4_reg binmorph4_reg.c)
+add_prog_target(binmorph5_reg binmorph5_reg.c)
+add_prog_target(binmorph6_reg binmorph6_reg.c)
+add_prog_target(blackwhite_reg blackwhite_reg.c)
+add_prog_target(blend1_reg blend1_reg.c)
+add_prog_target(blend2_reg blend2_reg.c)
+add_prog_target(blend3_reg blend3_reg.c)
+add_prog_target(blend4_reg blend4_reg.c)
+add_prog_target(blend5_reg blend5_reg.c)
+add_prog_target(boxa1_reg boxa1_reg.c)
+add_prog_target(boxa2_reg boxa2_reg.c)
+add_prog_target(boxa3_reg boxa3_reg.c)
+add_prog_target(boxa4_reg boxa4_reg.c)
+add_prog_target(bytea_reg bytea_reg.c)
+add_prog_target(ccbord_reg ccbord_reg.c)
+add_prog_target(ccthin1_reg ccthin1_reg.c)
+add_prog_target(ccthin2_reg ccthin2_reg.c)
+add_prog_target(checkerboard_reg checkerboard_reg.c)
+add_prog_target(circle_reg circle_reg.c)
+add_prog_target(cmapquant_reg cmapquant_reg.c)
+add_prog_target(colorcontent_reg colorcontent_reg.c)
+add_prog_target(colorfill_reg colorfill_reg.c)
+add_prog_target(coloring_reg coloring_reg.c)
+add_prog_target(colorize_reg colorize_reg.c)
+add_prog_target(colormask_reg colormask_reg.c)
+add_prog_target(colormorph_reg colormorph_reg.c)
+add_prog_target(colorquant_reg colorquant_reg.c)
+add_prog_target(colorseg_reg colorseg_reg.c)
+add_prog_target(colorspace_reg colorspace_reg.c)
+add_prog_target(compare_reg compare_reg.c)
+add_prog_target(compfilter_reg compfilter_reg.c)
+add_prog_target(conncomp_reg conncomp_reg.c)
+add_prog_target(conversion_reg conversion_reg.c)
+add_prog_target(convolve_reg convolve_reg.c)
+add_prog_target(crop_reg crop_reg.c)
+add_prog_target(dewarp_reg dewarp_reg.c)
+add_prog_target(distance_reg distance_reg.c)
+add_prog_target(dither_reg dither_reg.c)
+add_prog_target(dna_reg dna_reg.c)
+add_prog_target(dwamorph1_reg dwamorph1_reg.c dwalinear.3.c dwalinearlow.3.c)
+add_prog_target(dwamorph2_reg dwamorph2_reg.c dwalinear.3.c dwalinearlow.3.c)
+add_prog_target(edge_reg edge_reg.c)
+add_prog_target(encoding_reg encoding_reg.c)
+add_prog_target(enhance_reg enhance_reg.c)
+add_prog_target(equal_reg equal_reg.c)
+add_prog_target(expand_reg expand_reg.c)
+add_prog_target(extrema_reg extrema_reg.c)
+add_prog_target(falsecolor_reg falsecolor_reg.c)
+add_prog_target(fhmtauto_reg fhmtauto_reg.c)
+add_prog_target(files_reg files_reg.c)
+add_prog_target(findcorners_reg findcorners_reg.c)
+add_prog_target(findpattern_reg findpattern_reg.c)
+add_prog_target(flipdetect_reg flipdetect_reg.c)
+add_prog_target(fmorphauto_reg fmorphauto_reg.c)
+add_prog_target(fpix1_reg fpix1_reg.c)
+add_prog_target(fpix2_reg fpix2_reg.c)
+add_prog_target(genfonts_reg genfonts_reg.c)
+add_prog_target(gifio_reg gifio_reg.c)
+add_prog_target(grayfill_reg grayfill_reg.c)
+add_prog_target(graymorph1_reg graymorph1_reg.c)
+add_prog_target(graymorph2_reg graymorph2_reg.c)
+add_prog_target(grayquant_reg grayquant_reg.c)
+add_prog_target(hardlight_reg hardlight_reg.c)
+add_prog_target(hash_reg hash_reg.c)
+add_prog_target(heap_reg heap_reg.c)
+add_prog_target(insert_reg insert_reg.c)
+add_prog_target(ioformats_reg ioformats_reg.c)
+add_prog_target(iomisc_reg iomisc_reg.c)
+add_prog_target(italic_reg italic_reg.c)
+add_prog_target(jbclass_reg jbclass_reg.c)
+add_prog_target(jp2kio_reg jp2kio_reg.c)
+add_prog_target(jpegio_reg jpegio_reg.c)
+add_prog_target(kernel_reg kernel_reg.c)
+add_prog_target(label_reg label_reg.c)
+add_prog_target(lineremoval_reg lineremoval_reg.c)
+add_prog_target(locminmax_reg locminmax_reg.c)
+add_prog_target(logicops_reg logicops_reg.c)
+add_prog_target(lowaccess_reg lowaccess_reg.c)
+add_prog_target(lowsat_reg lowsat_reg.c)
+add_prog_target(maze_reg maze_reg.c)
+add_prog_target(mtiff_reg mtiff_reg.c)
+add_prog_target(multitype_reg multitype_reg.c)
+add_prog_target(nearline_reg nearline_reg.c)
+add_prog_target(newspaper_reg newspaper_reg.c)
+add_prog_target(numa1_reg numa1_reg.c)
+add_prog_target(numa2_reg numa2_reg.c)
+add_prog_target(numa3_reg numa3_reg.c)
+add_prog_target(overlap_reg overlap_reg.c)
+add_prog_target(pageseg_reg pageseg_reg.c)
+add_prog_target(paintmask_reg paintmask_reg.c)
+add_prog_target(paint_reg paint_reg.c)
+add_prog_target(pdfio1_reg pdfio1_reg.c)
+add_prog_target(pdfio2_reg pdfio2_reg.c)
+add_prog_target(pdfseg_reg pdfseg_reg.c)
+add_prog_target(pixa1_reg pixa1_reg.c)
+add_prog_target(pixa2_reg pixa2_reg.c)
+add_prog_target(pixadisp_reg pixadisp_reg.c)
+add_prog_target(pixalloc_reg pixalloc_reg.c)
+add_prog_target(pixcomp_reg pixcomp_reg.c)
+add_prog_target(pixmem_reg pixmem_reg.c)
+add_prog_target(pixserial_reg pixserial_reg.c)
+add_prog_target(pixtile_reg pixtile_reg.c)
+add_prog_target(pngio_reg pngio_reg.c)
+add_prog_target(pnmio_reg pnmio_reg.c)
+add_prog_target(projection_reg projection_reg.c)
+add_prog_target(projective_reg projective_reg.c)
+add_prog_target(psioseg_reg psioseg_reg.c)
+add_prog_target(psio_reg psio_reg.c)
+add_prog_target(pta_reg pta_reg.c)
+add_prog_target(ptra1_reg ptra1_reg.c)
+add_prog_target(ptra2_reg ptra2_reg.c)
+add_prog_target(quadtree_reg quadtree_reg.c)
+add_prog_target(rankbin_reg rankbin_reg.c)
+add_prog_target(rankhisto_reg rankhisto_reg.c)
+add_prog_target(rank_reg rank_reg.c)
+add_prog_target(rasteropip_reg rasteropip_reg.c)
+add_prog_target(rasterop_reg rasterop_reg.c)
+add_prog_target(rectangle_reg rectangle_reg.c)
+add_prog_target(rotate1_reg rotate1_reg.c)
+add_prog_target(rotate2_reg rotate2_reg.c)
+add_prog_target(scale_reg scale_reg.c)
+add_prog_target(selio_reg selio_reg.c)
+add_prog_target(shear1_reg shear1_reg.c)
+add_prog_target(shear2_reg shear2_reg.c)
+add_prog_target(skew_reg skew_reg.c)
+add_prog_target(smallpix_reg smallpix_reg.c)
+add_prog_target(smoothedge_reg smoothedge_reg.c)
+add_prog_target(speckle_reg speckle_reg.c)
+add_prog_target(splitcomp_reg splitcomp_reg.c)
+add_prog_target(string_reg string_reg.c)
+add_prog_target(subpixel_reg subpixel_reg.c)
+add_prog_target(texturefill_reg texturefill_reg.c)
+add_prog_target(threshnorm_reg threshnorm_reg.c)
+add_prog_target(translate_reg translate_reg.c)
+add_prog_target(warper_reg warper_reg.c)
+add_prog_target(watershed_reg watershed_reg.c)
+add_prog_target(webpio_reg webpio_reg.c)
+add_prog_target(webpanimio_reg webpanimio_reg.c)
+add_prog_target(wordboxes_reg wordboxes_reg.c)
+add_prog_target(writetext_reg writetext_reg.c)
+add_prog_target(xformbox_reg xformbox_reg.c)
+add_prog_target(adaptmap_dark adaptmap_dark.c)
+add_prog_target(arabic_lines arabic_lines.c)
+add_prog_target(arithtest arithtest.c)
+add_prog_target(autogentest1 autogentest1.c)
+add_prog_target(autogentest2 autogentest2.c autogen.137.c)
+add_prog_target(barcodetest barcodetest.c)
+add_prog_target(binarize_set binarize_set.c)
+add_prog_target(binarizefiles binarizefiles.c)
+add_prog_target(bincompare bincompare.c)
+add_prog_target(blendcmaptest blendcmaptest.c)
+add_prog_target(buffertest buffertest.c)
+add_prog_target(ccbordtest ccbordtest.c)
+add_prog_target(cctest1 cctest1.c)
+add_prog_target(cleanpdf cleanpdf.c)
+add_prog_target(colorsegtest colorsegtest.c)
+add_prog_target(comparepages comparepages.c)
+add_prog_target(comparepixa comparepixa.c)
+add_prog_target(comparetest comparetest.c)
+add_prog_target(concatpdf concatpdf.c)
+add_prog_target(contrasttest contrasttest.c)
+add_prog_target(convertfilestopdf convertfilestopdf.c)
+add_prog_target(convertfilestops convertfilestops.c)
+add_prog_target(convertformat convertformat.c)
+add_prog_target(convertsegfilestopdf convertsegfilestopdf.c)
+add_prog_target(convertsegfilestops convertsegfilestops.c)
+add_prog_target(converttogray converttogray.c)
+add_prog_target(converttopdf converttopdf.c)
+add_prog_target(converttops converttops.c)
+add_prog_target(cornertest cornertest.c)
+add_prog_target(corrupttest corrupttest.c)
+add_prog_target(croptext croptext.c)
+add_prog_target(deskew_it deskew_it.c)
+add_prog_target(dewarprules dewarprules.c)
+add_prog_target(dewarptest1 dewarptest1.c)
+add_prog_target(dewarptest2 dewarptest2.c)
+add_prog_target(dewarptest3 dewarptest3.c)
+add_prog_target(dewarptest4 dewarptest4.c)
+add_prog_target(dewarptest5 dewarptest5.c)
+add_prog_target(digitprep1 digitprep1.c)
+add_prog_target(displayboxa displayboxa.c)
+add_prog_target(displayboxes_on_pixa displayboxes_on_pixa.c)
+add_prog_target(displaypix displaypix.c)
+add_prog_target(displaypixa displaypixa.c)
+add_prog_target(dwalineargen dwalineargen.c)
+add_prog_target(fcombautogen fcombautogen.c)
+add_prog_target(fhmtautogen fhmtautogen.c)
+add_prog_target(fileinfo fileinfo.c)
+add_prog_target(findbinding findbinding.c)
+add_prog_target(find_colorregions find_colorregions.c)
+add_prog_target(findpattern1 findpattern1.c)
+add_prog_target(findpattern2 findpattern2.c)
+add_prog_target(findpattern3 findpattern3.c)
+add_prog_target(fmorphautogen fmorphautogen.c)
+add_prog_target(fpixcontours fpixcontours.c)
+add_prog_target(gammatest gammatest.c)
+add_prog_target(graphicstest graphicstest.c)
+add_prog_target(graymorphtest graymorphtest.c)
+add_prog_target(histotest histotest.c)
+add_prog_target(histoduptest histoduptest.c)
+add_prog_target(htmlviewer htmlviewer.c)
+add_prog_target(imagetops imagetops.c)
+add_prog_target(jbcorrelation jbcorrelation.c)
+add_prog_target(jbrankhaus jbrankhaus.c)
+add_prog_target(jbwords jbwords.c)
+add_prog_target(lightcolortest lightcolortest.c)
+add_prog_target(listtest listtest.c)
+add_prog_target(livre_adapt livre_adapt.c)
+add_prog_target(livre_hmt livre_hmt.c)
+add_prog_target(livre_makefigs livre_makefigs.c)
+add_prog_target(livre_orient livre_orient.c)
+add_prog_target(livre_pageseg livre_pageseg.c)
+add_prog_target(livre_seedgen livre_seedgen.c)
+add_prog_target(livre_tophat livre_tophat.c)
+add_prog_target(maketile maketile.c)
+add_prog_target(maptest maptest.c)
+add_prog_target(messagetest messagetest.c)
+add_prog_target(misctest1 misctest1.c)
+add_prog_target(modifyhuesat modifyhuesat.c)
+add_prog_target(morphseq_reg morphseq_reg.c)
+add_prog_target(morphtest1 morphtest1.c)
+add_prog_target(numaranktest numaranktest.c)
+add_prog_target(otsutest1 otsutest1.c)
+add_prog_target(otsutest2 otsutest2.c)
+add_prog_target(pagesegtest1 pagesegtest1.c)
+add_prog_target(pagesegtest2 pagesegtest2.c)
+add_prog_target(partifytest partifytest.c)
+add_prog_target(partitiontest partitiontest.c)
+add_prog_target(percolatetest percolatetest.c)
+add_prog_target(pixaatest pixaatest.c)
+add_prog_target(pixafileinfo pixafileinfo.c)
+add_prog_target(plottest plottest.c)
+add_prog_target(printimage printimage.c)
+add_prog_target(printsplitimage printsplitimage.c)
+add_prog_target(printtiff printtiff.c)
+add_prog_target(rasteroptest rasteroptest.c)
+add_prog_target(rbtreetest rbtreetest.c)
+add_prog_target(recog_bootnum1 recog_bootnum1.c)
+add_prog_target(recog_bootnum2 recog_bootnum2.c)
+add_prog_target(recog_bootnum3 recog_bootnum3.c)
+add_prog_target(recogsort recogsort.c)
+add_prog_target(recogtest1 recogtest1.c)
+add_prog_target(recogtest2 recogtest2.c)
+add_prog_target(recogtest3 recogtest3.c)
+add_prog_target(recogtest4 recogtest4.c)
+add_prog_target(recogtest5 recogtest5.c)
+add_prog_target(recogtest6 recogtest6.c)
+add_prog_target(recogtest7 recogtest7.c)
+add_prog_target(reducetest reducetest.c)
+add_prog_target(removecmap removecmap.c)
+add_prog_target(renderfonts renderfonts.c)
+add_prog_target(replacebytes replacebytes.c)
+add_prog_target(rotatefastalt rotatefastalt.c)
+add_prog_target(rotate_it rotate_it.c)
+add_prog_target(rotateorthtest1 rotateorthtest1.c)
+add_prog_target(rotateorth_reg rotateorth_reg.c)
+add_prog_target(rotatetest1 rotatetest1.c)
+add_prog_target(runlengthtest runlengthtest.c)
+add_prog_target(scaleandtile scaleandtile.c)
+add_prog_target(scale_it scale_it.c)
+add_prog_target(scaletest1 scaletest1.c)
+add_prog_target(scaletest2 scaletest2.c)
+add_prog_target(seedfilltest seedfilltest.c)
+add_prog_target(seedspread_reg seedspread_reg.c)
+add_prog_target(settest settest.c)
+add_prog_target(sharptest sharptest.c)
+add_prog_target(sheartest sheartest.c)
+add_prog_target(showedges showedges.c)
+add_prog_target(skewtest skewtest.c)
+add_prog_target(sorttest sorttest.c)
+add_prog_target(splitimage2pdf splitimage2pdf.c)
+add_prog_target(sudokutest sudokutest.c)
+add_prog_target(textorient textorient.c)
+add_prog_target(tiffpdftest tiffpdftest.c)
+add_prog_target(trctest trctest.c)
+add_prog_target(underlinetest underlinetest.c)
+add_prog_target(warpertest warpertest.c)
+add_prog_target(wordsinorder wordsinorder.c)
+add_prog_target(writemtiff writemtiff.c)
+add_prog_target(xtractprotos xtractprotos.c)
+add_prog_target(yuvtest yuvtest.c)
+
+set (INSTALL_PROGS
+ convertfilestopdf convertfilestops
+ convertformat convertsegfilestopdf convertsegfilestops
+ converttopdf converttops fileinfo xtractprotos
+)
+
+foreach(make_install ${INSTALL_PROGS})
+ install(TARGETS ${make_install} RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
+endforeach()
diff --git a/leptonica/prog/Leptonica.jpg b/leptonica/prog/Leptonica.jpg
new file mode 100644
index 00000000..676f46f1
--- /dev/null
+++ b/leptonica/prog/Leptonica.jpg
Binary files differ
diff --git a/leptonica/prog/Makefile.am b/leptonica/prog/Makefile.am
new file mode 100644
index 00000000..ecdf3d2f
--- /dev/null
+++ b/leptonica/prog/Makefile.am
@@ -0,0 +1,138 @@
+AUTOMAKE_OPTIONS = parallel-tests
+AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_builddir)/src
+LDADD = $(top_builddir)/src/liblept.la $(LIBM)
+
+INSTALL_PROGS = convertfilestopdf convertfilestops \
+ convertformat convertsegfilestopdf convertsegfilestops \
+ converttopdf converttops fileinfo imagetops xtractprotos
+
+AUTO_REG_PROGS = adaptmap_reg adaptnorm_reg affine_reg alphaops_reg \
+ alphaxform_reg baseline_reg bilateral2_reg \
+ bilinear_reg binarize_reg \
+ binmorph1_reg binmorph3_reg binmorph6_reg blackwhite_reg \
+ blend1_reg blend2_reg blend3_reg blend4_reg blend5_reg \
+ boxa1_reg boxa2_reg boxa3_reg boxa4_reg bytea_reg \
+ ccbord_reg ccthin1_reg ccthin2_reg \
+ checkerboard_reg circle_reg cmapquant_reg \
+ colorcontent_reg colorfill_reg \
+ coloring_reg colorize_reg \
+ colormask_reg colormorph_reg colorquant_reg \
+ colorseg_reg colorspace_reg compare_reg \
+ compfilter_reg conncomp_reg conversion_reg \
+ convolve_reg crop_reg dewarp_reg distance_reg \
+ dither_reg dna_reg dwamorph1_reg edge_reg encoding_reg \
+ enhance_reg equal_reg expand_reg extrema_reg \
+ falsecolor_reg fhmtauto_reg \
+ findcorners_reg findpattern_reg flipdetect_reg \
+ fpix1_reg fpix2_reg genfonts_reg \
+ grayfill_reg graymorph1_reg graymorph2_reg \
+ grayquant_reg hardlight_reg hash_reg heap_reg \
+ insert_reg ioformats_reg iomisc_reg italic_reg \
+ jbclass_reg jpegio_reg \
+ kernel_reg label_reg lineremoval_reg \
+ locminmax_reg logicops_reg lowaccess_reg lowsat_reg \
+ maze_reg mtiff_reg multitype_reg \
+ nearline_reg newspaper_reg numa1_reg numa2_reg numa3_reg \
+ overlap_reg pageseg_reg paint_reg paintmask_reg \
+ pdfio1_reg pdfio2_reg pdfseg_reg \
+ pixa1_reg pixa2_reg pixadisp_reg pixcomp_reg \
+ pixmem_reg pixserial_reg pngio_reg pnmio_reg \
+ projection_reg projective_reg \
+ psio_reg psioseg_reg pta_reg \
+ ptra1_reg ptra2_reg \
+ quadtree_reg rankbin_reg rankhisto_reg \
+ rank_reg rasteropip_reg rasterop_reg rectangle_reg \
+ rotate1_reg rotate2_reg rotateorth_reg \
+ scale_reg seedspread_reg selio_reg \
+ shear1_reg shear2_reg skew_reg \
+ smallpix_reg speckle_reg splitcomp_reg \
+ string_reg subpixel_reg \
+ texturefill_reg threshnorm_reg \
+ translate_reg warper_reg \
+ watershed_reg wordboxes_reg \
+ writetext_reg xformbox_reg
+
+if HAVE_LIBGIF
+AUTO_REG_PROGS += gifio_reg
+endif
+
+if HAVE_LIBWEBP
+AUTO_REG_PROGS += webpio_reg
+endif
+
+if HAVE_LIBWEBP_ANIM
+AUTO_REG_PROGS += webpanimio_reg
+endif
+
+if HAVE_LIBJP2K
+AUTO_REG_PROGS += jp2kio_reg
+endif
+
+MANUAL_REG_PROGS = alltests_reg bilateral1_reg \
+ binmorph2_reg binmorph4_reg binmorph5_reg \
+ dwamorph2_reg \
+ files_reg fmorphauto_reg \
+ morphseq_reg pixalloc_reg pixtile_reg \
+ smoothedge_reg
+
+OTHER_PROGS = adaptmap_dark \
+ arabic_lines arithtest \
+ autogentest1 autogentest2 barcodetest \
+ binarizefiles binarize_set bincompare \
+ blendcmaptest buffertest \
+ ccbordtest cctest1 cleanpdf \
+ colorsegtest comparepages comparepixa \
+ comparetest concatpdf \
+ contrasttest converttogray \
+ cornertest corrupttest croptext deskew_it \
+ dewarprules dewarptest1 dewarptest2 \
+ dewarptest3 dewarptest4 dewarptest5 \
+ digitprep1 displayboxa displayboxes_on_pixa \
+ displaypix displaypixa dwalineargen \
+ fcombautogen fhmtautogen findbinding find_colorregions \
+ findpattern1 findpattern2 findpattern3 \
+ fmorphautogen fpixcontours \
+ gammatest graphicstest graymorphtest \
+ histotest histoduptest htmlviewer \
+ jbcorrelation jbrankhaus jbwords \
+ listtest lightcolortest livre_adapt livre_hmt \
+ livre_makefigs livre_orient \
+ livre_pageseg livre_seedgen livre_tophat \
+ maketile maptest messagetest misctest1 \
+ modifyhuesat morphtest1 \
+ numaranktest otsutest1 otsutest2 \
+ pagesegtest1 pagesegtest2 \
+ partifytest partitiontest percolatetest \
+ pixaatest pixafileinfo plottest \
+ printimage printsplitimage printtiff \
+ rasteroptest rbtreetest \
+ recog_bootnum1 recog_bootnum2 recog_bootnum3 \
+ recogsort recogtest1 recogtest2 recogtest3 \
+ recogtest4 recogtest5 recogtest6 recogtest7 \
+ reducetest removecmap \
+ renderfonts replacebytes \
+ rotatefastalt rotate_it \
+ rotateorthtest1 rotatetest1 \
+ runlengthtest scaleandtile \
+ scale_it scaletest1 scaletest2 \
+ seedfilltest settest sharptest \
+ sheartest showedges skewtest \
+ sorttest splitimage2pdf sudokutest \
+ textorient tiffpdftest trctest \
+ underlinetest warpertest wordsinorder \
+ writemtiff yuvtest
+
+if ENABLE_PROGRAMS
+bin_PROGRAMS = $(INSTALL_PROGS)
+noinst_PROGRAMS = $(AUTO_REG_PROGS) $(MANUAL_REG_PROGS) $(OTHER_PROGS)
+endif
+
+check_PROGRAMS = $(AUTO_REG_PROGS)
+TESTS = $(AUTO_REG_PROGS)
+TESTS_ENVIRONMENT = $(srcdir)/reg_wrapper.sh
+EXTRA_DIST = reg_wrapper.sh
+
+dwamorph1_reg_SOURCES = dwamorph1_reg.c dwalinear.3.c dwalinearlow.3.c
+dwamorph2_reg_SOURCES = dwamorph2_reg.c dwalinear.3.c dwalinearlow.3.c
+
+autogentest2_SOURCES = autogentest2.c autogen.137.c
diff --git a/leptonica/prog/adaptmap_dark.c b/leptonica/prog/adaptmap_dark.c
new file mode 100644
index 00000000..d8c22133
--- /dev/null
+++ b/leptonica/prog/adaptmap_dark.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * adaptmap_dark.c
+ *
+ * Demonstrates the effect of the fg threshold on adaptive mapping
+ * and cleaning for images with dark and variable background.
+ *
+ * The example pages are text and image. For both, because the
+ * background is both dark and variable, using a lower threshold
+ * gives much better results.
+ *
+ * For text, cleaning the background to white after adaptively
+ * remapping to make the background uniform is preferable.
+ * The final cleaning step uses pixGammaTRC() where the white value
+ * (here, 180) is set below the remapped gray value (here, 200).
+ *
+ * For the image, however, it is best to stop after remapping
+ * the background. Going further and moving pixels near the
+ * background color to white removes the details in the lighter
+ * regions of the image. In practice, parts of a scanned page
+ * that are image (as opposed to text) don't necessarily have
+ * background pixels that should be white. These regions can be
+ * protected by masks from operations, such as pixGammaTRC(),
+ * where the white value is less than 255.
+ *
+ * This also tests some code useful for rendering:
+ * * NUp display from pixa to pixa
+ * * Interleaving of both pixa and pixacomp
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+void GenCleans(const char *fname, l_int32 *pindex, l_int32 thresh, L_BMF *bmf);
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 index;
+L_BMF *bmf;
+PIXA *pixa1, *pixa2, *pixa3, *pixa4;
+PIXAC *pixac1, *pixac2, *pixac3;
+
+ PROCNAME("adaptmap_dark");
+
+ setLeptDebugOK(1);
+ bmf = bmfCreate(NULL, 10);
+ index = 0;
+ lept_mkdir("lept/adapt");
+
+ /* Using a variety of different thresholds */
+ GenCleans("cavalerie.29.jpg", &index, 80, bmf);
+ GenCleans("cavalerie.29.jpg", &index, 60, bmf);
+ GenCleans("cavalerie.29.jpg", &index, 40, bmf);
+ GenCleans("cavalerie.11.jpg", &index, 80, bmf);
+ GenCleans("cavalerie.11.jpg", &index, 60, bmf);
+ GenCleans("cavalerie.11.jpg", &index, 40, bmf);
+
+ /* Read the images and convert to a 4-up pixa */
+ pixa1 = convertToNUpPixa("/tmp/lept/adapt", "adapt_", 2, 2, 500,
+ 6, 2, 0);
+
+ /* Convert to pdf */
+ L_INFO("Writing to /tmp/lept/adapt/cleaning.pdf\n", procName);
+ pixaConvertToPdf(pixa1, 100, 1.0, L_JPEG_ENCODE,
+ 75, "Adaptive cleaning",
+ "/tmp/lept/adapt/cleaning.pdf");
+ pixaDestroy(&pixa1);
+
+ /* Test the pixac interleaving. Make two copies,
+ * and interleave them:
+ * (1) convert NUp 2 x 1
+ * (2) convert twice to pixac
+ * (3) interleave the two copies
+ * (4) convert back to pixa
+ * (5) convert NUp 1 x 2 (result now is 2 x 2)
+ * (6) output as pdf */
+ pixa1 = convertToNUpPixa("/tmp/lept/adapt", "adapt_", 2, 1, 500,
+ 6, 2, 0);
+ startTimer();
+ pixac1 = pixacompCreateFromPixa(pixa1, IFF_DEFAULT, L_CLONE);
+ pixac2 = pixacompCreateFromPixa(pixa1, IFF_DEFAULT, L_CLONE);
+ pixac3 = pixacompInterleave(pixac1, pixac2);
+ pixa2 = pixaCreateFromPixacomp(pixac3, L_CLONE);
+ pixa3 = pixaConvertToNUpPixa(pixa2, NULL, 1, 2, 1000, 6, 2, 0);
+ lept_stderr("Time with pixac interleaving = %7.3f sec\n", stopTimer());
+ L_INFO("Writing to /tmp/lept/adapt/cleaning2.pdf\n", procName);
+ pixaConvertToPdf(pixa3, 100, 1.0, L_JPEG_ENCODE,
+ 75, "Adaptive cleaning", "/tmp/lept/adapt/cleaning2.pdf");
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ pixacompDestroy(&pixac1);
+ pixacompDestroy(&pixac2);
+ pixacompDestroy(&pixac3);
+
+ /* Test the pixa interleaving. Make two copies,
+ * and interleave them:
+ * (1) convert NUp 2 x 1
+ * (2) copy and interleave
+ * (3) convert NUp 1 x 2 (result now is 2 x 2)
+ * (4) output as pdf */
+ pixa1 = convertToNUpPixa("/tmp/lept/adapt", "adapt_", 2, 1, 500,
+ 6, 2, 0);
+ startTimer();
+ pixa2 = pixaCopy(pixa1, L_COPY_CLONE);
+ pixa3 = pixaInterleave(pixa1, pixa2, L_CLONE);
+ pixa4 = pixaConvertToNUpPixa(pixa3, NULL, 1, 2, 1000, 6, 2, 0);
+ lept_stderr("Time with pixa interleaving = %7.3f sec\n", stopTimer());
+ L_INFO("Writing to /tmp/lept/adapt/cleaning3.pdf\n", procName);
+ pixaConvertToPdf(pixa4, 100, 1.0, L_JPEG_ENCODE,
+ 75, "Adaptive cleaning", "/tmp/lept/adapt/cleaning3.pdf");
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ pixaDestroy(&pixa4);
+ bmfDestroy(&bmf);
+ return 0;
+}
+
+void
+GenCleans(const char *fname,
+ l_int32 *pindex,
+ l_int32 thresh,
+ L_BMF *bmf)
+{
+l_int32 index, blackval, whiteval;
+char buf[256];
+PIX *pix1, *pix2, *pix3, *pix4, *pix5;
+
+ blackval = 70;
+ whiteval = 180;
+ index = *pindex;
+ pix1 = pixRead(fname);
+ snprintf(buf, sizeof(buf), "/tmp/lept/adapt/adapt_%03d.jpg", index++);
+ pixWrite(buf, pix1, IFF_JFIF_JPEG);
+
+ pix2 = pixBackgroundNorm(pix1, NULL, NULL, 10, 15, thresh, 25, 200, 2, 1);
+ snprintf(buf, sizeof(buf), "Norm color: fg thresh = %d", thresh);
+ lept_stderr("%s\n", buf);
+ pix3 = pixAddTextlines(pix2, bmf, buf, 0x00ff0000, L_ADD_BELOW);
+ snprintf(buf, sizeof(buf), "/tmp/lept/adapt/adapt_%03d.jpg", index++);
+ pixWrite(buf, pix3, IFF_JFIF_JPEG);
+ pixDestroy(&pix3);
+ pix3 = pixGammaTRC(NULL, pix2, 1.0, blackval, whiteval);
+ snprintf(buf, sizeof(buf), "Clean color: fg thresh = %d", thresh);
+ pix4 = pixAddSingleTextblock(pix3, bmf, buf, 0x00ff0000, L_ADD_BELOW, NULL);
+ snprintf(buf, sizeof(buf), "/tmp/lept/adapt/adapt_%03d.jpg", index++);
+ pixWrite(buf, pix4, IFF_JFIF_JPEG);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ pix2 = pixConvertRGBToGray(pix1, 0.33, 0.34, 0.33);
+ pix3 = pixBackgroundNorm(pix2, NULL, NULL, 10, 15, thresh, 25, 200, 2, 1);
+ pix4 = pixGammaTRC(NULL, pix3, 1.0, blackval, whiteval);
+ snprintf(buf, sizeof(buf), "Clean gray: fg thresh = %d", thresh);
+ pix5 = pixAddSingleTextblock(pix4, bmf, buf, 0x00ff0000, L_ADD_BELOW, NULL);
+ snprintf(buf, sizeof(buf), "/tmp/lept/adapt/adapt_%03d.jpg", index++);
+ pixWrite(buf, pix5, IFF_JFIF_JPEG);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+
+ pixDestroy(&pix1);
+ *pindex = index;
+ return;
+}
diff --git a/leptonica/prog/adaptmap_reg.c b/leptonica/prog/adaptmap_reg.c
new file mode 100644
index 00000000..ea8f18de
--- /dev/null
+++ b/leptonica/prog/adaptmap_reg.c
@@ -0,0 +1,208 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Regression test demonstrating adaptive mappings in both gray and color
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Location of image region in wet-day.jpg */
+static const l_int32 XS = 151;
+static const l_int32 YS = 225;
+static const l_int32 WS = 913;
+static const l_int32 HS = 1285;
+
+static const l_int32 SIZE_X = 10;
+static const l_int32 SIZE_Y = 30;
+static const l_int32 BINTHRESH = 50;
+static const l_int32 MINCOUNT = 30;
+
+static const l_int32 BGVAL = 200;
+static const l_int32 SMOOTH_X = 2;
+static const l_int32 SMOOTH_Y = 1;
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w, h;
+PIX *pixs, *pixg, *pixim, *pixgm, *pixmi, *pix1, *pix2;
+PIX *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/adapt"); // REMOVE?
+
+ pixs = pixRead("wet-day.jpg");
+ pixa = pixaCreate(0);
+ pixg = pixConvertRGBToGray(pixs, 0.33, 0.34, 0.33);
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixaAddPix(pixa, pixg, L_INSERT);
+ pixGetDimensions(pixs, &w, &h, NULL);
+
+ /* Process in grayscale */
+ startTimer();
+ pixim = pixCreate(w, h, 1);
+ pixRasterop(pixim, XS, YS, WS, HS, PIX_SET, NULL, 0, 0);
+ pixGetBackgroundGrayMap(pixg, pixim, SIZE_X, SIZE_Y,
+ BINTHRESH, MINCOUNT, &pixgm);
+ lept_stderr("Time for gray adaptmap gen: %7.3f\n", stopTimer());
+ regTestWritePixAndCheck(rp, pixgm, IFF_PNG); /* 0 */
+ pixaAddPix(pixa, pixgm, L_INSERT);
+
+ startTimer();
+ pixmi = pixGetInvBackgroundMap(pixgm, BGVAL, SMOOTH_X, SMOOTH_Y);
+ lept_stderr("Time for gray inv map generation: %7.3f\n", stopTimer());
+ regTestWritePixAndCheck(rp, pixmi, IFF_PNG); /* 1 */
+ pixaAddPix(pixa, pixmi, L_INSERT);
+
+ startTimer();
+ pix1 = pixApplyInvBackgroundGrayMap(pixg, pixmi, SIZE_X, SIZE_Y);
+ lept_stderr("Time to apply gray inv map: %7.3f\n", stopTimer());
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 2 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ pix2 = pixGammaTRCMasked(NULL, pix1, pixim, 1.0, 0, 190);
+ pixInvert(pixim, pixim);
+ pixGammaTRCMasked(pix2, pix2, pixim, 1.0, 60, 190);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 3 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pixim);
+
+ /* Process in color */
+ startTimer();
+ pixim = pixCreate(w, h, 1);
+ pixRasterop(pixim, XS, YS, WS, HS, PIX_SET, NULL, 0, 0);
+ pixGetBackgroundRGBMap(pixs, pixim, NULL, SIZE_X, SIZE_Y,
+ BINTHRESH, MINCOUNT,
+ &pixmr, &pixmg, &pixmb);
+ lept_stderr("Time for color adaptmap gen: %7.3f\n", stopTimer());
+ regTestWritePixAndCheck(rp, pixmr, IFF_PNG); /* 4 */
+ regTestWritePixAndCheck(rp, pixmg, IFF_PNG); /* 5 */
+ regTestWritePixAndCheck(rp, pixmb, IFF_PNG); /* 6 */
+ pixaAddPix(pixa, pixmr, L_INSERT);
+ pixaAddPix(pixa, pixmg, L_INSERT);
+ pixaAddPix(pixa, pixmb, L_INSERT);
+
+ startTimer();
+ pixmri = pixGetInvBackgroundMap(pixmr, BGVAL, SMOOTH_X, SMOOTH_Y);
+ pixmgi = pixGetInvBackgroundMap(pixmg, BGVAL, SMOOTH_X, SMOOTH_Y);
+ pixmbi = pixGetInvBackgroundMap(pixmb, BGVAL, SMOOTH_X, SMOOTH_Y);
+ lept_stderr("Time for color inv map generation: %7.3f\n", stopTimer());
+ regTestWritePixAndCheck(rp, pixmri, IFF_PNG); /* 7 */
+ regTestWritePixAndCheck(rp, pixmgi, IFF_PNG); /* 8 */
+ regTestWritePixAndCheck(rp, pixmbi, IFF_PNG); /* 9 */
+ pixaAddPix(pixa, pixmri, L_INSERT);
+ pixaAddPix(pixa, pixmgi, L_INSERT);
+ pixaAddPix(pixa, pixmbi, L_INSERT);
+
+ startTimer();
+ pix1 = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi,
+ SIZE_X, SIZE_Y);
+ lept_stderr("Time to apply color inv maps: %7.3f\n", stopTimer());
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 10 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ pix2 = pixGammaTRCMasked(NULL, pix1, pixim, 1.0, 0, 190);
+ pixInvert(pixim, pixim);
+ pixGammaTRCMasked(pix2, pix2, pixim, 1.0, 60, 190);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 11 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pixim);
+
+ /* Process at higher level in color */
+ startTimer();
+ pixim = pixCreate(w, h, 1);
+ pixRasterop(pixim, XS, YS, WS, HS, PIX_SET, NULL, 0, 0);
+ pix1 = pixBackgroundNorm(pixs, pixim, NULL, 5, 10, BINTHRESH, 20,
+ BGVAL, SMOOTH_X, SMOOTH_Y);
+ lept_stderr("Time for bg normalization: %7.3f\n", stopTimer());
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 12 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ pix2 = pixGammaTRCMasked(NULL, pix1, pixim, 1.0, 0, 190);
+ pixInvert(pixim, pixim);
+ pixGammaTRCMasked(pix2, pix2, pixim, 1.0, 60, 190);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 13 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pixim);
+
+ /* Display results */
+ pix1 = pixaDisplayTiledAndScaled(pixa, 32, 400, 4, 0, 20, 2);
+ pixWrite("/tmp/lept/adapt/results.jpg", pix1, IFF_JFIF_JPEG);
+ pixDisplayWithTitle(pix1, 50, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ /* Check pixFillMapHoles() */
+ pixa = pixaCreate(3);
+ pix1 = pixRead("weasel8.png"); /* use this as the map */
+ pixGammaTRC(pix1, pix1, 1.0, 0, 270); /* darken white pixels */
+ pixaAddPix(pixa, pix1, L_COPY);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ pixRasterop(pix1, 0, 0, 5, h, PIX_SET, NULL, 0, 0); /* add white holes */
+ pixRasterop(pix1, 20, 0, 2, h, PIX_SET, NULL, 0, 0);
+ pixRasterop(pix1, 40, 0, 3, h, PIX_SET, NULL, 0, 0);
+ pixRasterop(pix1, 0, 0, w, 3, PIX_SET, NULL, 0, 0);
+ pixRasterop(pix1, 0, 15, w, 3, PIX_SET, NULL, 0, 0);
+ pixRasterop(pix1, 0, 35, w, 2, PIX_SET, NULL, 0, 0);
+ pixaAddPix(pixa, pix1, L_COPY);
+ pixFillMapHoles(pix1, w, h, L_FILL_WHITE);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixaDisplayTiledInColumns(pixa, 3, 1.0, 20, 1);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 14 */
+ pixDisplayWithTitle(pix2, 50, 850, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix2);
+
+ /* An even simpler check of pixFillMapHoles() */
+ pixa = pixaCreate(2);
+ pix1 = pixCreate(3, 3, 8);
+ pixSetPixel(pix1, 1, 0, 128);
+ pix2 = pixExpandReplicate(pix1, 25);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixFillMapHoles(pix1, 3, 3, L_FILL_BLACK);
+ pix2 = pixExpandReplicate(pix1, 25);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ pix1 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 15 */
+ pixDisplayWithTitle(pix1, 50, 1000, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/adaptnorm_reg.c b/leptonica/prog/adaptnorm_reg.c
new file mode 100644
index 00000000..842330d3
--- /dev/null
+++ b/leptonica/prog/adaptnorm_reg.c
@@ -0,0 +1,154 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * adaptnorm_reg.c
+ *
+ * Image normalization for two extreme cases:
+ * * variable and low contrast
+ * * good contrast but fast varying background
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w, h;
+l_float32 mps;
+PIX *pixs, *pixmin, *pix1, *pix2, *pix3, *pix4, *pix5;
+PIX *pix6, *pix7, *pix8, *pix9, *pix10, *pix11;
+PIXA *pixa1;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* ---------------------------------------------------------- *
+ * Normalize by adaptively expanding the dynamic range *
+ * ---------------------------------------------------------- */
+ pixa1 = pixaCreate(0);
+ pixs = pixRead("lighttext.jpg");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 0 */
+ pixaAddPix(pixa1, pixs, L_INSERT);
+ startTimer();
+ pix1 = pixContrastNorm(NULL, pixs, 10, 10, 40, 2, 2);
+ mps = 0.000001 * w * h / stopTimer();
+ lept_stderr("Time: Contrast norm: %7.3f Mpix/sec\n", mps);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 1 */
+
+ /* Apply a gamma to clean up the remaining background */
+ pix2 = pixGammaTRC(NULL, pix1, 1.5, 50, 235);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 2 */
+
+ /* Here are two possible output display images; a dithered
+ * 2 bpp image and a 7 level thresholded 4 bpp image */
+ pix3 = pixDitherTo2bpp(pix2, 1);
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3 */
+ pix4 = pixThresholdTo4bpp(pix2, 7, 1);
+ pixaAddPix(pixa1, pix4, L_INSERT);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 4 */
+
+ /* Binary image produced from 8 bpp normalized ones,
+ * before and after the gamma correction. */
+ pix5 = pixThresholdToBinary(pix1, 180);
+ pixaAddPix(pixa1, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 5 */
+ pix6 = pixThresholdToBinary(pix2, 200);
+ pixaAddPix(pixa1, pix6, L_INSERT);
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 6 */
+
+ pix7 = pixaDisplayTiledInColumns(pixa1, 3, 1.0, 30, 2);
+ pixDisplayWithTitle(pix7, 0, 0, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix7, IFF_JFIF_JPEG); /* 7 */
+ pixDestroy(&pix7);
+ pixaDestroy(&pixa1);
+
+ /* ---------------------------------------------------------- *
+ * Normalize for rapidly varying background *
+ * ---------------------------------------------------------- */
+ pixa1 = pixaCreate(0);
+ pixs = pixRead("w91frag.jpg");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixaAddPix(pixa1, pixs, L_INSERT);
+ regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 8 */
+ startTimer();
+ pix1 = pixBackgroundNormFlex(pixs, 7, 7, 1, 1, 10);
+ mps = 0.000001 * w * h / stopTimer();
+ lept_stderr("Time: Flexible bg norm: %7.3f Mpix/sec\n", mps);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 9 */
+
+ /* Now do it again in several steps */
+ pix2 = pixScaleSmooth(pixs, 1./7., 1./7.);
+ pix3 = pixScale(pix2, 7.0, 7.0);
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 10 */
+ pixLocalExtrema(pix2, 0, 0, &pixmin, NULL); /* 1's at minima */
+ pix4 = pixExpandBinaryReplicate(pixmin, 7, 7);
+ pixaAddPix(pixa1, pix4, L_INSERT);
+ regTestWritePixAndCheck(rp, pix4, IFF_JFIF_JPEG); /* 11 */
+ pix5 = pixSeedfillGrayBasin(pixmin, pix2, 10, 4);
+ pix6 = pixExtendByReplication(pix5, 1, 1);
+ regTestWritePixAndCheck(rp, pix6, IFF_JFIF_JPEG); /* 12 */
+ pixDestroy(&pixmin);
+ pixDestroy(&pix5);
+ pixDestroy(&pix2);
+ pix7 = pixGetInvBackgroundMap(pix6, 200, 1, 1); /* smoothing incl. */
+ pix8 = pixApplyInvBackgroundGrayMap(pixs, pix7, 7, 7);
+ pixaAddPix(pixa1, pix8, L_INSERT);
+ regTestWritePixAndCheck(rp, pix8, IFF_JFIF_JPEG); /* 13 */
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+
+ /* Process the result for gray and binary output */
+ pix9 = pixGammaTRCMasked(NULL, pix1, NULL, 1.0, 100, 175);
+ pixaAddPix(pixa1, pix9, L_INSERT);
+ regTestWritePixAndCheck(rp, pix9, IFF_JFIF_JPEG); /* 14 */
+ pix10 = pixThresholdTo4bpp(pix9, 10, 1);
+ pixaAddPix(pixa1, pix10, L_INSERT);
+ regTestWritePixAndCheck(rp, pix10, IFF_JFIF_JPEG); /* 15 */
+ pix11 = pixThresholdToBinary(pix9, 190);
+ pixaAddPix(pixa1, pix11, L_INSERT);
+ regTestWritePixAndCheck(rp, pix11, IFF_JFIF_JPEG); /* 16 */
+
+ pix2 = pixaDisplayTiledInColumns(pixa1, 3, 1.0, 30, 2);
+ pixDisplayWithTitle(pix2, 0, 700, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 17 */
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa1);
+ return regTestCleanup(rp);
+}
+
+
diff --git a/leptonica/prog/affine_reg.c b/leptonica/prog/affine_reg.c
new file mode 100644
index 00000000..a789d1c9
--- /dev/null
+++ b/leptonica/prog/affine_reg.c
@@ -0,0 +1,425 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Tests affine transforms, including invertability and large distortions.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static void MakePtas(l_int32 i, PTA **pptas, PTA **pptad);
+static l_int32 RenderHashedBoxa(PIX *pixt, BOXA *boxa, l_int32 i);
+
+
+ /* Sample values.
+ * 1-3: invertability tests
+ * 4: comparison between sampling and sequential
+ * 5: test with large distortion
+ */
+static const l_int32 x1[] = { 300, 300, 300, 95, 32};
+static const l_int32 y1[] = {1200, 1200, 1250, 2821, 934};
+static const l_int32 x2[] = {1200, 1200, 1125, 1432, 487};
+static const l_int32 y2[] = {1100, 1100, 1100, 2682, 934};
+static const l_int32 x3[] = { 200, 200, 200, 232, 32};
+static const l_int32 y3[] = { 200, 200, 200, 657, 67};
+
+static const l_int32 xp1[] = { 500, 300, 350, 117, 32};
+static const l_int32 yp1[] = {1700, 1400, 1400, 2629, 934};
+static const l_int32 xp2[] = {850, 1400, 1400, 1464, 487};
+static const l_int32 yp2[] = {850, 1500, 1500, 2432, 804};
+static const l_int32 xp3[] = { 450, 200, 400, 183, 61};
+static const l_int32 yp3[] = { 300, 300, 400, 490, 83};
+
+static const l_int32 SHIFTX = 44;
+static const l_int32 SHIFTY = 39;
+static const l_float32 SCALEX = 0.83;
+static const l_float32 SCALEY = 0.78;
+static const l_float32 ROTATION = 0.11; /* radian */
+
+#define ADDED_BORDER_PIXELS 1000
+#define ALL 1
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, w, h;
+l_float32 *mat1, *mat2, *mat3, *mat1i, *mat2i, *mat3i, *matdinv;
+l_float32 matd[9], matdi[9];
+BOXA *boxa, *boxa2;
+PIX *pix, *pixs, *pixb, *pixg, *pixc, *pixcs;
+PIX *pixd, *pix1, *pix2, *pix3;
+PIXA *pixa;
+PTA *ptas, *ptad;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pix = pixRead("feyn.tif");
+ pixs = pixScale(pix, 0.22, 0.22);
+ pixDestroy(&pix);
+
+#if ALL
+ /* Test invertability of sequential. */
+ lept_stderr("Test invertability of sequential\n");
+ pixa = pixaCreate(0);
+ for (i = 0; i < 3; i++) {
+ pixb = pixAddBorder(pixs, ADDED_BORDER_PIXELS, 0);
+ MakePtas(i, &ptas, &ptad);
+ pix1 = pixAffineSequential(pixb, ptad, ptas, 0, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0,3,6 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixAffineSequential(pix1, ptas, ptad, 0, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1,4,7 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixd = pixRemoveBorder(pix2, ADDED_BORDER_PIXELS);
+ pixXor(pixd, pixd, pixs);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 2,5,8 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixb);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ }
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 1.0, 20, 3);
+ pix2 = pixScaleToGray(pix1, 0.2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 9 */
+ pixDisplayWithTitle(pix2, 0, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa);
+#endif
+
+#if ALL
+ /* Test invertability of sampling */
+ lept_stderr("Test invertability of sampling\n");
+ pixa = pixaCreate(0);
+ for (i = 0; i < 3; i++) {
+ pixb = pixAddBorder(pixs, ADDED_BORDER_PIXELS, 0);
+ MakePtas(i, &ptas, &ptad);
+ pix1 = pixAffineSampledPta(pixb, ptad, ptas, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10,13,16 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixAffineSampledPta(pix1, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 11,14,17 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixd = pixRemoveBorder(pix2, ADDED_BORDER_PIXELS);
+ pixXor(pixd, pixd, pixs);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 12,15,18 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixb);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ }
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 1.0, 20, 3);
+ pix2 = pixScaleToGray(pix1, 0.2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 19 */
+ pixDisplayWithTitle(pix2, 200, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixs);
+ pixaDestroy(&pixa);
+#endif
+
+#if ALL
+ /* Test invertability of interpolation on grayscale */
+ lept_stderr("Test invertability of grayscale interpolation\n");
+ pix = pixRead("feyn.tif");
+ pixg = pixScaleToGray3(pix);
+ pixDestroy(&pix);
+ pixa = pixaCreate(0);
+ for (i = 0; i < 3; i++) {
+ pixb = pixAddBorder(pixg, ADDED_BORDER_PIXELS / 3, 255);
+ MakePtas(i, &ptas, &ptad);
+ pix1 = pixAffinePta(pixb, ptad, ptas, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 20,23,26 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixAffinePta(pix1, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 21,24,27 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixd = pixRemoveBorder(pix2, ADDED_BORDER_PIXELS / 3);
+ pixXor(pixd, pixd, pixg);
+ pixInvert(pixd, pixd);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 22,25,28 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixb);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ }
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 1.0, 20, 3);
+ pix2 = pixScale(pix1, 0.2, 0.2);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 29 */
+ pixDisplayWithTitle(pix2, 400, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixg);
+ pixaDestroy(&pixa);
+#endif
+
+#if ALL
+ /* Test invertability of interpolation on color */
+ lept_stderr("Test invertability of color interpolation\n");
+ pixa = pixaCreate(0);
+ pixc = pixRead("test24.jpg");
+ pixcs = pixScale(pixc, 0.3, 0.3);
+ for (i = 0; i < 3; i++) {
+ pixb = pixAddBorder(pixcs, ADDED_BORDER_PIXELS / 4, 0xffffff00);
+ MakePtas(i, &ptas, &ptad);
+ pix1 = pixAffinePta(pixb, ptad, ptas, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 30,33,36 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixAffinePta(pix1, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 31,34,37 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixd = pixRemoveBorder(pix2, ADDED_BORDER_PIXELS / 4);
+ pixXor(pixd, pixd, pixcs);
+ pixInvert(pixd, pixd);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 32,35,38 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixb);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ }
+ pixDestroy(&pixcs);
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 1.0, 20, 3);
+ pix2 = pixScale(pix1, 0.25, 0.25);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 39 */
+ pixDisplayWithTitle(pix2, 600, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixc);
+ pixaDestroy(&pixa);
+#endif
+
+#if ALL
+ /* Comparison between sequential and sampling */
+ lept_stderr("Compare sequential with sampling\n");
+ pix = pixRead("feyn.tif");
+ pixs = pixScale(pix, 0.22, 0.22);
+ pixDestroy(&pix);
+
+ MakePtas(3, &ptas, &ptad);
+ pixa = pixaCreate(0);
+
+ /* Use sequential transforms */
+ pix1 = pixAffineSequential(pixs, ptas, ptad,
+ ADDED_BORDER_PIXELS, ADDED_BORDER_PIXELS);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 40 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Use sampled transform */
+ pix2 = pixAffineSampledPta(pixs, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 41 */
+ pixaAddPix(pixa, pix2, L_COPY);
+
+ /* Compare the results */
+ pixXor(pix2, pix2, pix1);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 42 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 1.0, 20, 3);
+ pix2 = pixScale(pix1, 0.5, 0.5);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 43 */
+ pixDisplayWithTitle(pix2, 800, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixs);
+ pixaDestroy(&pixa);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+#endif
+
+
+#if ALL
+ /* Test with large distortion */
+ lept_stderr("Test with large distortion\n");
+ MakePtas(4, &ptas, &ptad);
+ pixa = pixaCreate(0);
+ pix = pixRead("feyn.tif");
+ pixg = pixScaleToGray6(pix);
+ pixDestroy(&pix);
+
+ pix1 = pixAffineSequential(pixg, ptas, ptad, 0, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 44 */
+ pixaAddPix(pixa, pix1, L_COPY);
+
+ pix2 = pixAffineSampledPta(pixg, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 45 */
+ pixaAddPix(pixa, pix2, L_COPY);
+
+ pix3 = pixAffinePta(pixg, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 46 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+
+ pixXor(pix1, pix1, pix2);
+ pixInvert(pix1, pix1);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 47 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixXor(pix2, pix2, pix3);
+ pixInvert(pix2, pix2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 48 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 5, 1.0, 20, 3);
+ pix2 = pixScale(pix1, 0.8, 0.8);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 49 */
+ pixDisplayWithTitle(pix2, 1000, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixg);
+ pixaDestroy(&pixa);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+#endif
+
+#if ALL
+ /* Set up pix and boxa */
+ lept_stderr("Test affine transforms and inverses on pix and boxa\n");
+ pixa = pixaCreate(0);
+ pix = pixRead("lucasta.1.300.tif");
+ pixTranslate(pix, pix, 70, 0, L_BRING_IN_WHITE);
+ pix1 = pixCloseBrick(NULL, pix, 14, 5);
+ pixOpenBrick(pix1, pix1, 1, 2);
+ boxa = pixConnComp(pix1, NULL, 8);
+ pixs = pixConvertTo32(pix);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixc = pixCopy(NULL, pixs);
+ RenderHashedBoxa(pixc, boxa, 113);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 50 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixDestroy(&pix);
+ pixDestroy(&pix1);
+
+ /* Set up an affine transform in matd, and apply it to boxa */
+ mat1 = createMatrix2dTranslate(SHIFTX, SHIFTY);
+ mat2 = createMatrix2dScale(SCALEX, SCALEY);
+ mat3 = createMatrix2dRotate(w / 2, h / 2, ROTATION);
+ l_productMat3(mat3, mat2, mat1, matd, 3);
+ boxa2 = boxaAffineTransform(boxa, matd);
+
+ /* Set up the inverse transform --> matdi */
+ mat1i = createMatrix2dTranslate(-SHIFTX, -SHIFTY);
+ mat2i = createMatrix2dScale(1.0/ SCALEX, 1.0 / SCALEY);
+ mat3i = createMatrix2dRotate(w / 2, h / 2, -ROTATION);
+ l_productMat3(mat1i, mat2i, mat3i, matdi, 3);
+
+ /* Invert the original affine transform --> matdinv */
+ affineInvertXform(matd, &matdinv);
+ if (rp->display) {
+ lept_stderr(" Affine transform, applied to boxa\n");
+ for (i = 0; i < 9; i++) {
+ if (i && (i % 3 == 0)) lept_stderr("\n");
+ lept_stderr(" %7.3f ", matd[i]);
+ }
+ lept_stderr("\n Inverse transform, by composing inverse parts");
+ for (i = 0; i < 9; i++) {
+ if (i % 3 == 0) lept_stderr("\n");
+ lept_stderr(" %7.3f ", matdi[i]);
+ }
+ lept_stderr("\n Inverse transform, by inverting affine xform");
+ for (i = 0; i < 6; i++) {
+ if (i % 3 == 0) lept_stderr("\n");
+ lept_stderr(" %7.3f ", matdinv[i]);
+ }
+ lept_stderr("\n");
+ }
+
+ /* Apply the inverted affine transform --> pixs */
+ pixd = pixAffine(pixs, matdinv, L_BRING_IN_WHITE);
+ RenderHashedBoxa(pixd, boxa2, 513);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 51 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 30, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 52 */
+ pixDisplayWithTitle(pix1, 1200, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ pixDestroy(&pixs);
+ boxaDestroy(&boxa);
+ boxaDestroy(&boxa2);
+ lept_free(mat1);
+ lept_free(mat2);
+ lept_free(mat3);
+ lept_free(mat1i);
+ lept_free(mat2i);
+ lept_free(mat3i);
+ lept_free(matdinv);
+#endif
+
+ return regTestCleanup(rp);
+}
+
+static void
+MakePtas(l_int32 i,
+ PTA **pptas,
+ PTA **pptad)
+{
+
+ *pptas = ptaCreate(3);
+ ptaAddPt(*pptas, x1[i], y1[i]);
+ ptaAddPt(*pptas, x2[i], y2[i]);
+ ptaAddPt(*pptas, x3[i], y3[i]);
+ *pptad = ptaCreate(3);
+ ptaAddPt(*pptad, xp1[i], yp1[i]);
+ ptaAddPt(*pptad, xp2[i], yp2[i]);
+ ptaAddPt(*pptad, xp3[i], yp3[i]);
+ return;
+}
+
+
+static l_int32
+RenderHashedBoxa(PIX *pixt,
+ BOXA *boxa,
+ l_int32 i)
+{
+l_int32 j, n, rval, gval, bval;
+BOX *box;
+
+ n = boxaGetCount(boxa);
+ rval = (1413 * i) % 256;
+ gval = (4917 * i) % 256;
+ bval = (7341 * i) % 256;
+ for (j = 0; j < n; j++) {
+ box = boxaGetBox(boxa, j, L_CLONE);
+ pixRenderHashBoxArb(pixt, box, 10, 3, i % 4, 1, rval, gval, bval);
+ boxDestroy(&box);
+ }
+ return 0;
+}
+
+
diff --git a/leptonica/prog/alltests_reg.c b/leptonica/prog/alltests_reg.c
new file mode 100644
index 00000000..6d651424
--- /dev/null
+++ b/leptonica/prog/alltests_reg.c
@@ -0,0 +1,282 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * alltests_reg.c
+ *
+ * Tests all the reg tests:
+ *
+ * alltests_reg command
+ *
+ * where
+ * <command> == "generate" to make the golden files in /tmp/golden
+ * <command> == "compare" to make local files and compare with
+ * the golden files
+ * <command> == "display" to make local files and display
+ *
+ * You can also run each test individually with any one of these
+ * arguments. Warning: if you run this with "display", a very
+ * large number of images will be displayed on the screen.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const char *tests[] = {
+ "adaptmap_reg",
+ "adaptnorm_reg",
+ "affine_reg",
+ "alphaops_reg",
+ "alphaxform_reg",
+ "baseline_reg",
+ "bilateral2_reg",
+ "bilinear_reg",
+ "binarize_reg",
+ "binmorph1_reg",
+ "binmorph3_reg",
+ "binmorph6_reg",
+ "blackwhite_reg",
+ "blend1_reg",
+ "blend2_reg",
+ "blend3_reg",
+ "blend4_reg",
+ "blend5_reg",
+ "boxa1_reg",
+ "boxa2_reg",
+ "boxa3_reg",
+ "boxa4_reg",
+ "bytea_reg",
+ "ccbord_reg",
+ "ccthin1_reg",
+ "ccthin2_reg",
+ "checkerboard_reg",
+ "circle_reg",
+ "cmapquant_reg",
+ "colorcontent_reg",
+ "colorfill_reg",
+ "coloring_reg",
+ "colorize_reg",
+ "colormask_reg",
+ "colormorph_reg",
+ "colorquant_reg",
+ "colorseg_reg",
+ "colorspace_reg",
+ "compare_reg",
+ "compfilter_reg",
+ "conncomp_reg",
+ "conversion_reg",
+ "convolve_reg",
+ "crop_reg",
+ "dewarp_reg",
+ "distance_reg",
+ "dither_reg",
+ "dna_reg",
+ "dwamorph1_reg",
+ "edge_reg",
+ "encoding_reg",
+ "enhance_reg",
+ "equal_reg",
+ "expand_reg",
+ "extrema_reg",
+ "falsecolor_reg",
+ "fhmtauto_reg",
+ /* "files_reg", */
+ "findcorners_reg",
+ "findpattern_reg",
+ "flipdetect_reg",
+ "fpix1_reg",
+ "fpix2_reg",
+ "genfonts_reg",
+#if HAVE_LIBGIF
+ "gifio_reg",
+#endif /* HAVE_LIBGIF */
+ "grayfill_reg",
+ "graymorph1_reg",
+ "graymorph2_reg",
+ "grayquant_reg",
+ "hardlight_reg",
+ "hash_reg",
+ "heap_reg",
+ "insert_reg",
+ "ioformats_reg",
+ "iomisc_reg",
+ "italic_reg",
+ "jbclass_reg",
+#if HAVE_LIBJP2K
+ "jp2kio_reg",
+#endif /* HAVE_LIBJP2K */
+ "jpegio_reg",
+ "kernel_reg",
+ "label_reg",
+ "lineremoval_reg",
+ "locminmax_reg",
+ "logicops_reg",
+ "lowaccess_reg",
+ "lowsat_reg",
+ "maze_reg",
+ "mtiff_reg",
+ "multitype_reg",
+ "numa1_reg",
+ "numa2_reg",
+ "nearline_reg",
+ "newspaper_reg",
+ "overlap_reg",
+ "pageseg_reg",
+ "paint_reg",
+ "paintmask_reg",
+ "pdfio1_reg",
+ "pdfio2_reg",
+ "pdfseg_reg",
+ "pixa1_reg",
+ "pixa2_reg",
+ "pixadisp_reg",
+ "pixcomp_reg",
+ "pixmem_reg",
+ "pixserial_reg",
+ "pngio_reg",
+ "pnmio_reg",
+ "projection_reg",
+ "projective_reg",
+ "psio_reg",
+ "psioseg_reg",
+ "pta_reg",
+ "ptra1_reg",
+ "ptra2_reg",
+ "quadtree_reg",
+ "rank_reg",
+ "rankbin_reg",
+ "rankhisto_reg",
+ "rasterop_reg",
+ "rasteropip_reg",
+ "rectangle_reg",
+ "rotate1_reg",
+ "rotate2_reg",
+ "rotateorth_reg",
+ "scale_reg",
+ "seedspread_reg",
+ "selio_reg",
+ "shear1_reg",
+ "shear2_reg",
+ "skew_reg",
+ "smallpix_reg",
+ "speckle_reg",
+ "splitcomp_reg",
+ "string_reg",
+ "subpixel_reg",
+ "texturefill_reg",
+ "threshnorm_reg",
+ "translate_reg",
+ "warper_reg",
+ "watershed_reg",
+#if HAVE_LIBWEBP_ANIM
+ "webpanimio_reg",
+#endif /* HAVE_LIBWEBP_ANIM */
+#if HAVE_LIBWEBP
+ "webpio_reg",
+#endif /* HAVE_LIBWEBP */
+ "wordboxes_reg",
+ "writetext_reg",
+ "xformbox_reg",
+ };
+
+static const char *header = {"\n=======================\n"
+ "Regression Test Results\n"
+ "======================="};
+
+int main(int argc,
+ char **argv)
+{
+char *str, *results_file;
+char command[256], buf[256];
+l_int32 i, ntests, dotest, nfail, ret, start, stop;
+SARRAY *sa;
+static char mainName[] = "alltests_reg";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax alltests_reg [generate | compare | display]",
+ mainName, 1);
+
+ setLeptDebugOK(1); /* required for testing */
+ l_getCurrentTime(&start, NULL);
+ ntests = sizeof(tests) / sizeof(char *);
+ lept_stderr("Running alltests_reg:\n"
+ "This currently tests %d regression test\n"
+ "programs in the /prog directory.\n", ntests);
+
+ /* Clear the output file if we're doing the set of reg tests */
+ dotest = strcmp(argv[1], "compare") ? 0 : 1;
+ if (dotest) {
+ results_file = genPathname("/tmp/lept", "reg_results.txt");
+ sa = sarrayCreate(3);
+ sarrayAddString(sa, header, L_COPY);
+ sarrayAddString(sa, getLeptonicaVersion(), L_INSERT);
+ sarrayAddString(sa, getImagelibVersions(), L_INSERT);
+ str = sarrayToString(sa, 1);
+ sarrayDestroy(&sa);
+ l_binaryWrite("/tmp/lept/reg_results.txt", "w", str, strlen(str));
+ lept_free(str);
+ }
+
+ nfail = 0;
+ for (i = 0; i < ntests; i++) {
+#ifndef _WIN32
+ snprintf(command, sizeof(command) - 2, "./%s %s", tests[i], argv[1]);
+#else /* windows interprets '/' as a commandline flag */
+ snprintf(command, sizeof(command) - 2, "%s %s", tests[i], argv[1]);
+#endif /* ! _WIN32 */
+ ret = system(command);
+ if (ret) {
+ snprintf(buf, sizeof(buf), "Failed to complete %s\n", tests[i]);
+ if (dotest) {
+ l_binaryWrite("/tmp/lept/reg_results.txt", "a",
+ buf, strlen(buf));
+ nfail++;
+ }
+ else
+ lept_stderr("%s", buf);
+ }
+ }
+
+ if (dotest) {
+#ifndef _WIN32
+ snprintf(command, sizeof(command) - 2, "cat %s", results_file);
+#else
+ snprintf(command, sizeof(command) - 2, "type \"%s\"", results_file);
+#endif /* !_WIN32 */
+ lept_free(results_file);
+ ret = system(command);
+ lept_stderr("Success in %d of %d *_reg programs (output matches"
+ " the \"golden\" files)\n", ntests - nfail, ntests);
+ }
+
+ l_getCurrentTime(&stop, NULL);
+ lept_stderr("Time for all regression tests: %d sec\n", stop - start);
+ return 0;
+}
diff --git a/leptonica/prog/alphaops_reg.c b/leptonica/prog/alphaops_reg.c
new file mode 100644
index 00000000..e373db5f
--- /dev/null
+++ b/leptonica/prog/alphaops_reg.c
@@ -0,0 +1,337 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * alphaops_reg.c
+ *
+ * Tests various functions that use the alpha layer:
+ *
+ * (1) Remove and add alpha layers.
+ * Removing is done by blending with a uniform image.
+ * Adding is done by setting all white pixels to transparent,
+ * and grading the alpha layer to opaque depending on
+ * the distance from the nearest transparent pixel.
+ *
+ * (2) Tests transparency and cleaning under alpha.
+ *
+ * (3) Blending with a uniform color. Also tests an alternative
+ * way to "blend" to a color: component-wise multiplication by
+ * the color.
+ *
+ * (4) Testing RGB and colormapped images with alpha, including
+ * binary and ascii colormap serialization.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static PIX *DoBlendTest(PIX *pix, BOX *box, l_uint32 val, l_float32 gamma,
+ l_int32 minval, l_int32 maxval, l_int32 which);
+
+int main(int argc,
+ char **argv)
+{
+l_uint8 *data;
+l_int32 w, h, n1, n2, n, i, minval, maxval;
+l_int32 ncolors, rval, gval, bval, equal;
+l_int32 *rmap, *gmap, *bmap;
+l_uint32 color;
+l_float32 gamma;
+BOX *box;
+FILE *fp;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+PIX *pixs, *pixb, *pixg, *pixc, *pixd;
+PIX *pixg2, *pixcs1, *pixcs2, *pixd1, *pixd2;
+PIXA *pixa, *pixa2, *pixa3;
+PIXCMAP *cmap, *cmap2;
+RGBA_QUAD *cta;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* ------------------------ (1) ----------------------------*/
+ /* Blend with a white background */
+ pix1 = pixRead("books_logo.png");
+ pixDisplayWithTitle(pix1, 100, 0, NULL, rp->display);
+ pix2 = pixAlphaBlendUniform(pix1, 0xffffff00);
+ pixDisplayWithTitle(pix2, 100, 150, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+
+ /* Generate an alpha layer based on the white background */
+ pix3 = pixSetAlphaOverWhite(pix2);
+ pixSetSpp(pix3, 3);
+ /* without alpha */
+ pixWrite("/tmp/lept/regout/alphaops.2.png", pix3, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/alphaops.2.png"); /* 2 */
+ pixSetSpp(pix3, 4);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3, with alpha */
+ pixDisplayWithTitle(pix3, 100, 300, NULL, rp->display);
+
+ /* Render on a light yellow background */
+ pix4 = pixAlphaBlendUniform(pix3, 0xffffe000);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix4, 100, 450, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* ------------------------ (2) ----------------------------*/
+ lept_mkdir("lept/alpha");
+ /* Make the transparency (alpha) layer.
+ * pixs is the mask. We turn it into a transparency (alpha)
+ * layer by converting to 8 bpp. A small convolution fuzzes
+ * the mask edges so that you don't see the pixels. */
+ pixs = pixRead("feyn-fract.tif");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixg = pixConvert1To8(NULL, pixs, 0, 255);
+ pixg2 = pixBlockconvGray(pixg, NULL, 1, 1);
+ regTestWritePixAndCheck(rp, pixg2, IFF_JFIF_JPEG); /* 5 */
+ pixDisplayWithTitle(pixg2, 0, 0, "alpha", rp->display);
+
+ /* Make the viewable image.
+ * pixc is the image that we see where the alpha layer is
+ * opaque -- i.e., greater than 0. Scale it to the same
+ * size as the mask. To visualize what this will look like
+ * when displayed over a black background, create the black
+ * background image, pixb, and do the blending with pixcs1
+ * explicitly using the alpha layer pixg2. */
+ pixc = pixRead("tetons.jpg");
+ pixcs1 = pixScaleToSize(pixc, w, h);
+ regTestWritePixAndCheck(rp, pixcs1, IFF_JFIF_JPEG); /* 6 */
+ pixDisplayWithTitle(pixcs1, 300, 0, "viewable", rp->display);
+ pixb = pixCreateTemplate(pixcs1); /* black */
+ pixd1 = pixBlendWithGrayMask(pixb, pixcs1, pixg2, 0, 0);
+ regTestWritePixAndCheck(rp, pixd1, IFF_JFIF_JPEG); /* 7 */
+ pixDisplayWithTitle(pixd1, 600, 0, "alpha-blended 1", rp->display);
+
+ /* Embed the alpha layer pixg2 into the color image pixc.
+ * Write it out as is. Then clean pixcs1 (to 0) under the fully
+ * transparent part of the alpha layer, and write that result
+ * out as well. */
+ pixSetRGBComponent(pixcs1, pixg2, L_ALPHA_CHANNEL);
+ pixWrite("/tmp/lept/alpha/cs1.png", pixcs1, IFF_PNG);
+ pixcs2 = pixSetUnderTransparency(pixcs1, 0, 0);
+ pixWrite("/tmp/lept/alpha/cs2.png", pixcs2, IFF_PNG);
+
+ /* What will this look like over a black background?
+ * Do the blending explicitly and display. It should
+ * look identical to the blended result pixd1 before cleaning. */
+ pixd2 = pixBlendWithGrayMask(pixb, pixcs2, pixg2, 0, 0);
+ regTestWritePixAndCheck(rp, pixd2, IFF_JFIF_JPEG); /* 8 */
+ pixDisplayWithTitle(pixd2, 0, 400, "alpha blended 2", rp->display);
+
+ /* Read the two images back, ignoring the transparency layer.
+ * The uncleaned image will come back identical to pixcs1.
+ * However, the cleaned image will be black wherever
+ * the alpha layer was fully transparent. It will
+ * look the same when viewed through the alpha layer,
+ * but have much better compression. */
+ pix1 = pixRead("/tmp/lept/alpha/cs1.png"); /* just pixcs1 */
+ pix2 = pixRead("/tmp/lept/alpha/cs2.png"); /* cleaned under transparent */
+ n1 = nbytesInFile("/tmp/lept/alpha/cs1.png");
+ n2 = nbytesInFile("/tmp/lept/alpha/cs2.png");
+ lept_stderr(" Original: %d bytes\n Cleaned: %d bytes\n", n1, n2);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 9 */
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 10 */
+ pixDisplayWithTitle(pix1, 300, 400, "without alpha", rp->display);
+ pixDisplayWithTitle(pix2, 600, 400, "cleaned under transparent",
+ rp->display);
+
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixg2, L_INSERT);
+ pixaAddPix(pixa, pixcs1, L_INSERT);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pixd1, L_INSERT);
+ pixaAddPix(pixa, pixd2, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixd = pixaDisplayTiledInColumns(pixa, 1, 1.0, 20, 2);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 11 */
+ pixDisplayWithTitle(pixd, 200, 200, "composite", rp->display);
+ pixWrite("/tmp/lept/alpha/composite.png", pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs);
+ pixDestroy(&pixb);
+ pixDestroy(&pixg);
+ pixDestroy(&pixc);
+ pixDestroy(&pixcs2);
+ pixDestroy(&pixd);
+
+ /* ------------------------ (3) ----------------------------*/
+ color = 0xffffa000;
+ gamma = 1.0;
+ minval = 0;
+ maxval = 200;
+ box = boxCreate(0, 85, 600, 100);
+ pixa = pixaCreate(6);
+ pix1 = pixRead("blend-green1.jpg");
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixRead("blend-green2.png");
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixRead("blend-green3.png");
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixRead("blend-orange.jpg");
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixRead("blend-yellow.jpg");
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixRead("blend-red.png");
+ pixaAddPix(pixa, pix1, L_INSERT);
+ n = pixaGetCount(pixa);
+ pixa2 = pixaCreate(n);
+ pixa3 = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa, i, L_CLONE);
+ pix2 = DoBlendTest(pix1, box, color, gamma, minval, maxval, 1);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 12, 14, ... 22 */
+ pixDisplayWithTitle(pix2, 150 * i, 0, NULL, rp->display);
+ pixaAddPix(pixa2, pix2, L_INSERT);
+ pix2 = DoBlendTest(pix1, box, color, gamma, minval, maxval, 2);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 13, 15, ... 23 */
+ pixDisplayWithTitle(pix2, 150 * i, 200, NULL, rp->display);
+ pixaAddPix(pixa3, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ if (rp->display) {
+ pixaConvertToPdf(pixa2, 0, 0.75, L_FLATE_ENCODE, 0, "blend 1 test",
+ "/tmp/lept/alpha/blend1.pdf");
+ pixaConvertToPdf(pixa3, 0, 0.75, L_FLATE_ENCODE, 0, "blend 2 test",
+ "/tmp/lept/alpha/blend2.pdf");
+ }
+ pixaDestroy(&pixa);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ boxDestroy(&box);
+
+ /* ------------------------ (4) ----------------------------*/
+ /* Use one image as the alpha component for a second image */
+ pix1 = pixRead("test24.jpg");
+ pix2 = pixRead("marge.jpg");
+ pix3 = pixScale(pix2, 1.9, 2.2);
+ pix4 = pixConvertTo8(pix3, 0);
+ pixSetRGBComponent(pix1, pix4, L_ALPHA_CHANNEL);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 24 */
+ pixDisplayWithTitle(pix1, 600, 0, NULL, rp->display);
+
+ /* Set the alpha value in a colormap to bval */
+ pix5 = pixOctreeColorQuant(pix1, 128, 0);
+ cmap = pixGetColormap(pix5);
+ pixcmapToArrays(cmap, &rmap, &gmap, &bmap, NULL);
+ n = pixcmapGetCount(cmap);
+ for (i = 0; i < n; i++) {
+ pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+ cta = (RGBA_QUAD *)cmap->array;
+ cta[i].alpha = bval;
+ }
+
+ /* Test binary serialization/deserialization of colormap with alpha */
+ pixcmapSerializeToMemory(cmap, 4, &ncolors, &data);
+ cmap2 = pixcmapDeserializeFromMemory(data, 4, ncolors);
+ cmapEqual(cmap, cmap2, 4, &equal);
+ regTestCompareValues(rp, TRUE, equal, 0.0); /* 25 */
+ pixcmapDestroy(&cmap2);
+ lept_free(data);
+
+ /* Test ascii serialization/deserialization of colormap with alpha */
+ fp = fopenWriteStream("/tmp/lept/alpha/cmap.4", "w");
+ pixcmapWriteStream(fp, cmap);
+ fclose(fp);
+ fp = fopenReadStream("/tmp/lept/alpha/cmap.4");
+ cmap2 = pixcmapReadStream(fp);
+ fclose(fp);
+ cmapEqual(cmap, cmap2, 4, &equal);
+ regTestCompareValues(rp, TRUE, equal, 0.0); /* 26 */
+ pixcmapDestroy(&cmap2);
+
+ /* Test r/w for cmapped pix with non-opaque alpha */
+ pixDisplayWithTitle(pix5, 900, 0, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 27 */
+ pixWrite("/tmp/lept/alpha/fourcomp.png", pix5, IFF_PNG);
+ pix6 = pixRead("/tmp/lept/alpha/fourcomp.png");
+ regTestComparePix(rp, pix5, pix6); /* 28 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ lept_free(rmap);
+ lept_free(gmap);
+ lept_free(bmap);
+ return regTestCleanup(rp);
+}
+
+
+ /* Use which == 1 to test alpha blending; which == 2 to test "blending"
+ * by pixel multiplication. This generates a composite of 5 images:
+ * the original, blending over a box at the bottom (2 ways), and
+ * multiplying over a box at the bottom (2 ways). */
+static PIX *
+DoBlendTest(PIX *pix, BOX *box, l_uint32 color, l_float32 gamma,
+ l_int32 minval, l_int32 maxval, l_int32 which)
+{
+PIX *pix1, *pix2, *pix3, *pixd;
+PIXA *pixa;
+ pixa = pixaCreate(5);
+ pix1 = pixRemoveColormap(pix, REMOVE_CMAP_TO_FULL_COLOR);
+ pix2 = pixCopy(NULL, pix1);
+ pixaAddPix(pixa, pix2, L_COPY);
+ if (which == 1)
+ pix3 = pixBlendBackgroundToColor(NULL, pix2, box, color,
+ gamma, minval, maxval);
+ else
+ pix3 = pixMultiplyByColor(NULL, pix2, box, color);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ if (which == 1)
+ pixBlendBackgroundToColor(pix2, pix2, box, color,
+ gamma, minval, maxval);
+ else
+ pixMultiplyByColor(pix2, pix2, box, color);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix2 = pixCopy(NULL, pix1);
+ if (which == 1)
+ pix3 = pixBlendBackgroundToColor(NULL, pix2, NULL, color,
+ gamma, minval, maxval);
+ else
+ pix3 = pixMultiplyByColor(NULL, pix2, NULL, color);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ if (which == 1)
+ pixBlendBackgroundToColor(pix2, pix2, NULL, color,
+ gamma, minval, maxval);
+ else
+ pixMultiplyByColor(pix2, pix2, NULL, color);
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ pixd = pixaDisplayTiledInRows(pixa, 32, 800, 1.0, 0, 30, 2);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+ return pixd;
+}
diff --git a/leptonica/prog/alphaxform_reg.c b/leptonica/prog/alphaxform_reg.c
new file mode 100644
index 00000000..7d30c47b
--- /dev/null
+++ b/leptonica/prog/alphaxform_reg.c
@@ -0,0 +1,230 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * alphaxform_reg.c
+ *
+ * This tests the alpha blending functions when used with various
+ * transforms (scaling, rotation, affine, projective, bilinear).
+ *
+ * It also tests the version that are wrapped in a gamma transform,
+ * which is a technique for getting a truer color on transforming,
+ * because it undoes the gamma that has been applied to an image
+ * before transforming and then re-applying the gamma transform.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static void MakePtas(l_int32 i, l_int32 npts, PTA **pptas, PTA **pptad);
+
+static const l_int32 x1[] = { 300, 300, 300, 95, 32 };
+static const l_int32 y1[] = { 1200, 1200, 1250, 2821, 934 };
+static const l_int32 x2[] = { 1200, 1200, 1125, 1432, 487 };
+static const l_int32 y2[] = { 1100, 1100, 1100, 2682, 934 };
+static const l_int32 x3[] = { 200, 200, 200, 232, 32 };
+static const l_int32 y3[] = { 200, 200, 200, 657, 67 };
+static const l_int32 x4[] = { 1200, 1200, 1125, 1432, 487 };
+static const l_int32 y4[] = { 200, 200, 200, 242, 84 };
+
+static const l_int32 xp1[] = { 500, 300, 350, 117, 32 };
+static const l_int32 yp1[] = { 1700, 1400, 1100, 2629, 934 };
+static const l_int32 xp2[] = { 850, 400, 1100, 1664, 487 };
+static const l_int32 yp2[] = { 850, 500, 1300, 2432, 804 };
+static const l_int32 xp3[] = { 450, 200, 400, 183, 61 };
+static const l_int32 yp3[] = { 300, 300, 400, 490, 83 };
+static const l_int32 xp4[] = { 850, 1000, 1100, 1664, 487 };
+static const l_int32 yp4[] = { 350, 350, 400, 532, 114 };
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs2, *pixs3, *pixb1, *pixb2, *pixb3;
+PIX *pixr2, *pixr3;
+PIX *pixc1, *pixc2, *pixc3, *pixcs1, *pixcs2, *pixcs3;
+PIX *pixd, *pixt1, *pixt2, *pixt3;
+PTA *ptas1, *ptas2, *ptas3, *ptad1, *ptad2, *ptad3;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixc1 = pixRead("test24.jpg");
+ pixc2 = pixRead("wyom.jpg");
+ pixc3 = pixRead("marge.jpg");
+
+ /* Test alpha blend scaling */
+ pixd = pixCreate(900, 400, 32);
+ pixSetAll(pixd);
+ pixs2 = pixScaleWithAlpha(pixc2, 0.5, 0.5, NULL, 0.3);
+ pixs3 = pixScaleWithAlpha(pixc3, 0.4, 0.4, NULL, 0.7);
+ pixb1 = pixBlendWithGrayMask(pixd, pixs3, NULL, 100, 100);
+ pixb2 = pixBlendWithGrayMask(pixb1, pixs2, NULL, 300, 130);
+ pixb3 = pixBlendWithGrayMask(pixb2, pixs3, NULL, 600, 160);
+ regTestWritePixAndCheck(rp, pixb3, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pixb3, 900, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixDestroy(&pixs2);
+ pixDestroy(&pixs3);
+ pixDestroy(&pixb1);
+ pixDestroy(&pixb2);
+ pixDestroy(&pixb3);
+
+ /* Test alpha blend rotation */
+ pixd = pixCreate(1200, 800, 32);
+ pixSetAll(pixd);
+ pixr3 = pixRotateWithAlpha(pixc3, -0.3, NULL, 1.0);
+ pixr2 = pixRotateWithAlpha(pixc2, +0.3, NULL, 1.0);
+ pixb3 = pixBlendWithGrayMask(pixd, pixr3, NULL, 100, 100);
+ pixb2 = pixBlendWithGrayMask(pixb3, pixr2, NULL, 400, 100);
+ regTestWritePixAndCheck(rp, pixb2, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pixb2, 500, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixDestroy(&pixr3);
+ pixDestroy(&pixr2);
+ pixDestroy(&pixb3);
+ pixDestroy(&pixb2);
+
+ pixcs1 = pixScale(pixc1, 0.35, 0.35);
+ pixcs2 = pixScale(pixc2, 0.55, 0.55);
+ pixcs3 = pixScale(pixc3, 0.65, 0.65);
+
+ /* Test alpha blend affine */
+ pixd = pixCreate(800, 900, 32);
+ pixSetAll(pixd);
+ MakePtas(2, 3, &ptas1, &ptad1);
+ MakePtas(4, 3, &ptas2, &ptad2);
+ MakePtas(3, 3, &ptas3, &ptad3);
+ pixt1 = pixAffinePtaWithAlpha(pixcs1, ptad1, ptas1, NULL, 1.0, 300);
+ pixt2 = pixAffinePtaWithAlpha(pixcs2, ptad2, ptas2, NULL, 0.8, 400);
+ pixt3 = pixAffinePtaWithAlpha(pixcs3, ptad3, ptas3, NULL, 0.7, 300);
+ pixb1 = pixBlendWithGrayMask(pixd, pixt1, NULL, -250, 20);
+ pixb2 = pixBlendWithGrayMask(pixb1, pixt2, NULL, -150, -250);
+ pixb3 = pixBlendWithGrayMask(pixb2, pixt3, NULL, -100, 220);
+ regTestWritePixAndCheck(rp, pixb3, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pixb3, 100, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixDestroy(&pixb1);
+ pixDestroy(&pixb2);
+ pixDestroy(&pixb3);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixDestroy(&pixt3);
+ ptaDestroy(&ptas1);
+ ptaDestroy(&ptas2);
+ ptaDestroy(&ptas3);
+ ptaDestroy(&ptad1);
+ ptaDestroy(&ptad2);
+ ptaDestroy(&ptad3);
+
+ /* Test alpha blend projective */
+ pixd = pixCreate(900, 900, 32);
+ pixSetAll(pixd);
+ MakePtas(2, 4, &ptas1, &ptad1);
+ MakePtas(4, 4, &ptas2, &ptad2);
+ MakePtas(3, 4, &ptas3, &ptad3);
+ pixt1 = pixProjectivePtaWithAlpha(pixcs1, ptad1, ptas1, NULL, 1.0, 300);
+ pixt2 = pixProjectivePtaWithAlpha(pixcs2, ptad2, ptas2, NULL, 0.8, 400);
+ pixt3 = pixProjectivePtaWithAlpha(pixcs3, ptad3, ptas3, NULL, 0.7, 400);
+ pixb1 = pixBlendWithGrayMask(pixd, pixt1, NULL, -150, 20);
+ pixb2 = pixBlendWithGrayMask(pixb1, pixt2, NULL, -50, -250);
+ pixb3 = pixBlendWithGrayMask(pixb2, pixt3, NULL, -100, 220);
+ regTestWritePixAndCheck(rp, pixb3, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pixb3, 300, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixDestroy(&pixb1);
+ pixDestroy(&pixb2);
+ pixDestroy(&pixb3);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixDestroy(&pixt3);
+ ptaDestroy(&ptas1);
+ ptaDestroy(&ptas2);
+ ptaDestroy(&ptas3);
+ ptaDestroy(&ptad1);
+ ptaDestroy(&ptad2);
+ ptaDestroy(&ptad3);
+
+ /* Test alpha blend bilinear */
+ pixd = pixCreate(900, 900, 32);
+ pixSetAll(pixd);
+ MakePtas(2, 4, &ptas1, &ptad1);
+ MakePtas(4, 4, &ptas2, &ptad2);
+ MakePtas(3, 4, &ptas3, &ptad3);
+ pixt1 = pixBilinearPtaWithAlpha(pixcs1, ptad1, ptas1, NULL, 1.0, 300);
+ pixt2 = pixBilinearPtaWithAlpha(pixcs2, ptad2, ptas2, NULL, 0.8, 400);
+ pixt3 = pixBilinearPtaWithAlpha(pixcs3, ptad3, ptas3, NULL, 0.7, 400);
+ pixb1 = pixBlendWithGrayMask(pixd, pixt1, NULL, -150, 20);
+ pixb2 = pixBlendWithGrayMask(pixb1, pixt2, NULL, -50, -250);
+ pixb3 = pixBlendWithGrayMask(pixb2, pixt3, NULL, -100, 220);
+ regTestWritePixAndCheck(rp, pixb3, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pixb3, 500, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixDestroy(&pixb1);
+ pixDestroy(&pixb2);
+ pixDestroy(&pixb3);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixDestroy(&pixt3);
+ ptaDestroy(&ptas1);
+ ptaDestroy(&ptas2);
+ ptaDestroy(&ptas3);
+ ptaDestroy(&ptad1);
+ ptaDestroy(&ptad2);
+ ptaDestroy(&ptad3);
+
+ pixDestroy(&pixc1);
+ pixDestroy(&pixc2);
+ pixDestroy(&pixc3);
+ pixDestroy(&pixcs1);
+ pixDestroy(&pixcs2);
+ pixDestroy(&pixcs3);
+ return regTestCleanup(rp);
+}
+
+
+static void
+MakePtas(l_int32 i,
+ l_int32 npts, /* 3 or 4 */
+ PTA **pptas,
+ PTA **pptad)
+{
+
+ *pptas = ptaCreate(npts);
+ ptaAddPt(*pptas, x1[i], y1[i]);
+ ptaAddPt(*pptas, x2[i], y2[i]);
+ ptaAddPt(*pptas, x3[i], y3[i]);
+ if (npts == 4) ptaAddPt(*pptas, x4[i], y4[i]);
+ *pptad = ptaCreate(npts);
+ ptaAddPt(*pptad, xp1[i], yp1[i]);
+ ptaAddPt(*pptad, xp2[i], yp2[i]);
+ ptaAddPt(*pptad, xp3[i], yp3[i]);
+ if (npts == 4) ptaAddPt(*pptad, xp4[i], yp4[i]);
+ return;
+}
diff --git a/leptonica/prog/amoris.2.150.jpg b/leptonica/prog/amoris.2.150.jpg
new file mode 100644
index 00000000..0e251068
--- /dev/null
+++ b/leptonica/prog/amoris.2.150.jpg
Binary files differ
diff --git a/leptonica/prog/aneurisms8.jpg b/leptonica/prog/aneurisms8.jpg
new file mode 100644
index 00000000..97432588
--- /dev/null
+++ b/leptonica/prog/aneurisms8.jpg
Binary files differ
diff --git a/leptonica/prog/arabic.png b/leptonica/prog/arabic.png
new file mode 100644
index 00000000..2cd43f35
--- /dev/null
+++ b/leptonica/prog/arabic.png
Binary files differ
diff --git a/leptonica/prog/arabic2.png b/leptonica/prog/arabic2.png
new file mode 100644
index 00000000..80180539
--- /dev/null
+++ b/leptonica/prog/arabic2.png
Binary files differ
diff --git a/leptonica/prog/arabic_lines.c b/leptonica/prog/arabic_lines.c
new file mode 100644
index 00000000..d4a95db8
--- /dev/null
+++ b/leptonica/prog/arabic_lines.c
@@ -0,0 +1,166 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * arabic_lines.c
+ *
+ * Demonstrates some segmentation techniques and display options.
+ * To see the results in one image: /tmp/lept/lineseg/result.png.
+ *
+ * This demonstration shows many different operations. However,
+ * better results may be obtained from pixExtractLines()
+ * which is a much simpler function. See testmisc1.c for examples.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+
+/* Hit-miss transform that splits lightly touching lines */
+static const char *seltext = "xxxxxxx"
+ " x "
+ " x "
+ " x "
+ " x "
+ " x "
+ " x "
+ " x "
+ "o X o"
+ " x "
+ " x "
+ " x "
+ " x "
+ " x "
+ " x "
+ " x "
+ "xxxxxxx";
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w, h, d, w2, h2, i, ncols, same;
+l_float32 angle, conf;
+BOX *box;
+BOXA *boxa1, *boxa2;
+PIX *pix, *pixs, *pixb, *pixb2;
+PIX *pix1, *pix2, *pix3, *pix4;
+PIXA *pixam; /* mask with a single component over each column */
+PIXA *pixa, *pixa1, *pixa2;
+PIXAA *pixaa, *pixaa2;
+SEL *selsplit;
+static char mainName[] = "arabic_lines";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: arabic_lines", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/lineseg");
+ pixa = pixaCreate(0);
+
+ /* Binarize input */
+ pixs = pixRead("arabic.png");
+ pixGetDimensions(pixs, &w, &h, &d);
+ pix = pixConvertTo1(pixs, 128);
+ pixDestroy(&pixs);
+
+ /* Deskew */
+ pixb = pixFindSkewAndDeskew(pix, 1, &angle, &conf);
+ pixDestroy(&pix);
+ lept_stderr("Skew angle: %7.2f degrees; %6.2f conf\n", angle, conf);
+ pixaAddPix(pixa, pixb, L_INSERT);
+
+ /* Use full image morphology to find columns, at 2x reduction.
+ This only works for very simple layouts where each column
+ of text extends the full height of the input image. */
+ pixb2 = pixReduceRankBinary2(pixb, 2, NULL);
+ pix1 = pixMorphCompSequence(pixb2, "c5.500 + o20.20", 0);
+ boxa1 = pixConnComp(pix1, &pixam, 8);
+ ncols = boxaGetCount(boxa1);
+ lept_stderr("Num columns: %d\n", ncols);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ boxaDestroy(&boxa1);
+
+ /* Use selective region-based morphology to get the textline mask. */
+ pixa2 = pixaMorphSequenceByRegion(pixb2, pixam, "c100.3 + o30.1", 0, 0);
+ pixGetDimensions(pixb2, &w2, &h2, NULL);
+ pix2 = pixaDisplay(pixa2, w2, h2);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaDestroy(&pixam);
+ pixDestroy(&pixb2);
+
+ /* Some of the lines may be touching, so use a HMT to split the
+ lines in each column, and use a pixaa to save the results. */
+ selsplit = selCreateFromString(seltext, 17, 7, "selsplit");
+ pixaa = pixaaCreate(ncols);
+ for (i = 0; i < ncols; i++) {
+ pix2 = pixaGetPix(pixa2, i, L_CLONE);
+ box = pixaGetBox(pixa2, i, L_COPY);
+ pix3 = pixHMT(NULL, pix2, selsplit);
+ pixXor(pix3, pix3, pix2);
+ boxa2 = pixConnComp(pix3, &pixa1, 8);
+ pixaaAddPixa(pixaa, pixa1, L_INSERT);
+ pixaaAddBox(pixaa, box, L_INSERT);
+ pix4 = pixaDisplayRandomCmap(pixa1, 0, 0);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ lept_stderr("Num textlines in col %d: %d\n", i, boxaGetCount(boxa2));
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ boxaDestroy(&boxa2);
+ }
+ pixaDestroy(&pixa2);
+
+ /* Visual output */
+ pix2 = selDisplayInPix(selsplit, 31, 2);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix3 = pixaDisplayTiledAndScaled(pixa, 32, 400, 3, 0, 35, 3);
+ pixWrite("/tmp/lept/lineseg/result.png", pix3, IFF_PNG);
+ pixDisplay(pix3, 100, 100);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix3);
+ selDestroy(&selsplit);
+
+ /* Test pixaa I/O */
+ pixaaWrite("/tmp/lept/lineseg/pixaa", pixaa);
+ pixaa2 = pixaaRead("/tmp/lept/lineseg/pixaa");
+ pixaaWrite("/tmp/lept/lineseg/pixaa2", pixaa2);
+ filesAreIdentical("/tmp/lept/lineseg/pixaa", "/tmp/lept/lineseg/pixaa2",
+ &same);
+ if (!same)
+ L_ERROR("pixaa I/O failure\n", mainName);
+ pixaaDestroy(&pixaa2);
+
+ /* Test pixaa display */
+ pix2 = pixaaDisplay(pixaa, w2, h2);
+ pixWrite("/tmp/lept/lineseg/textlines.png", pix2, IFF_PNG);
+ pixaaDestroy(&pixaa);
+ pixDestroy(&pix2);
+
+ return 0;
+}
+
diff --git a/leptonica/prog/arithtest.c b/leptonica/prog/arithtest.c
new file mode 100644
index 00000000..75fac589
--- /dev/null
+++ b/leptonica/prog/arithtest.c
@@ -0,0 +1,83 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * arithtest.c
+ *
+ * Test the pixacc functions, using an 8 bpp image and converting
+ * back and forth between 8 and 16 bpp.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein;
+l_int32 w, h;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5;
+static char mainName[] = "arithtest";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: arithtest filein", mainName, 1);
+ filein = argv[1];
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/arith");
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pix not made", mainName, 1);
+
+ /* Input a grayscale image and convert it to 16 bpp */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pix1 = pixInitAccumulate(w, h, 0);
+ pixAccumulate(pix1, pixs, L_ARITH_ADD);
+ pixMultConstAccumulate(pix1, 255., 0);
+ pix2 = pixFinalAccumulate(pix1, 0, 16);
+ l_pngSetReadStrip16To8(0);
+ pixWrite("/tmp/lept/arith/pix1.png", pix2, IFF_PNG);
+
+ /* Convert it back to 8 bpp, linear mapped */
+ pix3 = pixMaxDynamicRange(pix2, L_LINEAR_SCALE);
+ pixWrite("/tmp/lept/arith/pix2.png", pix3, IFF_PNG);
+
+ /* Convert it back to 8 bpp using the MSB */
+ pix4 = pixRead("/tmp/pix1.png");
+ pix5 = pixConvert16To8(pix4, 1);
+ pixWrite("/tmp/lept/arith/pix3.png", pix5, IFF_PNG);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ return 0;
+}
diff --git a/leptonica/prog/autogen.137.c b/leptonica/prog/autogen.137.c
new file mode 100644
index 00000000..f414eee9
--- /dev/null
+++ b/leptonica/prog/autogen.137.c
@@ -0,0 +1,98 @@
+/*====================================================================*
+ - 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.137.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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+#include "autogen.137.h"
+
+/*---------------------------------------------------------------------*/
+/* Auto-generated deserializers */
+/*---------------------------------------------------------------------*/
+/*!
+ * \brief l_autodecode_137()
+ *
+ * \param[in] index into array of functions
+ * \return data struct e.g., pixa, in memory
+ */
+void *
+l_autodecode_137(l_int32 index)
+{
+l_uint8 *data1, *data2;
+l_int32 size1;
+size_t size2;
+void *result = NULL;
+l_int32 nfunc = 2;
+
+ PROCNAME("l_autodecode_137");
+
+ 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 the selected string, uncompress it, and read it */
+ switch (index) {
+ case 0:
+ data1 = decodeBase64(l_strdata_0, strlen(l_strdata_0), &size1);
+ data2 = zlibUncompress(data1, size1, &size2);
+ result = (void *)pixaReadMem(data2, size2);
+ lept_free(data1);
+ lept_free(data2);
+ break;
+ case 1:
+ data1 = decodeBase64(l_strdata_1, strlen(l_strdata_1), &size1);
+ data2 = zlibUncompress(data1, size1, &size2);
+ result = (void *)pixaReadMem(data2, size2);
+ lept_free(data1);
+ lept_free(data2);
+ break;
+ default:
+ L_ERROR("invalid index", procName);
+ }
+
+ return result;
+}
+
+
diff --git a/leptonica/prog/autogen.137.h b/leptonica/prog/autogen.137.h
new file mode 100644
index 00000000..81d4e487
--- /dev/null
+++ b/leptonica/prog/autogen.137.h
@@ -0,0 +1,306 @@
+/*====================================================================*
+ - 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.137.h
+ *
+ * Automatically generated function prototype and associated
+ * encoded serialized strings.
+ */
+
+#ifndef LEPTONICA_AUTOGEN_137_H
+#define LEPTONICA_AUTOGEN_137_H
+
+/*---------------------------------------------------------------------*/
+/* Function prototype */
+/*---------------------------------------------------------------------*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void *l_autodecode_137(l_int32 index);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+/*---------------------------------------------------------------------*/
+/* Serialized strings */
+/*---------------------------------------------------------------------*/
+static const char *l_strdata_0 =
+ "eJy1mgtUE1cax+9kQh4wIREQBhAyQCoPFYd3UEjCQ0RtC1prbbUyaBWsVvDRFRRIAjSgSwta"
+ "16q1Ilq7PrpdtN3W1q1OHCtUEcTXqu0KwSpY2yWIj4Ah2aA1xJ3NIeM5cg6gnJwzv9x78/99"
+ "97vXOXNxQTY2a+GKlYvzlmGRzi+/+878hSuwvEVY/uICLBGLj3F2Ts6z85L5eQULV1pehDsP"
+ "vnoO/uYErGDF47+MxQr/+JfzusyXJwucfZwBAIIp6akzLL85lm8UsvwAPUsOulp+8fPTX18J"
+ "nnx9Mn905KNXpybN5G1dYKAUbmwQ97JX3Sulc28MvmDKpJdTv0gmSh89NmLosRGR+NCTB//z"
+ "/x4OWx/ulbxm6tDDD1cNfkNN94+Mt/zR+4+HF5B6FeQ3VpVcs501+xcIYQFK76xehk69RgOJ"
+ "ZAjCtYL477uKDwdiaNBXJ5VqWbzVQBeuURoCwHc/elR3HQicSAOJYggisILw/zy1zx5I8BMQ"
+ "cxlUKWtR1fkhbeNQHxTdMQcTwVzqW+dV7FZeCtjFCriRcYa4TcOKZojlYsVi/2XCUXtYyY+x"
+ "/E5HCEjMA7ROC9t9+oHau2iVlruoZsXFs3WU+A2s8Lrf3xN2Ve3kLtzvEX+jZlZEeuPXc8+7"
+ "1sxZcaztDnwp14+tN95g04hjGBK7W4nx3x7uskecYR1IMwTz5KYBVfbNTi4S26Y2xIuOcTt6"
+ "FQcfClHXFu7+K8ITnnzE/OeRqGlPHG4svEbqvSqVrJ7uFmekVinBiyq2vVcOFX0Qz+1eA++g"
+ "4ccyxHez4reF12y3h//iED6Aeea9clL/owlQK0NKOL/IwXo5u6OBi6KurJ6qFi66VHHYAMo+"
+ "6Yc4hSXYMhl5tpkk83eSap1ZXvl6OgpSODiVMcadvobjGLI7Wdl95joL7LGPesKu1at0gOJr"
+ "XDXeyEQ0DC1prwDtx0ZGnM96J4vGImXIYi/enmYJssabXq3jU96aichKVaZGy3Fh//gztL/w"
+ "uN4JyFyQf//eXhJDQ4p/LkijrUiZFEHhFTpEJIOOcxUGiC8Dfy59qxP/LXsvBH444v5CWOoX"
+ "8+lRjDPE4lmxunepv7eHhT3BatHX6qLKIsqkFIS9xT0kiyrKMVS7sMCBLPfzGzKcd9GBmMrB"
+ "sSj0tIlCwcAPCoUqtHK/C5ijHudzJaNYRsdgqgbHpsuqBkollhFSVnkOehPUCSgxCO4fuRBe"
+ "HJBMJ2HqBsdmyM06Qyq/WByf784Ffi2eS9E4VyUdgakHHBO2u1XYKnEYjoOI7UJwJcutTtLe"
+ "N4POwDTZHXNRonVdqHRKmDI7IeaRiq4sFm+gECQrxbixiNQ/hKgcfI1rWqe2ywRr+oTofTlR"
+ "z32bGwD+dDH4/Q19aafotEyD3DHacUO0UKV/AiH9iMSz1NkyuCxRFEMk3VF8Cmac/4jMB5+7"
+ "8CFQwfflb/xMcIZOxzSqna10t1+brrJHJ35Cp7TQycSEtEiVhDZhnBgcV0QfF4FVW0ZfjS/v"
+ "uETnYRrXiJXn5pSABns8MutoqS2j5YFHo3iTO3FZmOqOmlJqIMrM0ijFqKmEMPSpdb1cTacY"
+ "bVYSuQSAKtNQFmjeF9TShGN/o+MyjXLHJjf+fye3hsQz1NlydOA1wvARqe+EKC72sdqyTD0u"
+ "xuGXx+BN04hccDs5GRS3BXiVfbMtnUYayTTdHRvYCY9JY05r+Spc5NQaWhg2NXRzjduM0rH+"
+ "kspUr81F13qEWTWb/xl5Y8O0ZfF7TteMrrhwZxYr7OeST7UB8/SrwrfTUZnmvmODGm47qK64"
+ "6uwAl7cqQK1TsahEjsasuKBS3HdGet1QH/K7GhH4+6XABF59Kd3ekUx94BhetC2eSfIobowQ"
+ "VYhvE6ax+EiRM3IHQe4LeNFZ6rrxah2Y7eXGAu9eERfwswwv0iGZqsKx6Y63/RxZzFlXAqd8"
+ "D6UUQ5Qc67esS5jKwPuRtBY5PspM5pvgFDOXU1gfLmGDvxe9ENgv95pMJ30+O4vY//kIdU0j"
+ "6l9TBX7L1nzNRbjqDr6lXpvL4azF3gZBTppCPsJ9NOvrq3xli3/722k6JlPpOBaUf3x+Jp3W"
+ "ChQKwCb3pY75grydFHppmt/b+o2VEyWBW7tzl/+0y/OViH7BTE+f1wq3Lz/hv6Qpz7jqrcW+"
+ "/zq69hROR2VqnMG9k9fjEW08f3M4RxtIlXPwoKNdZaBe4BYRcGd+NZ3hWbYAjxluOx++bI9h"
+ "aAugchYCHSwxwfWN/EfbgLGg86L7vgIZSpdKJFOpCKwwhn8bxg6v4DLIL5EwtJH6jFLdZCeK"
+ "EiO/y9FftJIagQe0Hl6Y9LboDTB3TnBsVm3hv+l0TB3iGJ27DZ2Qxfp86Uw22D4mNJWddq6T"
+ "xhDF1A6OMeC2pfYVM9Fu1uYbuFSFGFGORU0rcaNl+3kMPmECnp3cA0nnWCDOfbR/R8woFh2Q"
+ "qRP4VsCe2NmzhgndSacO8gGGOLX6/jT16Pn49Xv28OZXHRTNW5m/JX9SuqSxPvvWtvmVo9cU"
+ "/DVHcN/vgr+/aGl14nk6JFMzjLBC/pCx1W5xnGkdxWNqqFypj8OLin62pGsOTBmVAuThBRQd"
+ "e9Ubda6OwiVl+niO5q5MtIzI7H6HuCz5kcSvi2CqTy5AhHLWvS9TmpKOA8HZkKmJBzI/pr8B"
+ "ptZws76B8ILAKfbewLTHbyCx6YhAkQkUrR+HVGV7BPlow7555eiIdI+pG17Td4iTl1+lCk/t"
+ "3nT9lcZZp+dNcT4U68bduvZWpPeuqg/LJb8H3hvX3X5snteeabGLl7gs+j9LmKlHHFvCEbaN"
+ "M1SCNq/Gt8uhFMXXbAraiXGpPhjpFGN17tg+jgunMg1ngZdaA955ee6Ii3RCpgoRWQnD3r+z"
+ "anjT1VpqbRxPMFrWxniqCOtvwcy1Wn/+j2UjMv9pllM9ZkRYghuJt1S57CDw5edBrZn7rjTT"
+ "MZnqw7FVHPXUKva+7Is2yzkdtQojt9wbbTZT6qQVWIxcZQgSiZcRue0VW8rKoTFnwrdMjtyI"
+ "0BmZ6sXVyuj+a8p4e4zjrYwVUKWqvY+L9x/XG4VUbxx214BQsH+Ir4SXKt+5V62Gdo2oAkG3"
+ "gr0nHtlaTOd7PsYJsV2Mg3xov1ZvdOIlyrR6FXyKww8M4NSHWzYsAd7B0w+cn/F/ustMZePY"
+ "5E58anLNeDReZDxP5huzYKoY2yH0+LKh9IYYxxvN4BR88X7ZBbgHxkxaEMHlw2DRG+ODj6yQ"
+ "5NFYo5lKaaSVtenTeLs9eWufzGzmwrwo0Npr9pzdA3NcNA3m+tNaPF3eNDaEA1q2RS69B2We"
+ "oGMxVRHPiuV/NyrXHpanTZ9MrdbFz2sk9WIW9F2se7lr1heb6BhMZeOYEa1tqTYSpAoQpNa3"
+ "ManNFzX4gpeaxR+nbt8ZTid5PtaY/FSrOspIEAWmPkD1diqK/Cs74YMDkCbnHhfdAW6IVbmY"
+ "+dsE0QO4ZzlWgpwQqw3cTLOYw9nyeiYCFvnEhCxYsLSXzs3UGEIrtzEkZIQ9bj8rd7XFGKaI"
+ "hGDOgGoX/CIiep9ggz3rwni/v5XhS6dhagdPK03wh6VR9mheGhpFIcz70pxHSE1ZnCqh+cPs"
+ "w8Y8V+oH+Up0mekTKh+u7DayO36NRx6sV18ucqWK38VucjtKsB3yCvWAPww/+lC4yyPvXnZn"
+ "0euzaKbScLfSa7+bstoe/aSnTlt6zdkWerlah4uy2B2+mUV/lal1O4pZVHEh9h9ux58wmeuJ"
+ "arXFIGYVcV/OV6x0Q4mbUATY/A+8Tyj8pZxOzVQjjq1cxdOHLISlytXrIE2v3BVvMl8ncbMY"
+ "LkvkmSf/cAczCiXd5FlzN6B6jimKawP6sxclRQD+xfBFGYVLwujATL3imPfG2nrPpwDdIc4u"
+ "MKl0Fr+ssfgPoaK8NZ3KeuyxW1LiQ7wSXTado8MxdQuj6vHVpoMCFSZK0300W8rZ3aj5gAxT"
+ "5txCTjiFBGgnfnuu48C8O/ledd27Zi1fPX3RZ+gorXlGfdfW8yfurc+p/Jo/7XQ/QvqKu5pC"
+ "7ybzUDGNPYapaxyrzaS2tZmPwXuHeFGBaYuuLwdd06yvqtWtS6SiCjWdigsteqP0xTOTORrx"
+ "45i4tlDSxv9n2FY6KFP7IFZQL/GHbsMLfLBBysPvZhCX5Wqtf6VMJTJzORqWBmYhShQ1xVk2"
+ "bSD5niJandSsUkOfu2jAT/5+v87O2sajszJVlGMBGzQUsDqTnIPV78uuP5C998ACw2R2P9kA"
+ "LvBSwAh2gMumL6EOOhNTWTkWVKFPBdVjWcnVBUZhqdhcSOp1nbBG3CRaWYODD+LjRk1qNV+g"
+ "kzHVkWOlmeLpuluJ46PM58HJajOrp4WjaTnmi8pKNO3iNrWhEKZqHyh+ayP14/0rbwQYjiV6"
+ "mCdrAWKOGPjTgT56nzaGqbFQK3D8G4dU9oBtjqjllqFUmix1ZG+G5rol2XV5mk6uPCpBxj8R"
+ "pyo4uVrTsvac1rD7TRnVMKFYI3zXiMhNZfUJqNkor4UWeXrAwHN53KVtIe+J6PBMheXYBz71"
+ "ST/8UVgpWtsiOLulu4J+lm5CkrLXeTljO4MjAgp/p6qWHk4Ky50UNe5ceaHe9FnniP4p/Avk"
+ "XyrX//z54WKhcHRuzrekPx2aqa8cgx7q6tbqjErO/WNIgxlPMJ3R90VRDZ2I/FqUUdqiD96h"
+ "q02g5Gs1ZrSLOxHODEIgcPtBkCmoMzWOTspUVI6RJg0dN5CYB7v1lmTPp9HT6vcoXDf38Ge1"
+ "j9v5235uxWq/pd8VYD7T13l5be957z/vSErONwXvDwkUzlqSMPp+5QvJOD+/m07M1F6OdSyf"
+ "tOEIwOJjX2Eg6W0UvLoM1s0x3HyHxhDL1EKO9cIThnrhWhNEmbhIr7YrAU3k7u4WojI5YRhQ"
+ "6YLRAWi3ko/dFVd6oKbxg91MEK0zw2DjleD3R/+aSC9YY5maaPBc3PsR7IS4+QeGHbBsS4Ym"
+ "S9j7BmDQGuN2McQHe53OwNQwAivD8X9tnWuPYYztPrtE1fpXZ02LJ9IyDr3WQkjlZdkmvuiQ"
+ "kgTTU3gQCPUZc6shbtIYOtuz3OQanm3EEJvajc3GY4H/8vGhmYlx9IyLZWoUJytBK7iy1x6B"
+ "zT2cUh30xz0cN8QTkfOSQO/0kcud+sZ8SId5llP7xzAtP97ytQdjew7unlhE4p0QdZStURJ3"
+ "scFzMq1r1VI3VOKGXkXxdyGwhZLkYdNuvEGnY6oDxybLpnuoFlc1B9VFeSLBUvSD1fgoOZlv"
+ "gtwmaPNNnJQjTikpTpZEDWOB4orgyR8LUr+iEzLNfr6V8HRC/Gh7hMFD+33VOlk0vn0BsUWY"
+ "dl/Ak7SR+DF13SdqHQR/Fg7m4H4j/P82ir77j32WVhejkdMdY1NyvsYcJepqQDQr3ZDeHFVN"
+ "ottGbiBfc9Qb4fqgCp/pIqB9IaAtMOW2gE74LIf0w6+8MNuVV5xOFHxB5u9QZ5fAPEhthEW/"
+ "cndfFyBCj8Flt6xB8tO6T/voZz9xz3Ln6jGaxDQDtYcmHuollelgaq2mN/NyI8nLobAoSslm"
+ "g/90eTi7JX+0jc7zLFeuhp9MqU2euicatPkadbY/t+x7J4+eV/FoIyG1TLJJqOHwRRPatJiy"
+ "VOtfugi/O8ey9+OJg7tiN0XTQZkGv6sVNLQ9MMse6FCDtaLOLITNYk3v3q5yhNL5a67noM0L"
+ "Upq4qMpn+ghwlRMakXKqposOxjT1HbOiNXMp/eDFvmvZqvoCMmFmI9hclgwMWo/pr865sIwO"
+ "83wE4DskgDIdhJbOFqM+OxN+07MpAQg5O7L16JwtA3QWpvnv2NIaOv0oqxvgwg+Fms7GLkD1"
+ "cjSdFmOHoTuMRIEBTmVdUWzojFIcOiN8PH8P5oZeWztmDf1kIY6pCLhWzIv3+H+xh2nt7jac"
+ "PUme1CPjlxKGChV4uGFkYXt6/kw6BtO0H2nFuP/FbMWwaT/Y+w4v8q+MR8aiH3w5Dd/eXavN"
+ "L5EtazpxTXV6FPimY2Kq+na/kM71LA2o4T93ATYNKPfvzRQZQOUfrtAZvDW9l0W90ItvgskL"
+ "ww4euaKU0omYpjtiJRo38s784dNdbYmsEEKaQ+JZKu1RqMpVscEIafrc0asChTO+ypLu1ZKD"
+ "3Ia0cTQ0KdN0d2ywwp8arA+z23XZ0qJynQHW9IprzrprevNQ15ZsKUHlm4Q+LBC1M/Qfv4Zl"
+ "0MNe+nzC3vaQyn2gkiho0eYrOZTRG7mfnu5f1S9BXZsIQ/UHoLwjiB958usHdLBnOdoYvtTB"
+ "bEud5mg8H8dHLSGk18h8f9/5J3e5gJ8TA6CZGyPpOSplGuqOAY2zBXKNxZsWE/VLiPo8oiaP"
+ "IJYSubmEgSQzuWmxxAjkTTCzddRH+x9ceoFOxzTlHYusQGtkgdJutDniLtQTr+FwOPj7qgrU"
+ "P8KYogA9GR4fR/Qlv0RHej5hj9kurU/kWrIFCjy401WzYJSlChwstEK+CiXXRT+6tvw/QM+n"
+ "vh9vCxQRpzrb54405OEJtVp9AZfKcdG0jETEY9ASI6iTz7ZEBfZy2D8iN4XQ7zRLmea9h5XP"
+ "e2lUiT0++VDew3B4U1GtWt03Cqb6THyk4WEcnpATRxTsmEfqQwSV4mLCoL8FUw1ChbEXplpq"
+ "pWcgcPU92cLaoHZ66kqf5Z7u8KmL26buvTi8PoMoWEbqD6t0FFcjJu7WqXRzIGqiJelYyB3B"
+ "lu9A38qQNL/GYPqGTspUC45lr8w2e2O+wfE4y06Oyje6Ur3xSPUDvOhrqK46BpGruoRUrRxR"
+ "mnEje3ebEpWZs3PNMMCPh8VdO6faTcONf5brucOPZ7TtePqJiK4cYkseibeodBkQVS1E5HK8"
+ "yETqDTDVAyMDltoWpHiBK28E33pdtG4SHZKpMBwrJ32s5WR+aTaH8xVIfrHVHRGillVdHCFU"
+ "izek0e8XxjNVBGRFuXy78Jw9FOEfKLkExonGMBB2CFZPeHU5/RgmnqkR2Nbny44f2mzv+dbT"
+ "d5IgcQj2twzFdFjD0ghB7nLX6TcbX6EfG8Q/y72n4ZPN1ybZ5BMMquyTfM5EPuuUyQzAJWP4"
+ "xunS9L22LP8FbV4rjQ==";
+
+static const char *l_strdata_1 =
+ "eJy1nAdUE1n7hyckJICBhCIEKQlFQEVJRCmCJEBoNooNG4YioqKCCuKKKQRCF0SKWAARFEUE"
+ "xFXswQRBRQHLisoqAdsqSmLBKIH546qB7z9fzsfsOetRgzk5Z55n5s79vfedG9X81sUFk5as"
+ "2bJ13eZNpOlqC2I2hqzZQtocTopaF0eaTXKcqabmtlnBR0I2x63ZOvwhstr3T68gr5pFitvy"
+ "4x1r0o6fP6ml+i3wUleboAYAgLqPNz1g+PX7z5aI4b8A453PpcMvqlHey7YCv34dCrGYPvyi"
+ "4UN3XaRSGCplsTnvVYC8R/QJoVeEEd8/4eOxgH7KjZHw93EpI8elTCePHPr7P/7b0ZXlR++L"
+ "uhozcvT6jO9/EC39F22G37T4dfQGMVeEcEciUTP4wRg1ZwTPnsBkB2MW7cLk4pIBxjiCxHnX"
+ "xUwI1XSYVOPkVJLzWdcVUZF+UYHs4CtKSCoSSUSmoEq/Rp0BEtlqFZnAutNOhel+4X0QHluY"
+ "POPlPM1PXQ4p4qH94Nl4+7A6YIL3aLCsLlu4ckWK9qc5yqiPjWjvJYBqSrt737VdNlN7Td9l"
+ "dVhwbLXvGjmf0jFaP97R5uMVAx/tFdUt7CM2EN4ZMHm15byN/AWPFfEG/+D1uF2rzibpJIps"
+ "dfMzALMIL9eoPPu0Q+VTHp2/PnclH+Fd5IXQWR3zdWvNqvbcZRJXiWfDXtRa3ZXPJ7+/7WZr"
+ "tCDa96abH54xhZH6oeuAjneX2UDmxsEPPRfr3mPK9R3/ipg5NxIiNBOmEFEu9Ex2apEiobU/"
+ "hJLu/C2EaqNyzJotLhTm5mfw7QJOJ7QnLHFf4+aAMz1jV33h4kGgWanM+iLztINHRirxZtLu"
+ "vsDTj7UKUyduumsYqKI1wXdC82XV7h0601yj1J/V5mkVTokMO+QZUD0oUXrbzJkk3fB8IkTL"
+ "DqaWkVzriSpznSKt+B9aMXdMNK7RdDzaCxYtYqa/tYtapZROYyxzO5C2qRKFuEVgIO/dv2Sx"
+ "t3BILwfNdXyTlh6gJnkx7TfL1YfMmw8tbgnR1IsOeJhydhtjHMnuzpMgX+dIi+vtta6ddKmD"
+ "pLfG4k3YgljeSs1dbkIdS9IiEbB8VverPpwBX7L9FWp2+SuIqz1MVxW564TdMxoVuZr/uqdb"
+ "xQUihMCdFN8StU/kgO+3bVlbs7ZirdR3FgqYamtWfuyb7ScIk8O/Ms+4jswzIlCJB6phQS3C"
+ "kDNDKmGLzAiz0UcEOMJuKiMi2YTZmWKU0TUZx20P3beO2mAB+gCPPqxGADdT7PqRemmmEGDH"
+ "fwXYbdTECCLRXzHYV0SyIYsvliIEXuRvCFdVLIvtjeARCEMiGm1S4mlUtmeKKv4e+7oL5gDu"
+ "zJohJLDzuV0O5URxKDRgyDCRNeTIetn0BEXIDnLkZETiesLQDXJnXfDrumBGZXDEiWBpG1/M"
+ "5YhckSnqOoVrBeJWdBm6QutEUjwK6EmibnUGU59BQeFG4dgmeYIcFMQgXeOLHLBI4QlLhg2w"
+ "8MC61q5jhwAoCNz0G9udYiK/U2izZA1itsgE7xLu0BJ18DBR5YV+r+t0oLnCLOJ312ONUCK4"
+ "+YeVE2mvyb2qiEhbfmo4HGeUslLYa2UApHhUPZx8ejyUAW6mja1SGS+vVGizDjbwGwAlo9fT"
+ "gW0SU33TI4OlUAq4QaQlp5isNPG5Ioo5P5NV0KDKJ+FR/HfCgDN1uQzVdY3FSdLmFTJC9kXr"
+ "Tjc0epPyJnax1vb0hXo4Zv2GB2XnGz6tjN4Zv+ND9+FVlU8POtSB1BybINvHXP0FUHS4YTO2"
+ "omDOyPhGILFgMJMv7hEhea1UY7IzuIcfJctScnci2JI/ISlUFdDMuE7wmUtuvoTiCWh1RDCe"
+ "LxZR2lyYiX8GkoHOqLmiW/bBflB0uNmBl6NP+/L5kSJ0azl6MSLxpa1s71opqTWKVsSZXxRc"
+ "7JwyV/Vbw50oNgex3mGWElD5ZHo9HftgMxQObojoyOEsDRcfUQQX8jPEh4stmh9BKLk6Z0Pu"
+ "OU7eU3QkA1V5iX54hlIqfTCVR3dObjv/fOlTI8qEgtkzrSnoDIu45R+3VV78fPFIvlQi/Fx6"
+ "6ob1kTMBPlPPR+s8ftBvkT4vDFXy/MbAYA/KdOryc7r3vg1AleDGzNhGOeOXEkWNTQaQ/BTu"
+ "Ed/1rKNdXa+1v+kgB1MObGgsTwi4l8blCub7qASYnu7AH7bITxObH2WITkdQ5k6+/2dYQV69"
+ "57v71Hx7zN2vD8brWoQn3mOK6c/e3HJ+Rt3kEr6ommzU9AAiNB1uCI3tGvmOjH0kkgAC3Tia"
+ "DJ+CU4o2w9ugQjxIC2R8MS6Rs4XFllriQXR3K01mlFKMlOywKsIJ1bFkEkDBGdnrkGWIm9fe"
+ "Xd+NuPcn42PkeJIDlB9uNo3t3l0iH2OqbBqe3o5D54tiQ6Y8LKxS0WWMM7MkrK+IijuVQipt"
+ "mMvj+N7pPsN4bfLNbtck4WucJiFsXiXFsvnc4O2Kve1lXfSrqdPSzn4Kj/mdZXjFZ/GZUxJn"
+ "OlQDbrKNTWP1r3XUz3XJs7Sc8Z3CxR3Ca3RMXVTm2kDASc3UmHdd2H1rfEGnf6VkcgjngtXl"
+ "aF/PwsyT73MW9m/agA5tZPOzPVeil/LtvxTsrcrQcjAPN/N4d3FosA9Rd9ff9XYUzx/qAzcX"
+ "x+azdPSUqmI75M9gA8rks2ABnwFiEonkDjCO7wdqV7y3xVPR3a9o8YYprchoKv6Wu5i6k3Rq"
+ "uG7DpLBkyV3fR2Mazj3aCV/n/q6Ri+DkLPc81+v4F9Tj31kvrvl1XS6rssl45S5L3Xz9hox8"
+ "o4M0a+u6nFV/1GcrBxRFEYqTUA3dPV3Mp4dnNtzydL2dUJpQhrgY84Q7+FnHMJ1m8T5H46SK"
+ "VbLXCWN/TgQ/fpdjT4Z++77bGSvj3n7rI9q/0Q74VvLxpRfU6t/J6tBfN42JBtcPjxZFzzQP"
+ "NHxIu3JAM6GiN4+pc3opm4/gV5H+fD/FIUDySL/TIzY3ZLXwdHq78N5d3x1hnc699ze3VZUo"
+ "HZ0cPP/a1PRNZ+I12j9TLgmP6f6RZlLU93awSc9C1deVm73ivzjBDfHvVZDF307m1pFtipwM"
+ "5VUQ2yiWHEUmA9EY3fP6DEf0NkcgoctonW6Ezm0ozD9Z0v2Aab/7iqEIxkpeqLKNmGFxAI9Y"
+ "2zwIpFFR15qwyfq7bT8hDztzZwtYQGSJifhjydGNUDC4kTxeDoZMeOegCCzgZ7vg+zxDxiNp"
+ "cye5FWgGcttf+wlD9Ceaxf9lmlaLmjnp1I5HTG/h8kBbWii3mz2Bot3g9im94s6aKbFvL0dv"
+ "t7i4uqoY/+ch6dnuvlfI5TEh47xa++2gCnAjeGwKOqNXI+cJ6Oa8Ji5ifH1YVYVZvi4EwhZu"
+ "bI4Nwm80hMlKnPADrUVDmEmT8YcwAhyzxNAWTxzOUl0sCEYBD6YD0UgByKJ9S0b5nVI6+RLn"
+ "9w5TUoSkXW6hteCE/dJ3KGBP62pzin9oNlQAbm7i5AK2GjL9/y2QhUgx7qAYdoVGMEL3tQoZ"
+ "LCDxDZEsSy5xx4JZFFlfqNSmSdy6W+RrJMhiYYkgJZ53uBgkMLnqCNOS+8mzrL4htOkeQBvR"
+ "5e6Q60Z7qADcxDSSC+AXV7UoEkj5IbDmR4coUeQzObt1Gvra1CUzRNadpWnqx60S2B7ZUXl5"
+ "b39Lmo//vavoTc2uKfh9vndkpOMVYZNK/FsqK9pKjFwOzg4qE9CLKDmq5+JT4vovJnzSP5AU"
+ "uwGrz1ydKg72VvaJcn0f5SWJO3NXdOGK1fWXOm8rbl8tMLt4ZuutCterV1hXIs+u+fp6ILka"
+ "Kg43Wkly8czAMroi8c0/xHfdOTx8C+ug2ubivT1fWxlVKXHCUEs4ZGAKDd+hNc9nP3ge58pw"
+ "kNx/scUPPblBbPNol/d4x8zL5sm1dmGVmXvmcWuzYy0OiUwlFBrbAKd6wyx5nkNmWXfUiUUd"
+ "izfP+TRZuVIz7tRgNGLjh7iyhfhBaIfbFm7k6sn9MNtnKyvymzdyaxGHSwdyB4JCFCRTI7HE"
+ "IXtXiYl5PDZlgB1njH9Cia+RNok5rho+s71vI0hMtrKgJQJ54lVoHHCmkoMIsdHAAmUhKzYc"
+ "M5/SDoWHm6wGcnijuX+mK4IP+XVxhstR8vCo1E7P3c2wuzF5UylDNGF3aX3zVm5HefbBPmVN"
+ "6zNfT82SugbwA6cgZ7WpRvGwZUXGmLw/OzaFz+u3cjjytfGyPvEPDCWkK+G+VvPiD20PSLUn"
+ "7n5pUJ7Vuc0qwzkhCaoEN1iN5UoDIbqtipQCR64HSESTG6TsQRwSiewh8nCgwXq2EGdAknih"
+ "vWU9CC1wGY0+7mAEWxsj1lKveMxHdTjX3CB145pLcaQpBkNkQwZjSNDGJ+FixgcpAVMe78pq"
+ "qoxLhorADeWxDSzn/zewyJQZgCQZTGZLtbWG0EIwW+yMCs/mdv3muCha5zO1TWzJQ9J20vob"
+ "eq8nIDqdVjFyhX+8g8L+O0FtNzpgVLg0JJ4lfQYIcdnicaQhTuJaEpmLQj2cW5J2pKJJiOnl"
+ "9Evcexs5iLOVQbwbOZz5UEy4YTy2wSGfjEw0Er+Pd5OU/RY7Ey+r+FR7JvB7y1WUBJr4+fiO"
+ "HNJk7+QdKp0hLvZ7iea+5MWlHtzunCb9NC8LVscN/UrKBO3zJecaM21dqh02JafgZz1oOWLL"
+ "S44oI31Ymjt5J3598MnFMV8GRUrP66IHik99PAbxmwE3503kfiH7W39T5Cfv0eJQ6lh8Dmd4"
+ "kYx058qIPPTw6AfruDdiT/ug0bEOPqmO3s8RA6wM8nbTi4Y2qkBJYcaa+j2Fk6CgcPMcKwft"
+ "cPSaoAjUeKQRiUhhl7iyRQLMicNINMnWhHfEFAiSujYrP354EooDN5015DidLQWWinAmjupt"
+ "p0wgEIxryLLaw4gTiCNUbB6LQADJZBABlNfY3DdZSBdAqeBG59hqhvW/nipN1aL5EZJEtmZt"
+ "mx5EutdNVZ9SMzEnYlx4ZNf61fU934I8Aopimblb7S5bBZbX1i6neG1d0MHgXT/vZKm+e4dp"
+ "UNQ1DdtcP74aRmjv6qp2JvfTeHKDklfrpsryjwZqPt9ODUoAi93MWcTY3ntQNbipSZCr9ceH"
+ "pyhSmzIyX1CHJzcxuDYiG2wVi6i/S4ikodQEIpk8fQZAadlkTQDuukXoLXB/vhbKBjcULeVs"
+ "mxOvzlbExvw1SVDUlCgEusg1/d1KIbJ3pg7dRtevI8Qg2KZrDX1uHwOFy54+zqRHppqWVy19"
+ "gzmRMVljHt//Dr4/w0m4vkkcvf6vdJtHYgvM0zWtey99sLZptDx3V8/91Kx7he0Tt248Oj96"
+ "3cvF0ZtOf63NKwpdCfz5+vCrpi+y6s2qKWmhl4MeJkB14QamqVw3tWrbDkW6G+Q1wI9eTkGg"
+ "X4AD4D782xGwsEZHa2rpkWobEsXPjSjLdzRuf+fbqRfRkZViZxl+a21JSIlu9eEEY899DONZ"
+ "tYG1E2r/WrqkTaByr+lS2hrNt9Zn/8q7mProywKB0/6sY7KHBspFhYFTN1OgfZ0ZcDOUKHfT"
+ "Kdkl+R/zfcywG5cM0NvTTUXTItn2wTOytSZtK7zBzj0pClOao5dPtYue+/GC0y6ET9ze9omT"
+ "zm7yI7hXZ3b8Nc6+qu01cvv8vPKu+0tkjkc/TLEG3yGfvvh0XD/oQ9s6g4XVjJ6SiPh158MP"
+ "3jbcZdl7KTom4MJ9AhrqBzd2x1YjuP+/GqGZQu6QUOKlz9DC4ToBwFP5Ca5bmtj1WSIB1ZGH"
+ "w7whuHSCmOfaWbgO5Lue5I50JBIwOB86J+pQvBWUGW4Gj+2axP7sUQ1fk2tkgrIIWVZvZmB+"
+ "IOwMytW1aZ6J95mTXWTOXL381TLuOLX9vhfeZ+fu2a4cW3j/jll0GtL1eGjebuey6w5bq6yv"
+ "z1shYUVfvZSZNPfFjVUXdkkCvPZ9oPXe895Sedmw94QN3Sw+59pbVECH3pY6RBBzU8VUMAk6"
+ "9GbCjeKxldarRoYem4xF8fusZivNc/N0T0n4XWuN5lpkwST89d30u7Stt+eCuMVrZrAfBVry"
+ "XovCLB1fcDsdjaaHkGMrtWLLavUfcG+9eXFUz8j9uCCDEIaoUOp5TMRpbdw5cPzrq1SoDdy8"
+ "1pHb9NJ4aEU2QaMavmQsvT3LymqveqCJQVh5Bq1pubd1t+bKADrFghRhFbTjC2Xh4wpDHbr2"
+ "zvknY8fZvDva1JVfEmnl9mGjEHXywsvevRe9N+05/3Xp1IzSrvmSrEqwBzXtotdf5OL2E1Ad"
+ "uHk/tvix/I/4EbB3fSSiBwQVUlAE3m+kSbkNRYhwe10sQJoV9CK40fQUlOvfWSx7jNRvWEYC"
+ "Z3jlIkAMUTOwxDdg8Vqp69bSYlHyIPiV5FYkypJRd/KoLcQ3+kMUMmAk5KuYG9e4H+IND8iK"
+ "hu3dT984MaHUcMN8bFX1xl9D/e+uM09UjbeuY2d7JFpb0GuisMm1NduUG6/ZhWy9tuejzCc4"
+ "+GOujQ09MRqpzX4cwPMpo9VLauNJxCnZPWq9+xryg0pn69Sta+ydw2yfpld5FzuHavFyGmXZ"
+ "xHrcc2rU8oEeVBtuZ9YJ3EwZ1A5uOTBJbhc9P2u3Irvs73aEvKRWEw0lV6xymxbKtSopLOe8"
+ "yqPz+hvxt+p/V9E6dbqBdP6aFir/9gSU6N4CH7zK5KG9YAHDMrcnZR49mn3Uyu1+T4K/2tLD"
+ "Tr8dE1Tx0dvjtX6Lio39K7J33KSyPwIbuW11pw59Tp48mVNp/knNyrqNMHfGyZZaf3JD+771"
+ "h4/cI9+jrD67IF2kB/ZRN3uzDy5U/tC4FHoC4BYIY5vJYn4VCJdVBWRCoujtovWoddqZR9lP"
+ "aHbeS6KZ/A3xFuPi3y9SWb2AcW5d0cukHtpdWn9GoVH9/XzRA/WqSF7RFd7sqaZq1w+o4Cjj"
+ "JRNaFhN8LxV1VBQ3zEzdqePTOzHi/AJMpq/A0/Yk1362WRtneIZjvkiOuZKhe3I61BJuqWAo"
+ "twQLrn9QZPnzIcOhH30qj/ZX2e/QqzjqF7QMZlhdZ+gm0GKTZvVa7tPa78JU1uU4fniZUNZl"
+ "MdVzCXBSN7U8OXE6acDaaiig06Ti/aKWaSWPcifZha+JudmdpxOUeNW7KmHBug3FzvQgDu5W"
+ "3kMdqBPc8gBWeyfmzsXvTu7+23P1TSJzFnegyublzzcev/Gmjn8Gkegfnb5wOdpHkOSaqu/0"
+ "NoMboxle6pz9qFCoUtpUkE9flfT16XsdS6e9hIbUUqep0e6rkiRoK0M+71zfY6Llbua2yOCK"
+ "6F6oEtzqQVWu5ILPCFKkpPtzhmSwEf1oNHO5aSw5hAEAX9S0Xq0MnmgJwbCDm+5jeyTl/QNj"
+ "pqBWnU/Comibr1nVLPNkoJYcDsWn3U/heat8zKdaHtKp0iJv7bmjjWHWx1GH6NV2US+v28Qe"
+ "dIiTLBgK1Fy8AtTLZAXZOpzeth8KDjfIv5+/yX+D+9ydN1ERuL78/HFwzRWcEyo3wvARDCnO"
+ "AzhLpzytzo73hJLAzeDxcpJttKnuikjcRreMCF+QFCcUGIoOeUHEstRMpE04/CmGVIpJAZeR"
+ "ndEiUAvbxKkfZGHcqfhbIJUP2Kd/IwDbZ2yeEXOwBRrPdv9k+/D/RtYehSxg8Mlsd2PAOi42"
+ "urxA6z6UAW7YqsgZjuItDRQxyFsV37dfYgRevFZslr6GreHaCIxbLJrJ+40HPncDHr2c2KJq"
+ "d2EvlApuSGrLqXYErUIpovIc/QB9khQzgORhWnCExwZE8ifq910a0YRNwE1b4AWnt4fWqSGU"
+ "kWVAwiNLZ0aEXRw/qoTNEZlI+pUA6e/rdI/trVGBYsONNl05dvH2fJoi7Lkj2DgBhhQOdKOt"
+ "DCyH/ARiERsrSEYb83AfAwku95ESEG3lwkaeo0Xj8MjPfzwhO2d/FYgTJTjBzpdULGkglQ/U"
+ "+m3AHptWS4Cy/5O9VD/Yc9a9xChinz2yl4pD+tb09/bLHgyviUXYDQZHgIK24feJ42u5/gMs"
+ "3ksQiwEpzhUMKb9ZnKx/9QawodWDLx7Yfw1K+09Wqj9oo34fZCqiDRi1UhWAYDwWZNEkLOf6"
+ "wiwREUkUZOHiscWgmOIMNjVFGVo91RAO0lqylOvco538Ntr6tTYUx2NxaY8IzBpbsmwP5tF8"
+ "HTRwbf3CuxsKHFygCv9kM9UPhfyDdrPGMMaB4TGO+4jg4XA4svNQNFskxWF4saT3oIDPxzWS"
+ "+LhFGEGv3/A4v02LFxcjBVy1Ev0MWl1H6lk9oPrQ6oqGb9aDEGx7uFGlLsemOfauVoQ9Q47N"
+ "FbGQAhaax8JiWS5kmaxBrJlhXFqKNMRiDV8nxLVxiFisFpYkkJgAjm+9d1ZbnITu37aHG0tj"
+ "uxF/Vl95LbWqSWQdT5EZKhKlF6XjVbewebnLaW/zcahIU1nc5UNnq6U3t5K1QT/L9bwA5PGp"
+ "Qjvtz8t/r7HbM21PesSR+ktOq6Kp52z36Fegbhql6BI2DhBebzR9anuD5fUA6aC36g8HXAy0"
+ "orT/J4vMH06cK0c/KnKijVpkCrxI4ahuTxIzBUetWBsXRysSBSOpAnd9Jg/zuReLG6pDvnjs"
+ "cw0zZG9LbrYns3sttJBAaU1El+tiJS4UGG68jZMDz7c8SFAEPHVkUzoixZjKcFBxz2owjfr0"
+ "BSEQYLAZjE/ZEdu6EEi8jMUmIoBsba+mrKCq/3I64Qbf2CqXab/onvEBOnUCwYVMa8fVqChx"
+ "lA0IBA2g3IpOWzH8bkJ7D5rXjwYqH0+LdaoBD0Dx4Cbg2EZw4OgocSeFJ3YnJ4qpurxkVhjB"
+ "eKgiOE6q2czCCps4cS7DFUxwHCDEfeXUG6XgkBJ30pAYU2pc1kfECTggFovtV8eCYF8w8MgX"
+ "hQYev9gw84qhihQqAjcTxzYKjEZ9l0KERHuSf/Oe/fdlB9Sb0gH7Pq9oe1oEC0oDN+UmymkM"
+ "66p3KqJxHemIUAlWJuCuirVgg1jEMerB8XjI6VjaFo0mZIn37I/JaoSvVwcxM5sxMS1UawLB"
+ "nJ3v9zycrApor6559IIXBH0GZw836cZ22zuM7i3VSvu4v/GSMc/1CQZS/QlrpPrG9+tsnaWt"
+ "d8RilpOPlA+Go9Ff0aXvlEMAXt96gvJ5P+gDZnu4eaYjBz2Q4LlLEajHCChyOM+IHzE8ItFl"
+ "OM/8v+8hR7gPUBGkcEYXRgTu22eUocx/sx/UJxuiu5NZagSXRUyGVDDvzGXA7QqjvbeGDN1I"
+ "4wA3zsZWSMwZ3fKulbZinHgY3PPhGUBMmdESGir1eyYUi0CEuQxQpyp1u9PisWkD7N96CS7N"
+ "DyjxDGmT+LB+Cqh9Xb0fBGZu3Gh4zsPsKhQdbsiNDf0/toq01381FlDRLjwqVUpwGdoTGjfk"
+ "u1vkgD/3fV/px9CGXj9swD3s/a3iZEMB12sLlih9TmB23KDIQC/UUAMKwHUu/oBZZYuFwsNN"
+ "M5wcnt7iHKIInjxqBxb120CKSIRAX0ejPdE8jDIWx6Q4D5/abPwsvyY+yYv/fLhEWlg35wJC"
+ "tP8CFBBueo2tHvb8j3qYf0csJQu+tmIH8Z+KDw9S3d1nCxDDYduCpRL0h4aLSBa/BkQSdoV1"
+ "gNmuQxTkTqoAsVNmgQSSTnrq334xngTFhhtrWDl29UBysCLska9EcBD6g4N8Md5gFh9QCiwn"
+ "t3ORWByK4EIgy6wYNFvieATwLtXHIP2KeTkUDm6ojW0us//PuewZ2DdcqpwbPnvBayO+r+rs"
+ "JISKISL+yRlbGSMyXCro5IuzDkVcRICZkf4FbeNOQznhZtbY7izP0XfWpH0NPI6oC3ThJTOc"
+ "s0RIwhNKfMW5UKn/Usx1Vu8QRVaLCClkYUFzzj7tigKDlMLhGFYlWj4OArZ3RIa927aIDMWG"
+ "G26mcuyhzWeUFGGvGwk3zHC4GfR+IUYxGDUFmxkMB+nqIQGClwy+oCrdYMloHwTUoVbaB5zT"
+ "vkgWky3yUt930jneeIEuFZXDyiDeweTcZAQMCoaQWLCD2oH0Yap8PWIcYZaYC3JJN7Y7Apkx"
+ "x8t316+sgJr9k+/S/O9sWf7DLPP79xzIBLrIUjc/Gjl3xfYtHDbXITdpg5XuWYydqszccyWp"
+ "sWmDxu6DHlUXIxb6hhbdE2om6WqGB4eK6BNu9m/4UjdfuNA5ud68v2uZINH2BCtdb+f6DeLc"
+ "1T2dUBW4MTm2sfXzuw55f6voeLRjluwmOcxJOTAxrWSVZ9viXutVND/7mjPZ4K52a0b0zVtJ"
+ "J7pwBMZdjWR076yu+GUriwyXJTtNV5p0nKH3Itd/7uzbZ8wNMn0+a9oEHn9Z578DEFWtRcce"
+ "OzcV4uMIN0DH1qpZ+MuHoskm4+ntz/xNOVsTxh/kz1MLQ02iI3Q1MJMWhjn84Zb2UBZSoLJW"
+ "mCmZYfZw/kHZCT2fLTd749PvlVeddpqp4TxtDsHn6KaSQ5xnRGdS+KvGnkNGUAe4STq2Jtio"
+ "7yBzRWaCUGexg2AHegePScr0feDFK8aCJnjgc7f5qcUdpXOgUHATUkkOVTv93F1FUL/+AwhG"
+ "iYohH41YoaPiKgaM9kEPDzf/xrY+sh5ZH7U940dd4ogo7a+DkYImNI+KxVL52O6Zxpgni5/x"
+ "xcQ0gMhwtK3GZqlD4eCm3Ng6p5NGN3vLpQIQg8U9x5ENQ98zXm/OZmSzGA0MX+Wte8jA0qYY"
+ "7NlEpu9osv8DuJwRWA==";
+
+#endif /* LEPTONICA_AUTOGEN_137_H */
diff --git a/leptonica/prog/autogentest1.c b/leptonica/prog/autogentest1.c
new file mode 100644
index 00000000..f7c687d7
--- /dev/null
+++ b/leptonica/prog/autogentest1.c
@@ -0,0 +1,80 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * autogentest1.c
+ *
+ * This makes /tmp/lept/auto/autogen.137.c and /tmp/lept/auto/autogen.137.h.
+ * It shows how to use the stringcode facility.
+ *
+ * In general use, you compile and run the code generator before
+ * compiling and running the generated code, in autogentest2.c.
+ *
+ * But here, because we compile both autogentest1.c and autogentest2.c
+ * at the same time, it is necessary to put the generated code
+ * in this directory. Running autogentest1 will simply regenerate
+ * this code, but in the /tmp/lept/auto/ directory.
+ *
+ * As part of the test, this makes /tmp/lept/auto/autogen.138.c and
+ * /tmp/lept/auto/autogen.138.h, which contain the same data, using
+ * the function strcodeCreateFromFile(). With this method, you do not
+ * need to specify the file type (e.g., "PIXA")
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+#include <string.h>
+
+static const char *files[2] = { "fonts/chars-6.pa", "fonts/chars-10.pa" };
+
+static const char *filetext = "# testnames\n"
+ "fonts/chars-6.pa\n"
+ "fonts/chars-10.pa";
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i;
+L_STRCODE *strc;
+
+ setLeptDebugOK(1);
+
+ /* Method 1: generate autogen.137.c and autogen.137.h */
+ strc = strcodeCreate(137);
+ for (i = 0; i < 2; i++)
+ strcodeGenerate(strc, files[i], "PIXA");
+ strcodeFinalize(&strc, NULL);
+
+ /* Method 2: generate autogen.138.c and autogen.138.c */
+ l_binaryWrite("/tmp/lept/auto/fontnames.txt", "w", filetext,
+ strlen(filetext));
+ strcodeCreateFromFile("/tmp/lept/auto/fontnames.txt", 138, NULL);
+ return 0;
+}
+
diff --git a/leptonica/prog/autogentest2.c b/leptonica/prog/autogentest2.c
new file mode 100644
index 00000000..432694d4
--- /dev/null
+++ b/leptonica/prog/autogentest2.c
@@ -0,0 +1,69 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * autogentest2.c
+ *
+ * This is a test of the stringcode utility.
+ *
+ * It uses the files compiled from autogen.137.c and autogen.137.h
+ * to regenerate each of the 2 pixa from the strings in autogen.137.h.
+ * It then writes them to file and compares with the original.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+#include "autogen.137.h" /* this must be included */
+
+ /* Original serialized pixa, that were used by autogentest1.c */
+static const char *files[2] = { "fonts/chars-6.pa", "fonts/chars-10.pa" };
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i, same;
+PIXA *pixa;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/auto");
+
+ for (i = 0; i < 2; i++) {
+ pixa = (PIXA *)l_autodecode_137(i); /* this is the dispatcher */
+ pixaWrite("/tmp/lept/auto/junkpa.pa", pixa);
+ filesAreIdentical("/tmp/lept/auto/junkpa.pa", files[i], &same);
+ if (same)
+ lept_stderr("Files are the same for %s\n", files[i]);
+ else
+ lept_stderr("Error: files are different for %s\n", files[i]);
+ pixaDestroy(&pixa);
+ }
+
+ return 0;
+}
+
diff --git a/leptonica/prog/barcode-128-300.png b/leptonica/prog/barcode-128-300.png
new file mode 100644
index 00000000..a3548236
--- /dev/null
+++ b/leptonica/prog/barcode-128-300.png
Binary files differ
diff --git a/leptonica/prog/barcode-2of5-300.png b/leptonica/prog/barcode-2of5-300.png
new file mode 100644
index 00000000..6e52537c
--- /dev/null
+++ b/leptonica/prog/barcode-2of5-300.png
Binary files differ
diff --git a/leptonica/prog/barcode-39-300.png b/leptonica/prog/barcode-39-300.png
new file mode 100644
index 00000000..dcdb841f
--- /dev/null
+++ b/leptonica/prog/barcode-39-300.png
Binary files differ
diff --git a/leptonica/prog/barcode-93-300.png b/leptonica/prog/barcode-93-300.png
new file mode 100644
index 00000000..f202be10
--- /dev/null
+++ b/leptonica/prog/barcode-93-300.png
Binary files differ
diff --git a/leptonica/prog/barcode-codabar-300.png b/leptonica/prog/barcode-codabar-300.png
new file mode 100644
index 00000000..8f599cae
--- /dev/null
+++ b/leptonica/prog/barcode-codabar-300.png
Binary files differ
diff --git a/leptonica/prog/barcode-digits.png b/leptonica/prog/barcode-digits.png
new file mode 100644
index 00000000..bb3af672
--- /dev/null
+++ b/leptonica/prog/barcode-digits.png
Binary files differ
diff --git a/leptonica/prog/barcode-i2of5-300.png b/leptonica/prog/barcode-i2of5-300.png
new file mode 100644
index 00000000..79ccd4cd
--- /dev/null
+++ b/leptonica/prog/barcode-i2of5-300.png
Binary files differ
diff --git a/leptonica/prog/barcode-upc-300.png b/leptonica/prog/barcode-upc-300.png
new file mode 100644
index 00000000..02e2a94e
--- /dev/null
+++ b/leptonica/prog/barcode-upc-300.png
Binary files differ
diff --git a/leptonica/prog/barcodetest.c b/leptonica/prog/barcodetest.c
new file mode 100644
index 00000000..626e4e01
--- /dev/null
+++ b/leptonica/prog/barcodetest.c
@@ -0,0 +1,90 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * barcodetest.c
+ *
+ * barcodetest filein
+ *
+ * For each barcode in the image, if the barcode format is supported,
+ * this deskews and crops it, and then decodes it twice:
+ * (1) as is (deskewed)
+ * (2) after 180 degree rotation
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+#include "readbarcode.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein;
+PIX *pixs;
+SARRAY *saw1, *saw2, *sad1, *sad2;
+static char mainName[] = "barcodetest";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: barcodetest filein", mainName, 1);
+ filein = argv[1];
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/barc");
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ sad1 = pixProcessBarcodes(pixs, L_BF_ANY, L_USE_WIDTHS, &saw1, 0);
+ sarrayWrite("/tmp/lept/barc/saw1", saw1);
+ sarrayWrite("/tmp/lept/barc/sad1", sad1);
+ sarrayDestroy(&saw1);
+ sarrayDestroy(&sad1);
+
+ pixRotate180(pixs, pixs);
+ sad2 = pixProcessBarcodes(pixs, L_BF_ANY, L_USE_WIDTHS, &saw2, 0);
+ sarrayWrite("/tmp/lept/barc/saw2", saw2);
+ sarrayWrite("/tmp/lept/barc/sad2", sad2);
+ sarrayDestroy(&saw2);
+ sarrayDestroy(&sad2);
+
+/*
+{
+ SARRAY *saw3, *sad3;
+ sad3 = pixProcessBarcodes(pixs, L_BF_ANY, L_USE_WINDOW, &saw3, 1);
+ sarrayWrite("/tmp/lept/barc/saw3", saw3);
+ sarrayWrite("/tmp/lept/barc/sad3", sad3);
+ sarrayDestroy(&saw3);
+ sarrayDestroy(&sad3);
+}
+*/
+
+ pixDestroy(&pixs);
+ return 0;
+}
+
diff --git a/leptonica/prog/barcodetest1.jpg b/leptonica/prog/barcodetest1.jpg
new file mode 100644
index 00000000..2a813734
--- /dev/null
+++ b/leptonica/prog/barcodetest1.jpg
Binary files differ
diff --git a/leptonica/prog/barcodetest2.jpg b/leptonica/prog/barcodetest2.jpg
new file mode 100644
index 00000000..fe361690
--- /dev/null
+++ b/leptonica/prog/barcodetest2.jpg
Binary files differ
diff --git a/leptonica/prog/baseline_reg.c b/leptonica/prog/baseline_reg.c
new file mode 100644
index 00000000..a02de42e
--- /dev/null
+++ b/leptonica/prog/baseline_reg.c
@@ -0,0 +1,135 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * baselinetest.c
+ *
+ * This tests two things:
+ * (1) The ability to find a projective transform that will deskew
+ * textlines in an image with keystoning.
+ * (2) The ability to find baselines in a text image.
+ * (3) The ability to clean background to white in a dark and
+ * mottled text image.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+NUMA *na;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXA *pixadb;
+PTA *pta;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "baseline_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("keystone.png");
+
+ /* Test function for deskewing using projective transform
+ * on linear approximation for local skew angle */
+ pix1 = pixDeskewLocal(pixs, 10, 0, 0, 0.0, 0.0, 0.0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+
+ /* Test function for finding local skew angles */
+ na = pixGetLocalSkewAngles(pixs, 10, 0, 0, 0.0, 0.0, 0.0, NULL, NULL, 1);
+ gplotSimple1(na, GPLOT_PNG, "/tmp/lept/baseline/ang", "Angles in degrees");
+ pix2 = pixRead("/tmp/lept/baseline/ang.png");
+ pix3 = pixRead("/tmp/lept/baseline/skew.png");
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix2, 0, 550, NULL, rp->display);
+ pixDisplayWithTitle(pix3, 700, 550, NULL, rp->display);
+ numaDestroy(&na);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pixs);
+
+ /* Test baseline finder */
+ pixadb = pixaCreate(6);
+ na = pixFindBaselines(pix1, &pta, pixadb);
+ pix2 = pixRead("/tmp/lept/baseline/diff.png");
+ pix3 = pixRead("/tmp/lept/baseline/loc.png");
+ pix4 = pixRead("/tmp/lept/baseline/baselines.png");
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 3 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 4 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix2, 0, 0, NULL, rp->display);
+ pixDisplayWithTitle(pix3, 700, 0, NULL, rp->display);
+ pixDisplayWithTitle(pix4, 1350, 0, NULL, rp->display);
+ pix5 = pixaDisplayTiledInRows(pixadb, 32, 1500, 1.0, 0, 30, 2);
+ pixDisplayWithTitle(pix5, 0, 500, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 6 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixaDestroy(&pixadb);
+ numaDestroy(&na);
+ ptaDestroy(&pta);
+
+ /* Another test for baselines, with dark image */
+ pixadb = pixaCreate(6);
+ pixs = pixRead("pedante.079.jpg"); /* 75 ppi */
+ pix1 = pixRemoveBorder(pixs, 30);
+ pixaAddPix(pixadb, pix1, L_COPY);
+ pix2 = pixConvertRGBToGray(pix1, 0.33, 0.34, 0.33);
+ pix3 = pixScale(pix2, 4.0, 4.0); /* scale up to 300 ppi */
+ pix4 = pixCleanBackgroundToWhite(pix3, NULL, NULL, 1.0, 70, 170);
+ pix5 = pixThresholdToBinary(pix4, 170);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 7 */
+ pixaAddPix(pixadb, pixScale(pix5, 0.25, 0.25), L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pix1 = pixDeskew(pix5, 2);
+ na = pixFindBaselines(pix1, &pta, pixadb);
+ pix2 = pixaDisplayTiledInRows(pixadb, 32, 1500, 1.0, 0, 30, 2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 8 */
+ pixDisplayWithTitle(pix2, 800, 500, NULL, rp->display);
+ pixaDestroy(&pixadb);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix5);
+ numaDestroy(&na);
+ ptaDestroy(&pta);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/bigweasel2.4c.png b/leptonica/prog/bigweasel2.4c.png
new file mode 100644
index 00000000..ce2e27e1
--- /dev/null
+++ b/leptonica/prog/bigweasel2.4c.png
Binary files differ
diff --git a/leptonica/prog/bilateral1_reg.c b/leptonica/prog/bilateral1_reg.c
new file mode 100644
index 00000000..5f76a225
--- /dev/null
+++ b/leptonica/prog/bilateral1_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * bilateral1_reg.c
+ *
+ * Regression test for bilateral (nonlinear) filtering, with both:
+ * (1) Separable results with full res intermediate images
+ * (2) Exact results
+ * This test takes about 30 seconds, so it is not included
+ * in the alltests_reg set.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static void DoTestsOnImage(PIX *pixs, L_REGPARAMS *rp, l_int32 width);
+
+static const l_int32 ncomps = 10;
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("rock.png");
+ DoTestsOnImage(pixs, rp, 2000); /* 0 - 16 */
+ pixDestroy(&pixs);
+
+ pixs = pixRead("church.png");
+ DoTestsOnImage(pixs, rp, 1500); /* 17 - 33 */
+ pixDestroy(&pixs);
+
+ pixs = pixRead("color-wheel-hue.jpg");
+ DoTestsOnImage(pixs, rp, 1000); /* 34 - 50 */
+ pixDestroy(&pixs);
+
+ return regTestCleanup(rp);
+}
+
+
+static void
+DoTestsOnImage(PIX *pixs,
+ L_REGPARAMS *rp,
+ l_int32 width)
+{
+PIX *pix, *pixd;
+PIXA *pixa;
+
+ pixa = pixaCreate(0);
+ pix = pixBilateral(pixs, 5.0, 10.0, ncomps, 1);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 5.0, 20.0, ncomps, 1);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 5.0, 40.0, ncomps, 1);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 5.0, 60.0, ncomps, 1);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 10.0, 10.0, ncomps, 1);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 10.0, 20.0, ncomps, 1);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 10.0, 40.0, ncomps, 1);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 10.0, 60.0, ncomps, 1);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 10.0, 10.0, ncomps, 2);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 10.0, 20.0, ncomps, 2);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 10.0, 40.0, ncomps, 2);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 10.0, 60.0, ncomps, 2);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBlockBilateralExact(pixs, 10.0, 10.0);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBlockBilateralExact(pixs, 10.0, 20.0);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBlockBilateralExact(pixs, 10.0, 40.0);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBlockBilateralExact(pixs, 10.0, 60.0);
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pixd = pixaDisplayTiledInRows(pixa, 32, width, 1.0, 0, 30, 2);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG);
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ return;
+}
+
diff --git a/leptonica/prog/bilateral2_reg.c b/leptonica/prog/bilateral2_reg.c
new file mode 100644
index 00000000..b02dc7d1
--- /dev/null
+++ b/leptonica/prog/bilateral2_reg.c
@@ -0,0 +1,105 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * bilateral2_reg.c
+ *
+ * Regression test for bilateral (nonlinear) filtering.
+ *
+ * Separable operation with intermediate images at 4x reduction.
+ * This speeds the filtering up by about 30x compared to
+ * separable operation with full resolution intermediate images.
+ * Using 4x reduction on intermediates, this runs at about
+ * 3 MPix/sec, with very good quality.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static void DoTestsOnImage(PIX *pixs, L_REGPARAMS *rp);
+
+static const l_int32 ncomps = 10;
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("test24.jpg");
+ DoTestsOnImage(pixs, rp); /* 0 - 7 */
+ pixDestroy(&pixs);
+
+ return regTestCleanup(rp);
+}
+
+
+static void
+DoTestsOnImage(PIX *pixs,
+ L_REGPARAMS *rp)
+{
+PIX *pix, *pixd;
+PIXA *pixa;
+
+ pixa = pixaCreate(0);
+ pix = pixBilateral(pixs, 5.0, 10.0, ncomps, 4); /* 0 */
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 5.0, 20.0, ncomps, 4); /* 1 */
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 5.0, 40.0, ncomps, 4); /* 2 */
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 5.0, 60.0, ncomps, 4); /* 3 */
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 10.0, 10.0, ncomps, 4); /* 4 */
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 10.0, 20.0, ncomps, 4); /* 5 */
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 10.0, 40.0, ncomps, 4); /* 6 */
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = pixBilateral(pixs, 10.0, 60.0, ncomps, 4); /* 7 */
+ regTestWritePixAndCheck(rp, pix, IFF_JFIF_JPEG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pixd = pixaDisplayTiledInRows(pixa, 32, 2500, 1.0, 0, 30, 2);
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ return;
+}
+
+
diff --git a/leptonica/prog/bilinear_reg.c b/leptonica/prog/bilinear_reg.c
new file mode 100644
index 00000000..01aa7ad8
--- /dev/null
+++ b/leptonica/prog/bilinear_reg.c
@@ -0,0 +1,263 @@
+/*====================================================================*
+ - 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_reg.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static void MakePtas(l_int32 i, PTA **pptas, PTA **pptad);
+
+ /* Sample values.
+ * 1: test with relatively large distortion
+ * 2-3: invertability tests
+ */
+static const l_int32 x1[] = { 32, 32, 32};
+static const l_int32 y1[] = { 150, 150, 150};
+static const l_int32 x2[] = { 520, 520, 520};
+static const l_int32 y2[] = { 150, 150, 150};
+static const l_int32 x3[] = { 32, 32, 32};
+static const l_int32 y3[] = { 612, 612, 612};
+static const l_int32 x4[] = { 520, 520, 520};
+static const l_int32 y4[] = { 612, 612, 612};
+
+static const l_int32 xp1[] = { 32, 32, 32};
+static const l_int32 yp1[] = { 150, 150, 150};
+static const l_int32 xp2[] = { 520, 520, 520};
+static const l_int32 yp2[] = { 44, 124, 140};
+static const l_int32 xp3[] = { 32, 32, 32};
+static const l_int32 yp3[] = { 612, 612, 612};
+static const l_int32 xp4[] = { 520, 520, 520};
+static const l_int32 yp4[] = { 694, 624, 622};
+
+#define ALL 1
+#define ADDED_BORDER_PIXELS 250
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pixd;
+PIX *pixb, *pixg, *pixc, *pixcs;
+PIXA *pixa;
+PTA *ptas, *ptad;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn.tif");
+ pixg = pixScaleToGray(pixs, 0.2);
+ pixDestroy(&pixs);
+
+#if ALL
+ /* Test non-invertability of sampling */
+ lept_stderr("Test invertability of sampling\n");
+ pixa = pixaCreate(0);
+ for (i = 1; i < 3; i++) {
+ pixb = pixAddBorder(pixg, ADDED_BORDER_PIXELS, 255);
+ MakePtas(i, &ptas, &ptad);
+ pix1 = pixBilinearSampledPta(pixb, ptad, ptas, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0,3,6 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixBilinearSampledPta(pix1, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1,4,7 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixd = pixRemoveBorder(pix2, ADDED_BORDER_PIXELS);
+ pixInvert(pixd, pixd);
+ pixXor(pixd, pixd, pixg);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 2,5,8 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixb);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ }
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 0.5, 20, 3);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 9 */
+ pixDisplayWithTitle(pix1, 0, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if ALL
+ /* Test invertability of grayscale interpolation */
+ lept_stderr("Test invertability of grayscale interpolation\n");
+ pixa = pixaCreate(0);
+ for (i = 1; i < 3; i++) {
+ pixb = pixAddBorder(pixg, ADDED_BORDER_PIXELS, 255);
+ MakePtas(i, &ptas, &ptad);
+ pix1 = pixBilinearPta(pixb, ptad, ptas, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 10,13 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixBilinearPta(pix1, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 11,14 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixd = pixRemoveBorder(pix2, ADDED_BORDER_PIXELS);
+ pixInvert(pixd, pixd);
+ pixXor(pixd, pixd, pixg);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 12,15 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixb);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ }
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 0.5, 20, 3);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 16 */
+ pixDisplayWithTitle(pix1, 200, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if ALL
+ /* Test invertability of color interpolation */
+ lept_stderr("Test invertability of color interpolation\n");
+ pixa = pixaCreate(0);
+ pixc = pixRead("test24.jpg");
+ pixcs = pixScale(pixc, 0.3, 0.3);
+ for (i = 1; i < 3; i++) {
+ pixb = pixAddBorder(pixcs, ADDED_BORDER_PIXELS / 2, 0xffffff00);
+ MakePtas(i, &ptas, &ptad);
+ pix1 = pixBilinearPta(pixb, ptad, ptas, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 17,20 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixBilinearPta(pix1, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 18,21 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixd = pixRemoveBorder(pix2, ADDED_BORDER_PIXELS / 2);
+ pixXor(pixd, pixd, pixc);
+ pixInvert(pixd, pixd);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 19,22 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixb);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ }
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 0.5, 20, 3);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 23 */
+ pixDisplayWithTitle(pix1, 400, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pixc);
+ pixDestroy(&pixcs);
+ pixaDestroy(&pixa);
+#endif
+
+#if ALL
+ /* Comparison between sampling and interpolated */
+ lept_stderr("Compare sampling with interpolated\n");
+ MakePtas(2, &ptas, &ptad);
+ pixa = pixaCreate(0);
+
+ /* Use sampled transform */
+ pix1 = pixBilinearSampledPta(pixg, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 24 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Use interpolated transforms */
+ pix2 = pixBilinearPta(pixg, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 25 */
+ pixaAddPix(pixa, pix2, L_COPY);
+
+ /* Compare the results */
+ pixXor(pix2, pix2, pix1);
+ pixInvert(pix2, pix2);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 26 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 0.5, 20, 3);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 27 */
+ pixDisplayWithTitle(pix1, 600, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pixg);
+ pixaDestroy(&pixa);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+#endif
+
+#if ALL
+ /* Large distortion with inversion */
+ lept_stderr("Large bilinear distortion with inversion\n");
+ MakePtas(0, &ptas, &ptad);
+ pixa = pixaCreate(0);
+ pixs = pixRead("marge.jpg");
+ pixg = pixConvertTo8(pixs, 0);
+
+ pix1 = pixBilinearSampledPta(pixg, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 28 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ pix2 = pixBilinearPta(pixg, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 29 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ pix3 = pixBilinearSampledPta(pix1, ptad, ptas, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 30 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+
+ pix4 = pixBilinearPta(pix2, ptad, ptas, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix4, IFF_JFIF_JPEG); /* 31 */
+ pixaAddPix(pixa, pix4, L_INSERT);
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 4, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 32 */
+ pixDisplayWithTitle(pix1, 800, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixg);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+#endif
+
+ return regTestCleanup(rp);
+}
+
+
+static void
+MakePtas(l_int32 i,
+ PTA **pptas,
+ PTA **pptad)
+{
+ *pptas = ptaCreate(4);
+ ptaAddPt(*pptas, x1[i], y1[i]);
+ ptaAddPt(*pptas, x2[i], y2[i]);
+ ptaAddPt(*pptas, x3[i], y3[i]);
+ ptaAddPt(*pptas, x4[i], y4[i]);
+ *pptad = ptaCreate(4);
+ ptaAddPt(*pptad, xp1[i], yp1[i]);
+ ptaAddPt(*pptad, xp2[i], yp2[i]);
+ ptaAddPt(*pptad, xp3[i], yp3[i]);
+ ptaAddPt(*pptad, xp4[i], yp4[i]);
+ return;
+}
diff --git a/leptonica/prog/binarize_reg.c b/leptonica/prog/binarize_reg.c
new file mode 100644
index 00000000..1bec31ba
--- /dev/null
+++ b/leptonica/prog/binarize_reg.c
@@ -0,0 +1,180 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Tests Sauvola local binarization and variants
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+PIX *PixTest1(PIX *pixs, l_int32 size, l_float32 factor, L_REGPARAMS *rp);
+PIX *PixTest2(PIX *pixs, l_int32 size, l_float32 factor, l_int32 nx,
+ l_int32 ny, L_REGPARAMS *rp);
+void PixTest3(PIX *pixs, l_int32 size, l_float32 factor,
+ l_int32 nx, l_int32 ny, l_int32 paircount, L_REGPARAMS *rp);
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pixt1, *pixt2;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("w91frag.jpg");
+
+ PixTest3(pixs, 3, 0.20, 2, 3, 0, rp);
+ PixTest3(pixs, 6, 0.20, 100, 100, 1, rp);
+ PixTest3(pixs, 10, 0.40, 10, 10, 2, rp);
+ PixTest3(pixs, 10, 0.40, 20, 20, 3, rp);
+ PixTest3(pixs, 20, 0.34, 30, 30, 4, rp);
+
+ pixt1 = PixTest1(pixs, 7, 0.34, rp);
+ pixt2 = PixTest2(pixs, 7, 0.34, 4, 4, rp);
+ regTestComparePix(rp, pixt1, pixt2);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ /* Do combination of contrast norm and sauvola */
+ pixt1 = pixContrastNorm(NULL, pixs, 100, 100, 55, 1, 1);
+ pixSauvolaBinarizeTiled(pixt1, 8, 0.34, 1, 1, NULL, &pixt2);
+ regTestWritePixAndCheck(rp, pixt1, IFF_PNG);
+ regTestWritePixAndCheck(rp, pixt2, IFF_PNG);
+ pixDisplayWithTitle(pixt1, 100, 500, NULL, rp->display);
+ pixDisplayWithTitle(pixt2, 700, 500, NULL, rp->display);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
+
+
+PIX *
+PixTest1(PIX *pixs,
+ l_int32 size,
+ l_float32 factor,
+ L_REGPARAMS *rp)
+{
+l_int32 w, h;
+PIX *pixm, *pixsd, *pixth, *pixd, *pixt;
+PIXA *pixa;
+
+ pixm = pixsd = pixth = pixd = NULL;
+ pixGetDimensions(pixs, &w, &h, NULL);
+
+ /* Get speed */
+ startTimer();
+ pixSauvolaBinarize(pixs, size, factor, 1, NULL, NULL, NULL, &pixd);
+ lept_stderr("\nSpeed: 1 tile, %7.3f Mpix/sec\n",
+ (w * h / 1000000.) / stopTimer());
+ pixDestroy(&pixd);
+
+ /* Get results */
+ pixSauvolaBinarize(pixs, size, factor, 1, &pixm, &pixsd, &pixth, &pixd);
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixm, L_INSERT);
+ pixaAddPix(pixa, pixsd, L_INSERT);
+ pixaAddPix(pixa, pixth, L_INSERT);
+ pixaAddPix(pixa, pixd, L_COPY);
+ pixt = pixaDisplayTiledInColumns(pixa, 2, 1.0, 30, 2);
+ regTestWritePixAndCheck(rp, pixt, IFF_JFIF_JPEG);
+ if (rp->index < 5)
+ pixDisplayWithTitle(pixt, 100, 100, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+
+ pixaDestroy(&pixa);
+ pixDestroy(&pixt);
+ return pixd;
+}
+
+
+PIX *
+PixTest2(PIX *pixs,
+ l_int32 size,
+ l_float32 factor,
+ l_int32 nx,
+ l_int32 ny,
+ L_REGPARAMS *rp)
+{
+l_int32 w, h;
+PIX *pixth, *pixd, *pixt;
+PIXA *pixa;
+
+ pixth = pixd = NULL;
+ pixGetDimensions(pixs, &w, &h, NULL);
+
+ /* Get speed */
+ startTimer();
+ pixSauvolaBinarizeTiled(pixs, size, factor, nx, ny, NULL, &pixd);
+ lept_stderr("Speed: %d x %d tiles, %7.3f Mpix/sec\n",
+ nx, ny, (w * h / 1000000.) / stopTimer());
+ pixDestroy(&pixd);
+
+ /* Get results */
+ pixSauvolaBinarizeTiled(pixs, size, factor, nx, ny, &pixth, &pixd);
+ regTestWritePixAndCheck(rp, pixth, IFF_JFIF_JPEG);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ if (rp->index < 5 && rp->display) {
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixth, L_COPY);
+ pixaAddPix(pixa, pixd, L_COPY);
+ pixt = pixaDisplayTiledInColumns(pixa, 2, 1.0, 30, 2);
+ pixDisplayWithTitle(pixt, 100, 400, NULL, rp->display);
+ pixDestroy(&pixt);
+ pixaDestroy(&pixa);
+ }
+
+ pixDestroy(&pixth);
+ return pixd;
+}
+
+
+void
+PixTest3(PIX *pixs,
+ l_int32 size,
+ l_float32 factor,
+ l_int32 nx,
+ l_int32 ny,
+ l_int32 paircount,
+ L_REGPARAMS *rp)
+{
+PIX *pixt1, *pixt2;
+
+ pixt1 = PixTest1(pixs, size, factor, rp);
+ pixt2 = PixTest2(pixs, size, factor, nx, ny, rp);
+ regTestComparePix(rp, pixt1, pixt2);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ return;
+}
diff --git a/leptonica/prog/binarize_set.c b/leptonica/prog/binarize_set.c
new file mode 100644
index 00000000..874c541b
--- /dev/null
+++ b/leptonica/prog/binarize_set.c
@@ -0,0 +1,177 @@
+/*====================================================================*
+ - 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_set.c
+ *
+ * Does 5 different types of binarization for the contest.
+ *
+ * Method 1. Using local background normalization, followed by
+ * a global threshold.
+ * Method 2. Using local background normalization, followed by
+ * Otsu on the result to get a global threshold that
+ * can be applied to the normalized image.
+ * Method 3. Using local background normalization with two different
+ * thresholds. For the part of the image near the text,
+ * a high threshold can be chosen, to render the text
+ * fully in black. For the rest of the image,
+ * much of which is background, use a threshold based on
+ * the Otsu global value of the original image.
+ * Method 4. Background normalization followed by Sauvola binarization.
+ * Method 5. Contrast normalization followed by background normalization
+ * and thresholding.
+ *
+ * The first 3 were submitted to a binarization contest associated
+ * with ICDAR in 2009. The 4th and 5th work better for difficult
+ * images, such as w91frag.jpg.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define ALL 1
+
+
+int main(int argc,
+ char **argv)
+{
+char *infile;
+l_int32 w, d, threshval, ival, newval;
+l_uint32 val;
+PIX *pixs, *pixg, *pixg2;
+PIX *pix1, *pix2;
+PIXA *pixa;
+static char mainName[] = "binarize_set";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: binarize_set infile", mainName, 1);
+ infile = argv[1];
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/binar");
+
+ pixa = pixaCreate(5);
+ pixs = pixRead(infile);
+ pixGetDimensions(pixs, &w, NULL, &d);
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixDisplay(pixs, 100, 0);
+
+#if ALL
+ /* 1. Standard background normalization with a global threshold. */
+ pixg = pixConvertTo8(pixs, 0);
+ pix1 = pixBackgroundNorm(pixg, NULL, NULL, 10, 15, 100, 50, 255, 2, 2);
+ pix2 = pixThresholdToBinary(pix1, 160);
+ pixWrite("/tmp/lept/binar/binar1.png", pix2, IFF_PNG);
+ pixDisplay(pix2, 100, 0);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pixg);
+ pixDestroy(&pix1);
+#endif
+
+#if ALL
+ /* 2. 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. */
+ pixg = pixConvertTo8(pixs, 0);
+ pix1 = pixOtsuThreshOnBackgroundNorm(pixg, NULL, 10, 15, 100,
+ 50, 255, 2, 2, 0.10, &threshval);
+ lept_stderr("thresh val = %d\n", threshval);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixWrite("/tmp/lept/binar/binar2.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 100, 200);
+ pixDestroy(&pixg);
+#endif
+
+#if ALL
+ /* 3. Background normalization with Otsu threshold estimation and
+ * masking for threshold selection. */
+ pixg = pixConvertTo8(pixs, 0);
+ pix1 = pixMaskedThreshOnBackgroundNorm(pixg, NULL, 10, 15, 100,
+ 50, 2, 2, 0.10, &threshval);
+ lept_stderr("thresh val = %d\n", threshval);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixWrite("/tmp/lept/binar/binar3.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 100, 400);
+ pixDestroy(&pixg);
+#endif
+
+#if ALL
+ /* 4. Background normalization followed by Sauvola binarization */
+ if (d == 32)
+ pixg = pixConvertRGBToGray(pixs, 0.2, 0.7, 0.1);
+ else
+ pixg = pixConvertTo8(pixs, 0);
+ pixg2 = pixContrastNorm(NULL, pixg, 20, 20, 130, 2, 2);
+ pixSauvolaBinarizeTiled(pixg2, 25, 0.40, 1, 1, NULL, &pix1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixWrite("/tmp/lept/binar/binar4.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 100, 600);
+ pixDestroy(&pixg);
+ pixDestroy(&pixg2);
+#endif
+
+#if ALL
+ /* 5. Contrast normalization followed by background normalization, and
+ * thresholding. */
+ if (d == 32)
+ pixg = pixConvertRGBToGray(pixs, 0.2, 0.7, 0.1);
+ else
+ pixg = pixConvertTo8(pixs, 0);
+
+ pixOtsuAdaptiveThreshold(pixg, 5000, 5000, 0, 0, 0.1, &pix1, NULL);
+ pixGetPixel(pix1, 0, 0, &val);
+ ival = (l_int32)val;
+ newval = ival + (l_int32)(0.6 * (110 - ival));
+ lept_stderr("th1 = %d, th2 = %d\n", ival, newval);
+ pixDestroy(&pix1);
+
+ pixContrastNorm(pixg, pixg, 50, 50, 130, 2, 2);
+ pixg2 = pixBackgroundNorm(pixg, NULL, NULL, 20, 20, 70, 40, 200, 2, 2);
+
+ ival = L_MIN(ival, 110);
+ pix1 = pixThresholdToBinary(pixg2, ival);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixWrite("/tmp/lept/binar/binar5.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 100, 800);
+ pixDestroy(&pixg);
+ pixDestroy(&pixg2);
+#endif
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 30, 2);
+ pixWrite("/tmp/lept/binar/binar6.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 1000, 0);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ return 0;
+}
diff --git a/leptonica/prog/binarizefiles.c b/leptonica/prog/binarizefiles.c
new file mode 100644
index 00000000..5285aa94
--- /dev/null
+++ b/leptonica/prog/binarizefiles.c
@@ -0,0 +1,120 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * binarizefiles.c
+ *
+ * Program that optionally scales and then binarizes a set of files,
+ * writing them to the specified directory in tiff-g4 format.
+ * The resolution is preserved.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "allheaders.h"
+
+l_int32 main(int argc,
+ char **argv)
+{
+char buf[512], dirname[256];
+char *dirin, *pattern, *subdirout, *fname, *tail, *basename;
+l_int32 thresh, i, n;
+l_float32 scalefactor;
+PIX *pix1, *pix2, *pix3, *pix4;
+SARRAY *sa;
+static char mainName[] = "binarizefiles.c";
+
+ if (argc != 6) {
+ lept_stderr(
+ "Syntax: binarizefiles dirin pattern thresh scalefact dirout\n"
+ " dirin: input directory for image files\n"
+ " pattern: use 'allfiles' to convert all files\n"
+ " in the directory\n"
+ " thresh: 0 for adaptive; > 0 for global thresh (e.g., 128)\n"
+ " scalefactor: in (0.0 ... 4.0]; use 1.0 to prevent scaling\n"
+ " subdirout: subdirectory of /tmp for output files\n");
+ return 1;
+ }
+ dirin = argv[1];
+ pattern = argv[2];
+ thresh = atoi(argv[3]);
+ scalefactor = atof(argv[4]);
+ subdirout = argv[5];
+ if (!strcmp(pattern, "allfiles"))
+ pattern = NULL;
+ if (scalefactor <= 0.0 || scalefactor > 4.0) {
+ L_WARNING("invalid scalefactor: setting to 1.0\n", mainName);
+ scalefactor = 1.0;
+ }
+
+ setLeptDebugOK(1);
+
+ /* Get the input filenames */
+ sa = getSortedPathnamesInDirectory(dirin, pattern, 0, 0);
+ sarrayWriteStream(stderr, sa);
+ n = sarrayGetCount(sa);
+
+ /* Write the output files */
+ makeTempDirname(dirname, 256, subdirout);
+ lept_stderr("dirname: %s\n", dirname);
+ lept_mkdir(subdirout);
+ for (i = 0; i < n; i++) {
+ fname = sarrayGetString(sa, i, L_NOCOPY);
+ if ((pix1 = pixRead(fname)) == NULL) {
+ L_ERROR("file %s not read as image", mainName, fname);
+ continue;
+ }
+ splitPathAtDirectory(fname, NULL, &tail);
+ splitPathAtExtension(tail, &basename, NULL);
+ snprintf(buf, sizeof(buf), "%s/%s.tif", dirname, basename);
+ lept_free(tail);
+ lept_free(basename);
+ lept_stderr("fileout: %s\n", buf);
+ if (scalefactor != 1.0)
+ pix2 = pixScale(pix1, scalefactor, scalefactor);
+ else
+ pix2 = pixClone(pix1);
+ if (thresh == 0) {
+ pix4 = pixConvertTo8(pix2, 0);
+ pix3 = pixAdaptThresholdToBinary(pix4, NULL, 1.0);
+ pixDestroy(&pix4);
+ } else {
+ pix3 = pixConvertTo1(pix2, thresh);
+ }
+ pixWrite(buf, pix3, IFF_TIFF_G4);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ sarrayDestroy(&sa);
+ return 0;
+}
+
diff --git a/leptonica/prog/bincompare.c b/leptonica/prog/bincompare.c
new file mode 100644
index 00000000..2f9c72c5
--- /dev/null
+++ b/leptonica/prog/bincompare.c
@@ -0,0 +1,98 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * bincompare.c
+ *
+ * Bitwise comparison of two binary images
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* set one of these to 1 */
+#define XOR 1
+#define SUBTRACT_1_FROM_2 0
+#define SUBTRACT_2_FROM_1 0
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w, h, d, n;
+char *filein1, *filein2, *fileout;
+PIX *pixs1, *pixs2;
+static char mainName[] = "bincompare";
+
+ if (argc != 4)
+ return ERROR_INT(" Syntax: bincompare filein1 filein2 fileout",
+ mainName, 1);
+ filein1 = argv[1];
+ filein2 = argv[2];
+ fileout = argv[3];
+ setLeptDebugOK(1);
+
+ if ((pixs1 = pixRead(filein1)) == NULL)
+ return ERROR_INT("pixs1 not made", mainName, 1);
+ if ((pixs2 = pixRead(filein2)) == NULL)
+ return ERROR_INT("pixs2 not made", mainName, 1);
+
+ pixGetDimensions(pixs1, &w, &h, &d);
+ if (d != 1)
+ return ERROR_INT("pixs1 not binary", mainName, 1);
+
+ pixCountPixels(pixs1, &n, NULL);
+ lept_stderr("Number of fg pixels in file1 = %d\n", n);
+ pixCountPixels(pixs2, &n, NULL);
+ lept_stderr("Number of fg pixels in file2 = %d\n", n);
+
+#if XOR
+ lept_stderr("xor: 1 ^ 2\n");
+ pixRasterop(pixs1, 0, 0, w, h, PIX_SRC ^ PIX_DST, pixs2, 0, 0);
+ pixCountPixels(pixs1, &n, NULL);
+ lept_stderr("Number of fg pixels in XOR = %d\n", n);
+ pixWrite(fileout, pixs1, IFF_PNG);
+#elif SUBTRACT_1_FROM_2
+ lept_stderr("subtract: 2 - 1\n");
+ pixRasterop(pixs1, 0, 0, w, h, PIX_SRC & PIX_NOT(PIX_DST), pixs2, 0, 0);
+ pixCountPixels(pixs1, &n, NULL);
+ lept_stderr("Number of fg pixels in 2 - 1 = %d\n", n);
+ pixWrite(fileout, pixs1, IFF_PNG);
+#elif SUBTRACT_2_FROM_1
+ lept_stderr("subtract: 1 - 2\n");
+ pixRasterop(pixs1, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC), pixs2, 0, 0);
+ pixCountPixels(pixs1, &n, NULL);
+ lept_stderr("Number of fg pixels in 1 - 2 = %d\n", n);
+ pixWrite(fileout, pixs1, IFF_PNG);
+#else
+ lept_stderr("no comparison selected\n");
+#endif
+
+ return 0;
+}
+
diff --git a/leptonica/prog/binding-example.45.jpg b/leptonica/prog/binding-example.45.jpg
new file mode 100644
index 00000000..c6f3b68c
--- /dev/null
+++ b/leptonica/prog/binding-example.45.jpg
Binary files differ
diff --git a/leptonica/prog/binmorph1_reg.c b/leptonica/prog/binmorph1_reg.c
new file mode 100644
index 00000000..1df62f3a
--- /dev/null
+++ b/leptonica/prog/binmorph1_reg.c
@@ -0,0 +1,578 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * binmorph1_reg.c
+ *
+ * This is a thorough regression test of different methods for
+ * doing binary morphology. It should always be run if changes
+ * are made to the low-level morphology code.
+ *
+ * Some things to note:
+ *
+ * (1) We add a white border to guarantee safe closing; i.e., that
+ * closing is extensive for ASYMMETRIC_MORPH_BC. The separable
+ * sequence for closing is not safe, so if we didn't add the border
+ * ab initio, we would get different results for the atomic sequence
+ * closing (which is safe) and the separable one.
+ *
+ * (2) There are no differences in any of the operations:
+ * rasterop general
+ * rasterop brick
+ * morph sequence rasterop brick
+ * dwa brick
+ * morph sequence dwa brick
+ * morph sequence dwa composite brick
+ * when using ASYMMETRIC_MORPH_BC.
+ * However, when using SYMMETRIC_MORPH_BC, there are differences
+ * in two of the safe closing operations. These differences
+ * are in pix numbers 4 and 5. These differences are
+ * all due to the fact that for SYMMETRIC_MORPH_BC, we don't need
+ * to add any borders to get the correct answer. When we do
+ * add a border of 0 pixels, we naturally get a different result.
+ *
+ * (3) The 2-way Sel decomposition functions, implemented with the
+ * separable brick interface, are tested separately against
+ * the rasterop brick. See binmorph2_reg.c.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* set these ad lib. */
+#define WIDTH 21 /* brick sel width */
+#define HEIGHT 15 /* brick sel height */
+
+void TestAll(L_REGPARAMS *rp, PIX *pix, l_int32 symmetric);
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn-fract.tif");
+
+ TestAll(rp, pixs, FALSE);
+ TestAll(rp, pixs, TRUE);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
+
+
+void
+TestAll(L_REGPARAMS *rp,
+ PIX *pixs,
+ l_int32 symmetric)
+{
+l_int32 ok, same;
+char sequence[512];
+PIX *pixref;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+PIX *pix7, *pix8, *pix9, *pix10, *pix11;
+PIX *pix12, *pix13, *pix14;
+SEL *sel;
+
+ if (symmetric) {
+ /* This works properly if there is an added border */
+ resetMorphBoundaryCondition(SYMMETRIC_MORPH_BC);
+#if 1
+ pix1 = pixAddBorder(pixs, 32, 0);
+ pixTransferAllData(pixs, &pix1, 0, 0);
+#endif
+ lept_stderr("Testing with symmetric boundary conditions\n");
+ } else {
+ resetMorphBoundaryCondition(ASYMMETRIC_MORPH_BC);
+ lept_stderr("Testing with asymmetric boundary conditions\n");
+ }
+
+ /* This is our test sel */
+ sel = selCreateBrick(HEIGHT, WIDTH, HEIGHT / 2, WIDTH / 2, SEL_HIT);
+
+ /* Dilation */
+ lept_stderr(" Testing dilation\n");
+ ok = TRUE;
+ pixref = pixDilate(NULL, pixs, sel); /* new one */
+ pix1 = pixCreateTemplate(pixs);
+ pixDilate(pix1, pixs, sel); /* existing one */
+ pixEqual(pixref, pix1, &same);
+ if (!same) {
+ lept_stderr("pixref != pix1 !\n"); ok = FALSE;
+ }
+ pix2 = pixCopy(NULL, pixs);
+ pixDilate(pix2, pix2, sel); /* in-place */
+ pixEqual(pixref, pix2, &same);
+ if (!same) {
+ lept_stderr("pixref != pix2 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "d%d.%d", WIDTH, HEIGHT);
+ pix3 = pixMorphSequence(pixs, sequence, 0); /* sequence, atomic */
+ pixEqual(pixref, pix3, &same);
+ if (!same) {
+ lept_stderr("pixref != pix3 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "d%d.1 + d1.%d", WIDTH, HEIGHT);
+ pix4 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable */
+ pixEqual(pixref, pix4, &same);
+ if (!same) {
+ lept_stderr("pixref != pix4 !\n"); ok = FALSE;
+ }
+ pix5 = pixDilateBrick(NULL, pixs, WIDTH, HEIGHT); /* new one */
+ pixEqual(pixref, pix5, &same);
+ if (!same) {
+ lept_stderr("pixref != pix5 !\n"); ok = FALSE;
+ }
+ pix6 = pixCreateTemplate(pixs);
+ pixDilateBrick(pix6, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix6, &same);
+ if (!same) {
+ lept_stderr("pixref != pix6 !\n"); ok = FALSE;
+ }
+ pix7 = pixCopy(NULL, pixs);
+ pixDilateBrick(pix7, pix7, WIDTH, HEIGHT); /* in-place */
+ pixEqual(pixref, pix7, &same);
+ if (!same) {
+ lept_stderr("pixref != pix7 !\n"); ok = FALSE;
+ }
+ pix8 = pixDilateBrickDwa(NULL, pixs, WIDTH, HEIGHT); /* new one */
+ pixEqual(pixref, pix8, &same);
+ if (!same) {
+ lept_stderr("pixref != pix8 !\n"); ok = FALSE;
+ }
+ pix9 = pixCreateTemplate(pixs);
+ pixDilateBrickDwa(pix9, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix9, &same);
+ if (!same) {
+ lept_stderr("pixref != pix9 !\n"); ok = FALSE;
+ }
+ pix10 = pixCopy(NULL, pixs);
+ pixDilateBrickDwa(pix10, pix10, WIDTH, HEIGHT); /* in-place */
+ pixEqual(pixref, pix10, &same);
+ if (!same) {
+ lept_stderr("pixref != pix10 !\n"); ok = FALSE;
+ }
+ pix11 = pixCreateTemplate(pixs);
+ pixDilateCompBrickDwa(pix11, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix11, &same);
+ if (!same) {
+ lept_stderr("pixref != pix11 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "d%d.%d", WIDTH, HEIGHT);
+ pix12 = pixMorphCompSequence(pixs, sequence, 0); /* comp sequence */
+ pixEqual(pixref, pix12, &same);
+ if (!same) {
+ lept_stderr("pixref != pix12!\n"); ok = FALSE;
+ }
+ pix13 = pixMorphSequenceDwa(pixs, sequence, 0); /* dwa sequence */
+ pixEqual(pixref, pix13, &same);
+ if (!same) {
+ lept_stderr("pixref != pix13!\n"); ok = FALSE;
+ }
+ pixDestroy(&pixref);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ pixDestroy(&pix9);
+ pixDestroy(&pix10);
+ pixDestroy(&pix11);
+ pixDestroy(&pix12);
+ pixDestroy(&pix13);
+
+ /* Erosion */
+ lept_stderr(" Testing erosion\n");
+ pixref = pixErode(NULL, pixs, sel); /* new one */
+ pix1 = pixCreateTemplate(pixs);
+ pixErode(pix1, pixs, sel); /* existing one */
+ pixEqual(pixref, pix1, &same);
+ if (!same) {
+ lept_stderr("pixref != pix1 !\n"); ok = FALSE;
+ }
+ pix2 = pixCopy(NULL, pixs);
+ pixErode(pix2, pix2, sel); /* in-place */
+ pixEqual(pixref, pix2, &same);
+ if (!same) {
+ lept_stderr("pixref != pix2 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "e%d.%d", WIDTH, HEIGHT);
+ pix3 = pixMorphSequence(pixs, sequence, 0); /* sequence, atomic */
+ pixEqual(pixref, pix3, &same);
+ if (!same) {
+ lept_stderr("pixref != pix3 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "e%d.1 + e1.%d", WIDTH, HEIGHT);
+ pix4 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable */
+ pixEqual(pixref, pix4, &same);
+ if (!same) {
+ lept_stderr("pixref != pix4 !\n"); ok = FALSE;
+ }
+ pix5 = pixErodeBrick(NULL, pixs, WIDTH, HEIGHT); /* new one */
+ pixEqual(pixref, pix5, &same);
+ if (!same) {
+ lept_stderr("pixref != pix5 !\n"); ok = FALSE;
+ }
+ pix6 = pixCreateTemplate(pixs);
+ pixErodeBrick(pix6, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix6, &same);
+ if (!same) {
+ lept_stderr("pixref != pix6 !\n"); ok = FALSE;
+ }
+ pix7 = pixCopy(NULL, pixs);
+ pixErodeBrick(pix7, pix7, WIDTH, HEIGHT); /* in-place */
+ pixEqual(pixref, pix7, &same);
+ if (!same) {
+ lept_stderr("pixref != pix7 !\n"); ok = FALSE;
+ }
+ pix8 = pixErodeBrickDwa(NULL, pixs, WIDTH, HEIGHT); /* new one */
+ pixEqual(pixref, pix8, &same);
+ if (!same) {
+ lept_stderr("pixref != pix8 !\n"); ok = FALSE;
+ }
+ pix9 = pixCreateTemplate(pixs);
+ pixErodeBrickDwa(pix9, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix9, &same);
+ if (!same) {
+ lept_stderr("pixref != pix9 !\n"); ok = FALSE;
+ }
+ pix10 = pixCopy(NULL, pixs);
+ pixErodeBrickDwa(pix10, pix10, WIDTH, HEIGHT); /* in-place */
+ pixEqual(pixref, pix10, &same);
+ if (!same) {
+ lept_stderr("pixref != pix10 !\n"); ok = FALSE;
+ }
+ pix11 = pixCreateTemplate(pixs);
+ pixErodeCompBrickDwa(pix11, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix11, &same);
+ if (!same) {
+ lept_stderr("pixref != pix11 !\n"); ok = FALSE;
+ }
+
+ snprintf(sequence, sizeof(sequence), "e%d.%d", WIDTH, HEIGHT);
+ pix12 = pixMorphCompSequence(pixs, sequence, 0); /* comp sequence */
+ pixEqual(pixref, pix12, &same);
+ if (!same) {
+ lept_stderr("pixref != pix12!\n"); ok = FALSE;
+ }
+ pix13 = pixMorphSequenceDwa(pixs, sequence, 0); /* dwa sequence */
+ pixEqual(pixref, pix13, &same);
+ if (!same) {
+ lept_stderr("pixref != pix13!\n"); ok = FALSE;
+ }
+ pixDestroy(&pixref);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ pixDestroy(&pix9);
+ pixDestroy(&pix10);
+ pixDestroy(&pix11);
+ pixDestroy(&pix12);
+ pixDestroy(&pix13);
+
+ /* Opening */
+ lept_stderr(" Testing opening\n");
+ pixref = pixOpen(NULL, pixs, sel); /* new one */
+ pix1 = pixCreateTemplate(pixs);
+ pixOpen(pix1, pixs, sel); /* existing one */
+ pixEqual(pixref, pix1, &same);
+ if (!same) {
+ lept_stderr("pixref != pix1 !\n"); ok = FALSE;
+ }
+ pix2 = pixCopy(NULL, pixs);
+ pixOpen(pix2, pix2, sel); /* in-place */
+ pixEqual(pixref, pix2, &same);
+ if (!same) {
+ lept_stderr("pixref != pix2 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "o%d.%d", WIDTH, HEIGHT);
+ pix3 = pixMorphSequence(pixs, sequence, 0); /* sequence, atomic */
+ pixEqual(pixref, pix3, &same);
+ if (!same) {
+ lept_stderr("pixref != pix3 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "e%d.%d + d%d.%d",
+ WIDTH, HEIGHT, WIDTH, HEIGHT);
+ pix4 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable */
+ pixEqual(pixref, pix4, &same);
+ if (!same) {
+ lept_stderr("pixref != pix4 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "e%d.1 + e1.%d + d%d.1 + d1.%d",
+ WIDTH, HEIGHT, WIDTH, HEIGHT);
+ pix5 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable^2 */
+ pixEqual(pixref, pix5, &same);
+ if (!same) {
+ lept_stderr("pixref != pix5 !\n"); ok = FALSE;
+ }
+ pix6 = pixOpenBrick(NULL, pixs, WIDTH, HEIGHT); /* new one */
+ pixEqual(pixref, pix6, &same);
+ if (!same) {
+ lept_stderr("pixref != pix6 !\n"); ok = FALSE;
+ }
+ pix7 = pixCreateTemplate(pixs);
+ pixOpenBrick(pix7, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix7, &same);
+ if (!same) {
+ lept_stderr("pixref != pix7 !\n"); ok = FALSE;
+ }
+ pix8 = pixCopy(NULL, pixs); /* in-place */
+ pixOpenBrick(pix8, pix8, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix8, &same);
+ if (!same) {
+ lept_stderr("pixref != pix8 !\n"); ok = FALSE;
+ }
+ pix9 = pixOpenBrickDwa(NULL, pixs, WIDTH, HEIGHT); /* new one */
+ pixEqual(pixref, pix9, &same);
+ if (!same) {
+ lept_stderr("pixref != pix9 !\n"); ok = FALSE;
+ }
+ pix10 = pixCreateTemplate(pixs);
+ pixOpenBrickDwa(pix10, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix10, &same);
+ if (!same) {
+ lept_stderr("pixref != pix10 !\n"); ok = FALSE;
+ }
+ pix11 = pixCopy(NULL, pixs);
+ pixOpenBrickDwa(pix11, pix11, WIDTH, HEIGHT); /* in-place */
+ pixEqual(pixref, pix11, &same);
+ if (!same) {
+ lept_stderr("pixref != pix11 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "o%d.%d", WIDTH, HEIGHT);
+ pix12 = pixMorphCompSequence(pixs, sequence, 0); /* comp sequence */
+ pixEqual(pixref, pix12, &same);
+ if (!same) {
+ lept_stderr("pixref != pix12!\n"); ok = FALSE;
+ }
+
+ pix13 = pixMorphSequenceDwa(pixs, sequence, 0); /* dwa sequence */
+ pixEqual(pixref, pix13, &same);
+ if (!same) {
+ lept_stderr("pixref != pix13!\n"); ok = FALSE;
+ }
+ pix14 = pixCreateTemplate(pixs);
+ pixOpenCompBrickDwa(pix14, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix14, &same);
+ if (!same) {
+ lept_stderr("pixref != pix14 !\n"); ok = FALSE;
+ }
+
+ pixDestroy(&pixref);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ pixDestroy(&pix9);
+ pixDestroy(&pix10);
+ pixDestroy(&pix11);
+ pixDestroy(&pix12);
+ pixDestroy(&pix13);
+ pixDestroy(&pix14);
+
+ /* Closing */
+ lept_stderr(" Testing closing\n");
+ pixref = pixClose(NULL, pixs, sel); /* new one */
+ pix1 = pixCreateTemplate(pixs);
+ pixClose(pix1, pixs, sel); /* existing one */
+ pixEqual(pixref, pix1, &same);
+ if (!same) {
+ lept_stderr("pixref != pix1 !\n"); ok = FALSE;
+ }
+ pix2 = pixCopy(NULL, pixs);
+ pixClose(pix2, pix2, sel); /* in-place */
+ pixEqual(pixref, pix2, &same);
+ if (!same) {
+ lept_stderr("pixref != pix2 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "d%d.%d + e%d.%d",
+ WIDTH, HEIGHT, WIDTH, HEIGHT);
+ pix3 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable */
+ pixEqual(pixref, pix3, &same);
+ if (!same) {
+ lept_stderr("pixref != pix3 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "d%d.1 + d1.%d + e%d.1 + e1.%d",
+ WIDTH, HEIGHT, WIDTH, HEIGHT);
+ pix4 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable^2 */
+ pixEqual(pixref, pix4, &same);
+ if (!same) {
+ lept_stderr("pixref != pix4 !\n"); ok = FALSE;
+ }
+ pix5 = pixCloseBrick(NULL, pixs, WIDTH, HEIGHT); /* new one */
+ pixEqual(pixref, pix5, &same);
+ if (!same) {
+ lept_stderr("pixref != pix5 !\n"); ok = FALSE;
+ }
+ pix6 = pixCreateTemplate(pixs);
+ pixCloseBrick(pix6, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix6, &same);
+ if (!same) {
+ lept_stderr("pixref != pix6 !\n"); ok = FALSE;
+ }
+ pix7 = pixCopy(NULL, pixs); /* in-place */
+ pixCloseBrick(pix7, pix7, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix7, &same);
+ if (!same) {
+ lept_stderr("pixref != pix7 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pixref);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+
+ /* Safe closing (using pix, not pixs) */
+ lept_stderr(" Testing safe closing\n");
+ pixref = pixCloseSafe(NULL, pixs, sel); /* new one */
+ pix1 = pixCreateTemplate(pixs);
+ pixCloseSafe(pix1, pixs, sel); /* existing one */
+ pixEqual(pixref, pix1, &same);
+ if (!same) {
+ lept_stderr("pixref != pix1 !\n"); ok = FALSE;
+ }
+ pix2 = pixCopy(NULL, pixs);
+ pixCloseSafe(pix2, pix2, sel); /* in-place */
+ pixEqual(pixref, pix2, &same);
+ if (!same) {
+ lept_stderr("pixref != pix2 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "c%d.%d", WIDTH, HEIGHT);
+ pix3 = pixMorphSequence(pixs, sequence, 0); /* sequence, atomic */
+ pixEqual(pixref, pix3, &same);
+ if (!same) {
+ lept_stderr("pixref != pix3 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "b32 + d%d.%d + e%d.%d",
+ WIDTH, HEIGHT, WIDTH, HEIGHT);
+ pix4 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable */
+ pixEqual(pixref, pix4, &same);
+ if (!same) {
+ lept_stderr("pixref != pix4 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "b32 + d%d.1 + d1.%d + e%d.1 + e1.%d",
+ WIDTH, HEIGHT, WIDTH, HEIGHT);
+ pix5 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable^2 */
+ pixEqual(pixref, pix5, &same);
+ if (!same) {
+ lept_stderr("pixref != pix5 !\n"); ok = FALSE;
+ }
+ pix6 = pixCloseSafeBrick(NULL, pixs, WIDTH, HEIGHT); /* new one */
+ pixEqual(pixref, pix6, &same);
+ if (!same) {
+ lept_stderr("pixref != pix6 !\n"); ok = FALSE;
+ }
+ pix7 = pixCreateTemplate(pixs);
+ pixCloseSafeBrick(pix7, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix7, &same);
+ if (!same) {
+ lept_stderr("pixref != pix7 !\n"); ok = FALSE;
+ }
+ pix8 = pixCopy(NULL, pixs); /* in-place */
+ pixCloseSafeBrick(pix8, pix8, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix8, &same);
+ if (!same) {
+ lept_stderr("pixref != pix8 !\n"); ok = FALSE;
+ }
+ pix9 = pixCloseBrickDwa(NULL, pixs, WIDTH, HEIGHT); /* new one */
+ pixEqual(pixref, pix9, &same);
+ if (!same) {
+ lept_stderr("pixref != pix9 !\n"); ok = FALSE;
+ }
+ pix10 = pixCreateTemplate(pixs);
+ pixCloseBrickDwa(pix10, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix10, &same);
+ if (!same) {
+ lept_stderr("pixref != pix10 !\n"); ok = FALSE;
+ }
+ pix11 = pixCopy(NULL, pixs);
+ pixCloseBrickDwa(pix11, pix11, WIDTH, HEIGHT); /* in-place */
+ pixEqual(pixref, pix11, &same);
+ if (!same) {
+ lept_stderr("pixref != pix11 !\n"); ok = FALSE;
+ }
+ snprintf(sequence, sizeof(sequence), "c%d.%d", WIDTH, HEIGHT);
+ pix12 = pixMorphCompSequence(pixs, sequence, 0); /* comp sequence */
+ pixEqual(pixref, pix12, &same);
+ if (!same) {
+ lept_stderr("pixref != pix12!\n"); ok = FALSE;
+ }
+ pix13 = pixMorphSequenceDwa(pixs, sequence, 0); /* dwa sequence */
+ pixEqual(pixref, pix13, &same);
+ if (!same) {
+ lept_stderr("pixref != pix13!\n"); ok = FALSE;
+ }
+ pix14 = pixCreateTemplate(pixs);
+ pixCloseCompBrickDwa(pix14, pixs, WIDTH, HEIGHT); /* existing one */
+ pixEqual(pixref, pix14, &same);
+ if (!same) {
+ lept_stderr("pixref != pix14 !\n"); ok = FALSE;
+ }
+
+ pixDestroy(&pixref);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ pixDestroy(&pix9);
+ pixDestroy(&pix10);
+ pixDestroy(&pix11);
+ pixDestroy(&pix12);
+ pixDestroy(&pix13);
+ pixDestroy(&pix14);
+
+ regTestCompareValues(rp, TRUE, ok, 0);
+ if (ok)
+ lept_stderr(" All morph tests OK!\n");
+ else
+ lept_stderr(" Some morph tests failed!\n");
+ selDestroy(&sel);
+}
+
diff --git a/leptonica/prog/binmorph2_reg.c b/leptonica/prog/binmorph2_reg.c
new file mode 100644
index 00000000..7b34794f
--- /dev/null
+++ b/leptonica/prog/binmorph2_reg.c
@@ -0,0 +1,227 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * binmorph2_reg.c
+ *
+ * Thorough regression test for binary separable rasterops,
+ * using the sequence interpreters. This compares the
+ * results for 2-way composite Sels with unitary Sels,
+ * all invoked on the separable block morph ops.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 MAX_SEL_SIZE = 120;
+
+static void writeResult(const char *sequence, l_int32 same);
+
+
+int main(int argc,
+ char **argv)
+{
+char buffer1[256];
+char buffer2[256];
+l_int32 i, same, same2, factor1, factor2, diff, success;
+PIX *pixs, *pixsd, *pixt1, *pixt2, *pixt3;
+static char mainName[] = "binmorph2_reg";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: binmorph2_reg", mainName, 1);
+
+ setLeptDebugOK(1);
+ pixs = pixRead("feyn-fract.tif");
+ pixsd = pixMorphCompSequence(pixs, "d5.5", 0);
+ success = TRUE;
+ for (i = 1; i < MAX_SEL_SIZE; i++) {
+
+ /* Check if the size is exactly decomposable */
+ selectComposableSizes(i, &factor1, &factor2);
+ diff = factor1 * factor2 - i;
+ lept_stderr("%d: (%d, %d): %d\n", i, factor1, factor2, diff);
+
+ /* Carry out operations on identical sized Sels: dilation */
+ snprintf(buffer1, sizeof(buffer1), "d%d.%d", i + diff, i + diff);
+ snprintf(buffer2, sizeof(buffer2), "d%d.%d", i, i);
+ pixt1 = pixMorphSequence(pixsd, buffer1, 0);
+ pixt2 = pixMorphCompSequence(pixsd, buffer2, 0);
+ pixEqual(pixt1, pixt2, &same);
+ if (i < 64) {
+ pixt3 = pixMorphCompSequenceDwa(pixsd, buffer2, 0);
+ pixEqual(pixt1, pixt3, &same2);
+ } else {
+ pixt3 = NULL;
+ same2 = TRUE;
+ }
+ if (same && same2)
+ writeResult(buffer1, 1);
+ else {
+ writeResult(buffer1, 0);
+ success = FALSE;
+ }
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixDestroy(&pixt3);
+
+ /* ... erosion */
+ snprintf(buffer1, sizeof(buffer1), "e%d.%d", i + diff, i + diff);
+ snprintf(buffer2, sizeof(buffer2), "e%d.%d", i, i);
+ pixt1 = pixMorphSequence(pixsd, buffer1, 0);
+ pixt2 = pixMorphCompSequence(pixsd, buffer2, 0);
+ pixEqual(pixt1, pixt2, &same);
+ if (i < 64) {
+ pixt3 = pixMorphCompSequenceDwa(pixsd, buffer2, 0);
+ pixEqual(pixt1, pixt3, &same2);
+ } else {
+ pixt3 = NULL;
+ same2 = TRUE;
+ }
+ if (same && same2)
+ writeResult(buffer1, 1);
+ else {
+ writeResult(buffer1, 0);
+ success = FALSE;
+ }
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixDestroy(&pixt3);
+
+ /* ... opening */
+ snprintf(buffer1, sizeof(buffer1), "o%d.%d", i + diff, i + diff);
+ snprintf(buffer2, sizeof(buffer2), "o%d.%d", i, i);
+ pixt1 = pixMorphSequence(pixsd, buffer1, 0);
+ pixt2 = pixMorphCompSequence(pixsd, buffer2, 0);
+ pixEqual(pixt1, pixt2, &same);
+ if (i < 64) {
+ pixt3 = pixMorphCompSequenceDwa(pixsd, buffer2, 0);
+ pixEqual(pixt1, pixt3, &same2);
+ } else {
+ pixt3 = NULL;
+ same2 = TRUE;
+ }
+ if (same && same2)
+ writeResult(buffer1, 1);
+ else {
+ writeResult(buffer1, 0);
+ success = FALSE;
+ }
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixDestroy(&pixt3);
+
+ /* ... closing */
+ snprintf(buffer1, sizeof(buffer1), "c%d.%d", i + diff, i + diff);
+ snprintf(buffer2, sizeof(buffer2), "c%d.%d", i, i);
+ pixt1 = pixMorphSequence(pixsd, buffer1, 0);
+ pixt2 = pixMorphCompSequence(pixsd, buffer2, 0);
+ pixEqual(pixt1, pixt2, &same);
+ if (i < 64) {
+ pixt3 = pixMorphCompSequenceDwa(pixsd, buffer2, 0);
+ pixEqual(pixt1, pixt3, &same2);
+ } else {
+ pixt3 = NULL;
+ same2 = TRUE;
+ }
+ if (same && same2)
+ writeResult(buffer1, 1);
+ else {
+ writeResult(buffer1, 0);
+ success = FALSE;
+ }
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixDestroy(&pixt3);
+
+ }
+ pixDestroy(&pixs);
+ pixDestroy(&pixsd);
+
+ if (success)
+ lept_stderr("\n---------- Success: no errors ----------\n");
+ else
+ lept_stderr("\n---------- Failure: error(s) found -----------\n");
+ return 0;
+}
+
+
+static void writeResult(const char *sequence,
+ l_int32 same)
+{
+ if (same)
+ lept_stderr("Sequence %s: SUCCESS\n", sequence);
+ else
+ lept_stderr("Sequence %s: FAILURE\n", sequence);
+}
+
+
+#if 0
+ for (i = 1; i < 400; i++) {
+ selectComposableSizes(i, &factor1, &factor2);
+ diff = factor1 * factor2 - i;
+ lept_stderr("%d: (%d, %d): %d\n",
+ i, factor1, factor2, diff);
+ selectComposableSels(i, L_HORIZ, &sel1, &sel2);
+ selDestroy(&sel1);
+ selDestroy(&sel2);
+ }
+#endif
+
+#if 0
+ selectComposableSels(68, L_HORIZ, &sel1, &sel2); /* 17, 4 */
+ str = selPrintToString(sel2);
+ lept_stderr(str);
+ selDestroy(&sel1);
+ selDestroy(&sel2);
+ lept_free(str);
+ selectComposableSels(70, L_HORIZ, &sel1, &sel2); /* 10, 7 */
+ str = selPrintToString(sel2);
+ selDestroy(&sel1);
+ selDestroy(&sel2);
+ lept_stderr(str);
+ lept_free(str);
+ selectComposableSels(85, L_HORIZ, &sel1, &sel2); /* 17, 5 */
+ str = selPrintToString(sel2);
+ selDestroy(&sel1);
+ selDestroy(&sel2);
+ lept_stderr(str);
+ lept_free(str);
+ selectComposableSels(96, L_HORIZ, &sel1, &sel2); /* 12, 8 */
+ str = selPrintToString(sel2);
+ selDestroy(&sel1);
+ selDestroy(&sel2);
+ lept_stderr(str);
+ lept_free(str);
+
+ { SELA *sela;
+ sela = selaAddBasic(NULL);
+ selaWrite("/tmp/junksela.sela", sela);
+ selaDestroy(&sela);
+ }
+#endif
diff --git a/leptonica/prog/binmorph3_reg.c b/leptonica/prog/binmorph3_reg.c
new file mode 100644
index 00000000..36a1ebac
--- /dev/null
+++ b/leptonica/prog/binmorph3_reg.c
@@ -0,0 +1,404 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * binmorph3_reg.c
+ *
+ * This is a regression test of dwa functions. It should always
+ * be run if changes are made to the low-level morphology code.
+ *
+ * Some things to note:
+ *
+ * (1) This compares results for these operations:
+ * - rasterop brick (non-separable, separable)
+ * - dwa brick (separable), as implemented in morphdwa.c
+ * - dwa brick separable, but using lower-level non-separable
+ * autogen'd code.
+ *
+ * (2) See in-line comments for ordinary closing and safe closing.
+ * The complication is due to the fact that the results differ
+ * for symmetric and asymmetric b.c., so we must do some
+ * fine adjustments of the border when implementing using
+ * the lower-level code directly.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+l_int32 TestAll(L_REGPARAMS *rp, PIX *pixs, l_int32 symmetric);
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn-fract.tif");
+
+ TestAll(rp, pixs, FALSE);
+ TestAll(rp, pixs, TRUE);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
+
+l_int32
+TestAll(L_REGPARAMS *rp,
+ PIX *pixs,
+ l_int32 symmetric)
+{
+char *selnameh, *selnamev;
+l_int32 ok, same, w, h, i, bordercolor, extraborder;
+l_int32 width[3] = {21, 1, 21};
+l_int32 height[3] = {1, 7, 7};
+PIX *pixref, *pix0, *pix1, *pix2, *pix3, *pix4;
+SEL *sel;
+SELA *sela;
+
+
+ if (symmetric) {
+ resetMorphBoundaryCondition(SYMMETRIC_MORPH_BC);
+ lept_stderr("Testing with symmetric boundary conditions\n"
+ "==========================================\n");
+ } else {
+ resetMorphBoundaryCondition(ASYMMETRIC_MORPH_BC);
+ lept_stderr("Testing with asymmetric boundary conditions\n"
+ "==========================================\n");
+ }
+
+ for (i = 0; i < 3; i++) {
+ w = width[i];
+ h = height[i];
+ sel = selCreateBrick(h, w, h / 2, w / 2, SEL_HIT);
+ selnameh = NULL;
+ selnamev = NULL;
+
+
+ /* Get the selnames for horiz and vert */
+ sela = selaAddBasic(NULL);
+ if (w > 1) {
+ if ((selnameh = selaGetBrickName(sela, w, 1)) == NULL) {
+ selaDestroy(&sela);
+ selDestroy(&sel);
+ return ERROR_INT("dwa hor sel not defined", rp->testname, 1);
+ }
+ }
+ if (h > 1) {
+ if ((selnamev = selaGetBrickName(sela, 1, h)) == NULL) {
+ selaDestroy(&sela);
+ selDestroy(&sel);
+ return ERROR_INT("dwa vert sel not defined", rp->testname, 1);
+ }
+ }
+ lept_stderr("w = %d, h = %d, selh = %s, selv = %s\n",
+ w, h, selnameh, selnamev);
+ ok = TRUE;
+ selaDestroy(&sela);
+
+ /* ----------------- Dilation ----------------- */
+ lept_stderr("Testing dilation\n");
+ pixref = pixDilate(NULL, pixs, sel);
+ pix1 = pixDilateBrickDwa(NULL, pixs, w, h);
+ pixEqual(pixref, pix1, &same);
+ if (!same) {
+ lept_stderr("pixref != pix1 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pix1);
+
+ if (w > 1)
+ pix1 = pixMorphDwa_1(NULL, pixs, L_MORPH_DILATE, selnameh);
+ else
+ pix1 = pixClone(pixs);
+ if (h > 1)
+ pix2 = pixMorphDwa_1(NULL, pix1, L_MORPH_DILATE, selnamev);
+ else
+ pix2 = pixClone(pix1);
+ pixEqual(pixref, pix2, &same);
+ if (!same) {
+ lept_stderr("pixref != pix2 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ pix1 = pixAddBorder(pixs, 32, 0);
+ if (w > 1)
+ pix2 = pixFMorphopGen_1(NULL, pix1, L_MORPH_DILATE, selnameh);
+ else
+ pix2 = pixClone(pix1);
+ if (h > 1)
+ pix3 = pixFMorphopGen_1(NULL, pix2, L_MORPH_DILATE, selnamev);
+ else
+ pix3 = pixClone(pix2);
+ pix4 = pixRemoveBorder(pix3, 32);
+ pixEqual(pixref, pix4, &same);
+ if (!same) {
+ lept_stderr("pixref != pix4 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pixref);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* ----------------- Erosion ----------------- */
+ lept_stderr("Testing erosion\n");
+ pixref = pixErode(NULL, pixs, sel);
+ pix1 = pixErodeBrickDwa(NULL, pixs, w, h);
+ pixEqual(pixref, pix1, &same);
+ if (!same) {
+ lept_stderr("pixref != pix1 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pix1);
+
+ if (w > 1)
+ pix1 = pixMorphDwa_1(NULL, pixs, L_MORPH_ERODE, selnameh);
+ else
+ pix1 = pixClone(pixs);
+ if (h > 1)
+ pix2 = pixMorphDwa_1(NULL, pix1, L_MORPH_ERODE, selnamev);
+ else
+ pix2 = pixClone(pix1);
+ pixEqual(pixref, pix2, &same);
+ if (!same) {
+ lept_stderr("pixref != pix2 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ pix1 = pixAddBorder(pixs, 32, 0);
+ if (w > 1)
+ pix2 = pixFMorphopGen_1(NULL, pix1, L_MORPH_ERODE, selnameh);
+ else
+ pix2 = pixClone(pix1);
+ if (h > 1)
+ pix3 = pixFMorphopGen_1(NULL, pix2, L_MORPH_ERODE, selnamev);
+ else
+ pix3 = pixClone(pix2);
+ pix4 = pixRemoveBorder(pix3, 32);
+ pixEqual(pixref, pix4, &same);
+ if (!same) {
+ lept_stderr("pixref != pix4 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pixref);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* ----------------- Opening ----------------- */
+ lept_stderr("Testing opening\n");
+ pixref = pixOpen(NULL, pixs, sel);
+ pix1 = pixOpenBrickDwa(NULL, pixs, w, h);
+ pixEqual(pixref, pix1, &same);
+ if (!same) {
+ lept_stderr("pixref != pix1 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pix1);
+
+ if (h == 1)
+ pix2 = pixMorphDwa_1(NULL, pixs, L_MORPH_OPEN, selnameh);
+ else if (w == 1)
+ pix2 = pixMorphDwa_1(NULL, pixs, L_MORPH_OPEN, selnamev);
+ else {
+ pix1 = pixMorphDwa_1(NULL, pixs, L_MORPH_ERODE, selnameh);
+ pix2 = pixMorphDwa_1(NULL, pix1, L_MORPH_ERODE, selnamev);
+ pixMorphDwa_1(pix1, pix2, L_MORPH_DILATE, selnameh);
+ pixMorphDwa_1(pix2, pix1, L_MORPH_DILATE, selnamev);
+ pixDestroy(&pix1);
+ }
+ pixEqual(pixref, pix2, &same);
+ if (!same) {
+ lept_stderr("pixref != pix2 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pix2);
+
+ pix1 = pixAddBorder(pixs, 32, 0);
+ if (h == 1)
+ pix3 = pixFMorphopGen_1(NULL, pix1, L_MORPH_OPEN, selnameh);
+ else if (w == 1)
+ pix3 = pixFMorphopGen_1(NULL, pix1, L_MORPH_OPEN, selnamev);
+ else {
+ pix2 = pixFMorphopGen_1(NULL, pix1, L_MORPH_ERODE, selnameh);
+ pix3 = pixFMorphopGen_1(NULL, pix2, L_MORPH_ERODE, selnamev);
+ pixFMorphopGen_1(pix2, pix3, L_MORPH_DILATE, selnameh);
+ pixFMorphopGen_1(pix3, pix2, L_MORPH_DILATE, selnamev);
+ pixDestroy(&pix2);
+ }
+ pix4 = pixRemoveBorder(pix3, 32);
+ pixEqual(pixref, pix4, &same);
+ if (!same) {
+ lept_stderr("pixref != pix4 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pixref);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* ----------------- Closing ----------------- */
+ lept_stderr("Testing closing\n");
+ pixref = pixClose(NULL, pixs, sel);
+
+ /* Note: L_MORPH_CLOSE for h==1 or w==1 gives safe closing,
+ * so we can't use it here. */
+ if (h == 1) {
+ pix1 = pixMorphDwa_1(NULL, pixs, L_MORPH_DILATE, selnameh);
+ pix2 = pixMorphDwa_1(NULL, pix1, L_MORPH_ERODE, selnameh);
+ }
+ else if (w == 1) {
+ pix1 = pixMorphDwa_1(NULL, pixs, L_MORPH_DILATE, selnamev);
+ pix2 = pixMorphDwa_1(NULL, pix1, L_MORPH_ERODE, selnamev);
+ }
+ else {
+ pix1 = pixMorphDwa_1(NULL, pixs, L_MORPH_DILATE, selnameh);
+ pix2 = pixMorphDwa_1(NULL, pix1, L_MORPH_DILATE, selnamev);
+ pixMorphDwa_1(pix1, pix2, L_MORPH_ERODE, selnameh);
+ pixMorphDwa_1(pix2, pix1, L_MORPH_ERODE, selnamev);
+ }
+ pixDestroy(&pix1);
+ pixEqual(pixref, pix2, &same);
+ if (!same) {
+ lept_stderr("pixref != pix2 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pix2);
+
+ /* Note: by adding only 32 pixels of border, we get
+ * the normal closing operation, even when calling
+ * with L_MORPH_CLOSE, because it requires 32 pixels
+ * of border to be safe. */
+ pix1 = pixAddBorder(pixs, 32, 0);
+ if (h == 1)
+ pix3 = pixFMorphopGen_1(NULL, pix1, L_MORPH_CLOSE, selnameh);
+ else if (w == 1)
+ pix3 = pixFMorphopGen_1(NULL, pix1, L_MORPH_CLOSE, selnamev);
+ else {
+ pix2 = pixFMorphopGen_1(NULL, pix1, L_MORPH_DILATE, selnameh);
+ pix3 = pixFMorphopGen_1(NULL, pix2, L_MORPH_DILATE, selnamev);
+ pixFMorphopGen_1(pix2, pix3, L_MORPH_ERODE, selnameh);
+ pixFMorphopGen_1(pix3, pix2, L_MORPH_ERODE, selnamev);
+ pixDestroy(&pix2);
+ }
+ pix4 = pixRemoveBorder(pix3, 32);
+ pixEqual(pixref, pix4, &same);
+ if (!same) {
+ lept_stderr("pixref != pix4 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pixref);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* ------------- Safe Closing ----------------- */
+ lept_stderr("Testing safe closing\n");
+ pixref = pixCloseSafe(NULL, pixs, sel);
+ pix0 = pixCloseSafeBrick(NULL, pixs, w, h);
+ pixEqual(pixref, pix0, &same);
+ if (!same) {
+ lept_stderr("pixref != pix0 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pix0);
+
+ pix1 = pixCloseBrickDwa(NULL, pixs, w, h);
+ pixEqual(pixref, pix1, &same);
+ if (!same) {
+ lept_stderr("pixref != pix1 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pix1);
+
+ bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+ if (bordercolor == 0) /* asymmetric b.c. */
+ extraborder = 32;
+ else /* symmetric b.c. */
+ extraborder = 0;
+
+ /* Note: for safe closing we need 64 border pixels.
+ * However, when we implement a separable Sel
+ * with pixMorphDwa_*(), we must do dilation and
+ * erosion explicitly, and these functions only
+ * add/remove a 32-pixel border. Thus, for that
+ * case we must add an additional 32-pixel border
+ * before doing the operations. That is the reason
+ * why the implementation in morphdwa.c adds the
+ * 64 bit border and then uses the lower-level
+ * pixFMorphopGen_*() functions. */
+ if (h == 1)
+ pix3 = pixMorphDwa_1(NULL, pixs, L_MORPH_CLOSE, selnameh);
+ else if (w == 1)
+ pix3 = pixMorphDwa_1(NULL, pixs, L_MORPH_CLOSE, selnamev);
+ else {
+ pix0 = pixAddBorder(pixs, extraborder, 0);
+ pix1 = pixMorphDwa_1(NULL, pix0, L_MORPH_DILATE, selnameh);
+ pix2 = pixMorphDwa_1(NULL, pix1, L_MORPH_DILATE, selnamev);
+ pixMorphDwa_1(pix1, pix2, L_MORPH_ERODE, selnameh);
+ pixMorphDwa_1(pix2, pix1, L_MORPH_ERODE, selnamev);
+ pix3 = pixRemoveBorder(pix2, extraborder);
+ pixDestroy(&pix0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ pixEqual(pixref, pix3, &same);
+ if (!same) {
+ lept_stderr("pixref != pix3 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pix3);
+
+ pix1 = pixAddBorder(pixs, 32 + extraborder, 0);
+ if (h == 1)
+ pix3 = pixFMorphopGen_1(NULL, pix1, L_MORPH_CLOSE, selnameh);
+ else if (w == 1)
+ pix3 = pixFMorphopGen_1(NULL, pix1, L_MORPH_CLOSE, selnamev);
+ else {
+ pix2 = pixFMorphopGen_1(NULL, pix1, L_MORPH_DILATE, selnameh);
+ pix3 = pixFMorphopGen_1(NULL, pix2, L_MORPH_DILATE, selnamev);
+ pixFMorphopGen_1(pix2, pix3, L_MORPH_ERODE, selnameh);
+ pixFMorphopGen_1(pix3, pix2, L_MORPH_ERODE, selnamev);
+ pixDestroy(&pix2);
+ }
+ pix4 = pixRemoveBorder(pix3, 32 + extraborder);
+ pixEqual(pixref, pix4, &same);
+ if (!same) {
+ lept_stderr("pixref != pix4 !\n"); ok = FALSE;
+ }
+ pixDestroy(&pixref);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ regTestCompareValues(rp, TRUE, ok, 0);
+ if (ok)
+ lept_stderr("All morph tests OK!\n\n");
+ selDestroy(&sel);
+ lept_free(selnameh);
+ lept_free(selnamev);
+ }
+
+ return 0;
+}
+
diff --git a/leptonica/prog/binmorph4_reg.c b/leptonica/prog/binmorph4_reg.c
new file mode 100644
index 00000000..a9023c56
--- /dev/null
+++ b/leptonica/prog/binmorph4_reg.c
@@ -0,0 +1,544 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * binmorph4_reg.c
+ *
+ * Regression test for dwa brick morph operations
+ * We compare:
+ * (1) morph composite vs. morph non-composite
+ * (2) dwa non-composite vs. morph composite
+ * (3) dwa composite vs. dwa non-composite
+ * (4) dwa composite vs. morph composite
+ * (5) dwa composite vs. morph non-composite
+ * The brick functions all have a pre-allocated pix as the dest.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void TestAll(L_REGPARAMS *rp, PIX *pixs, l_int32 symmetric);
+
+l_int32 DoComparisonDwa1(L_REGPARAMS *rp,
+ PIX *pixs, PIX *pix1, PIX *pix2, PIX *pix3,
+ PIX *pix4, PIX *pix5, PIX *pix6, l_int32 isize);
+l_int32 DoComparisonDwa2(L_REGPARAMS *rp,
+ PIX *pixs, PIX *pix1, PIX *pix2, PIX *pix3,
+ PIX *pix4, PIX *pix5, PIX *pix6, l_int32 isize);
+l_int32 DoComparisonDwa3(L_REGPARAMS *rp,
+ PIX *pixs, PIX *pix1, PIX *pix2, PIX *pix3,
+ PIX *pix4, PIX *pix5, PIX *pix6, l_int32 isize);
+l_int32 DoComparisonDwa4(L_REGPARAMS *rp,
+ PIX *pixs, PIX *pix1, PIX *pix2, PIX *pix3,
+ PIX *pix4, PIX *pix5, PIX *pix6, l_int32 isize);
+l_int32 DoComparisonDwa5(L_REGPARAMS *rp,
+ PIX *pixs, PIX *pix1, PIX *pix2, PIX *pix3,
+ PIX *pix4, PIX *pix5, PIX *pix6, l_int32 isize);
+void PixCompareDwa(L_REGPARAMS *rp,
+ l_int32 size, const char *type, PIX *pix1, PIX *pix2,
+ PIX *pix3, PIX *pix4, PIX *pix5, PIX *pix6);
+
+#define TIMING 0
+
+ /* Note: the symmetric case requires an extra border of size
+ * approximately 40 to succeed for all SE up to size 64. With
+ * a smaller border the differences are small, and most of the
+ * problem seems to be in the non-dwa code, because we are doing
+ * sequential erosions without an extra border, and things aren't
+ * being properly initialized. To avoid these errors, add the border
+ * in advance for symmetric b.c.
+ * Note that asymmetric b.c. are recommended for document image
+ * operations, and this test passes for asymmetric b.c. without
+ * any added border. */
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn-fract.tif");
+ TestAll(rp, pixs, FALSE);
+ TestAll(rp, pixs, TRUE);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
+
+void
+TestAll(L_REGPARAMS *rp,
+ PIX *pixs,
+ l_int32 symmetric)
+{
+l_int32 i;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+
+ if (symmetric) {
+ /* This works properly with an added border of 40 */
+ resetMorphBoundaryCondition(SYMMETRIC_MORPH_BC);
+ pix1 = pixAddBorder(pixs, 40, 0);
+ pixTransferAllData(pixs, &pix1, 0, 0);
+ lept_stderr("Testing with symmetric boundary conditions\n");
+ } else {
+ resetMorphBoundaryCondition(ASYMMETRIC_MORPH_BC);
+ lept_stderr("Testing with asymmetric boundary conditions\n");
+ }
+
+ pix1 = pixCreateTemplateNoInit(pixs);
+ pix2 = pixCreateTemplateNoInit(pixs);
+ pix3 = pixCreateTemplateNoInit(pixs);
+ pix4 = pixCreateTemplateNoInit(pixs);
+ pix5 = pixCreateTemplateNoInit(pixs);
+ pix6 = pixCreateTemplateNoInit(pixs);
+
+ for (i = 2; i < 64; i++) {
+
+#if 1
+ /* Compare morph composite with morph non-composite */
+ DoComparisonDwa1(rp, pixs, pix1, pix2, pix3, pix4, pix5, pix6, i);
+#endif
+
+#if 1
+ /* Compare DWA non-composite with morph composite */
+ if (i < 16)
+ DoComparisonDwa2(rp, pixs, pix1, pix2, pix3, pix4, pix5, pix6, i);
+ /* Compare DWA composite with DWA non-composite */
+ if (i < 16)
+ DoComparisonDwa3(rp, pixs, pix1, pix2, pix3, pix4, pix5, pix6, i);
+ /* Compare DWA composite with morph composite */
+ DoComparisonDwa4(rp, pixs, pix1, pix2, pix3, pix4, pix5, pix6, i);
+ /* Compare DWA composite with morph non-composite */
+ DoComparisonDwa5(rp, pixs, pix1, pix2, pix3, pix4, pix5, pix6, i);
+#endif
+ }
+ lept_stderr("\n");
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+}
+
+
+ /* Morph composite with morph non-composite */
+l_int32
+DoComparisonDwa1(L_REGPARAMS *rp,
+ PIX *pixs,
+ PIX *pix1,
+ PIX *pix2,
+ PIX *pix3,
+ PIX *pix4,
+ PIX *pix5,
+ PIX *pix6,
+ l_int32 isize)
+{
+l_int32 fact1, fact2, size;
+
+ selectComposableSizes(isize, &fact1, &fact2);
+ size = fact1 * fact2;
+
+ lept_stderr("..%d..", size);
+
+ if (TIMING) startTimer();
+ pixDilateCompBrick(pix1, pixs, size, 1);
+ pixDilateCompBrick(pix3, pixs, 1, size);
+ pixDilateCompBrick(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixDilateBrick(pix2, pixs, size, 1);
+ pixDilateBrick(pix4, pixs, 1, size);
+ pixDilateBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "dilate", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixErodeCompBrick(pix1, pixs, size, 1);
+ pixErodeCompBrick(pix3, pixs, 1, size);
+ pixErodeCompBrick(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixErodeBrick(pix2, pixs, size, 1);
+ pixErodeBrick(pix4, pixs, 1, size);
+ pixErodeBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "erode", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixOpenCompBrick(pix1, pixs, size, 1);
+ pixOpenCompBrick(pix3, pixs, 1, size);
+ pixOpenCompBrick(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixOpenBrick(pix2, pixs, size, 1);
+ pixOpenBrick(pix4, pixs, 1, size);
+ pixOpenBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "open", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixCloseSafeCompBrick(pix1, pixs, size, 1);
+ pixCloseSafeCompBrick(pix3, pixs, 1, size);
+ pixCloseSafeCompBrick(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixCloseSafeBrick(pix2, pixs, size, 1);
+ pixCloseSafeBrick(pix4, pixs, 1, size);
+ pixCloseSafeBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "close", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ return 0;
+}
+
+
+ /* Dwa non-composite with morph composite */
+l_int32
+DoComparisonDwa2(L_REGPARAMS *rp,
+ PIX *pixs,
+ PIX *pix1,
+ PIX *pix2,
+ PIX *pix3,
+ PIX *pix4,
+ PIX *pix5,
+ PIX *pix6,
+ l_int32 isize)
+{
+l_int32 fact1, fact2, size;
+
+ selectComposableSizes(isize, &fact1, &fact2);
+ size = fact1 * fact2;
+
+ lept_stderr("..%d..", size);
+
+ if (TIMING) startTimer();
+ pixDilateBrickDwa(pix1, pixs, size, 1);
+ pixDilateBrickDwa(pix3, pixs, 1, size);
+ pixDilateBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixDilateCompBrick(pix2, pixs, size, 1);
+ pixDilateCompBrick(pix4, pixs, 1, size);
+ pixDilateCompBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "dilate", pix1, pix2, pix3, pix4, pix5, pix6);
+
+/* pixDisplay(pix1, 100, 100); */
+/* pixDisplay(pix2, 800, 100); */
+
+ if (TIMING) startTimer();
+ pixErodeBrickDwa(pix1, pixs, size, 1);
+ pixErodeBrickDwa(pix3, pixs, 1, size);
+ pixErodeBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixErodeCompBrick(pix2, pixs, size, 1);
+ pixErodeCompBrick(pix4, pixs, 1, size);
+ pixErodeCompBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "erode", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixOpenBrickDwa(pix1, pixs, size, 1);
+ pixOpenBrickDwa(pix3, pixs, 1, size);
+ pixOpenBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixOpenCompBrick(pix2, pixs, size, 1);
+ pixOpenCompBrick(pix4, pixs, 1, size);
+ pixOpenCompBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "open", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixCloseBrickDwa(pix1, pixs, size, 1);
+ pixCloseBrickDwa(pix3, pixs, 1, size);
+ pixCloseBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixCloseSafeCompBrick(pix2, pixs, size, 1);
+ pixCloseSafeCompBrick(pix4, pixs, 1, size);
+ pixCloseSafeCompBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "close", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ return 0;
+}
+
+
+ /* Dwa composite with dwa non-composite */
+l_int32
+DoComparisonDwa3(L_REGPARAMS *rp,
+ PIX *pixs,
+ PIX *pix1,
+ PIX *pix2,
+ PIX *pix3,
+ PIX *pix4,
+ PIX *pix5,
+ PIX *pix6,
+ l_int32 isize)
+{
+l_int32 fact1, fact2, size;
+
+ selectComposableSizes(isize, &fact1, &fact2);
+ size = fact1 * fact2;
+
+ lept_stderr("..%d..", size);
+
+ if (TIMING) startTimer();
+ pixDilateCompBrickDwa(pix1, pixs, size, 1);
+ pixDilateCompBrickDwa(pix3, pixs, 1, size);
+ pixDilateCompBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixDilateBrickDwa(pix2, pixs, size, 1);
+ pixDilateBrickDwa(pix4, pixs, 1, size);
+ pixDilateBrickDwa(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "dilate", pix1, pix2, pix3, pix4, pix5, pix6);
+
+/* pixDisplay(pix1, 100, 100); */
+/* pixDisplay(pix2, 800, 100); */
+
+ if (TIMING) startTimer();
+ pixErodeCompBrickDwa(pix1, pixs, size, 1);
+ pixErodeCompBrickDwa(pix3, pixs, 1, size);
+ pixErodeCompBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixErodeBrickDwa(pix2, pixs, size, 1);
+ pixErodeBrickDwa(pix4, pixs, 1, size);
+ pixErodeBrickDwa(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "erode", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixOpenCompBrickDwa(pix1, pixs, size, 1);
+ pixOpenCompBrickDwa(pix3, pixs, 1, size);
+ pixOpenCompBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixOpenBrickDwa(pix2, pixs, size, 1);
+ pixOpenBrickDwa(pix4, pixs, 1, size);
+ pixOpenBrickDwa(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "open", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixCloseCompBrickDwa(pix1, pixs, size, 1);
+ pixCloseCompBrickDwa(pix3, pixs, 1, size);
+ pixCloseCompBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixCloseBrickDwa(pix2, pixs, size, 1);
+ pixCloseBrickDwa(pix4, pixs, 1, size);
+ pixCloseBrickDwa(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "close", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ return 0;
+}
+
+
+ /* Dwa composite with morph composite */
+l_int32
+DoComparisonDwa4(L_REGPARAMS *rp,
+ PIX *pixs,
+ PIX *pix1,
+ PIX *pix2,
+ PIX *pix3,
+ PIX *pix4,
+ PIX *pix5,
+ PIX *pix6,
+ l_int32 isize)
+{
+l_int32 fact1, fact2, size;
+
+ selectComposableSizes(isize, &fact1, &fact2);
+ size = fact1 * fact2;
+
+ lept_stderr("..%d..", size);
+
+ if (TIMING) startTimer();
+ pixDilateCompBrickDwa(pix1, pixs, size, 1);
+ pixDilateCompBrickDwa(pix3, pixs, 1, size);
+ pixDilateCompBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixDilateCompBrick(pix2, pixs, size, 1);
+ pixDilateCompBrick(pix4, pixs, 1, size);
+ pixDilateCompBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "dilate", pix1, pix2, pix3, pix4, pix5, pix6);
+
+/* pixDisplay(pix1, 100, 100); */
+/* pixDisplay(pix2, 800, 100); */
+
+ if (TIMING) startTimer();
+ pixErodeCompBrickDwa(pix1, pixs, size, 1);
+ pixErodeCompBrickDwa(pix3, pixs, 1, size);
+ pixErodeCompBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixErodeCompBrick(pix2, pixs, size, 1);
+ pixErodeCompBrick(pix4, pixs, 1, size);
+ pixErodeCompBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "erode", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixOpenCompBrickDwa(pix1, pixs, size, 1);
+ pixOpenCompBrickDwa(pix3, pixs, 1, size);
+ pixOpenCompBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixOpenCompBrick(pix2, pixs, size, 1);
+ pixOpenCompBrick(pix4, pixs, 1, size);
+ pixOpenCompBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "open", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixCloseCompBrickDwa(pix1, pixs, size, 1);
+ pixCloseCompBrickDwa(pix3, pixs, 1, size);
+ pixCloseCompBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixCloseSafeCompBrick(pix2, pixs, size, 1);
+ pixCloseSafeCompBrick(pix4, pixs, 1, size);
+ pixCloseSafeCompBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "close", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ return 0;
+}
+
+ /* Dwa composite with morph non-composite */
+l_int32
+DoComparisonDwa5(L_REGPARAMS *rp,
+ PIX *pixs,
+ PIX *pix1,
+ PIX *pix2,
+ PIX *pix3,
+ PIX *pix4,
+ PIX *pix5,
+ PIX *pix6,
+ l_int32 isize)
+{
+l_int32 fact1, fact2, size;
+
+ selectComposableSizes(isize, &fact1, &fact2);
+ size = fact1 * fact2;
+
+ lept_stderr("..%d..", size);
+
+ if (TIMING) startTimer();
+ pixDilateCompBrickDwa(pix1, pixs, size, 1);
+ pixDilateCompBrickDwa(pix3, pixs, 1, size);
+ pixDilateCompBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixDilateBrick(pix2, pixs, size, 1);
+ pixDilateBrick(pix4, pixs, 1, size);
+ pixDilateBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "dilate", pix1, pix2, pix3, pix4, pix5, pix6);
+
+/* pixDisplay(pix1, 100, 100); */
+/* pixDisplay(pix2, 800, 100); */
+
+ if (TIMING) startTimer();
+ pixErodeCompBrickDwa(pix1, pixs, size, 1);
+ pixErodeCompBrickDwa(pix3, pixs, 1, size);
+ pixErodeCompBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixErodeBrick(pix2, pixs, size, 1);
+ pixErodeBrick(pix4, pixs, 1, size);
+ pixErodeBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "erode", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixOpenCompBrickDwa(pix1, pixs, size, 1);
+ pixOpenCompBrickDwa(pix3, pixs, 1, size);
+ pixOpenCompBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixOpenBrick(pix2, pixs, size, 1);
+ pixOpenBrick(pix4, pixs, 1, size);
+ pixOpenBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "open", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixCloseCompBrickDwa(pix1, pixs, size, 1);
+ pixCloseCompBrickDwa(pix3, pixs, 1, size);
+ pixCloseCompBrickDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixCloseSafeBrick(pix2, pixs, size, 1);
+ pixCloseSafeBrick(pix4, pixs, 1, size);
+ pixCloseSafeBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "close", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ return 0;
+}
+
+
+void
+PixCompareDwa(L_REGPARAMS *rp,
+ l_int32 size,
+ const char *type,
+ PIX *pix1,
+ PIX *pix2,
+ PIX *pix3,
+ PIX *pix4,
+ PIX *pix5,
+ PIX *pix6)
+{
+l_int32 same;
+
+ pixEqual(pix1, pix2, &same);
+ regTestCompareValues(rp, TRUE, same, 0);
+ if (!same)
+ lept_stderr("%s (%d, 1) not same\n", type, size);
+ pixEqual(pix3, pix4, &same);
+ regTestCompareValues(rp, TRUE, same, 0);
+ if (!same)
+ lept_stderr("%s (1, %d) not same\n", type, size);
+ pixEqual(pix5, pix6, &same);
+ regTestCompareValues(rp, TRUE, same, 0);
+ if (!same)
+ lept_stderr("%s (%d, %d) not same\n", type, size, size);
+}
+
diff --git a/leptonica/prog/binmorph5_reg.c b/leptonica/prog/binmorph5_reg.c
new file mode 100644
index 00000000..22f69a93
--- /dev/null
+++ b/leptonica/prog/binmorph5_reg.c
@@ -0,0 +1,329 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * binmorph5_reg.c
+ *
+ * Regression test for expanded dwa morph operations.
+ * We compare:
+ * (1) dwa composite vs. morph composite
+ * (2) dwa composite vs. morph non-composite
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void TestAll(L_REGPARAMS *rp, PIX *pixs, l_int32 symmetric);
+
+l_int32 DoComparisonDwa1(L_REGPARAMS *rp,
+ PIX *pixs, PIX *pix1, PIX *pix2, PIX *pix3,
+ PIX *pix4, PIX *pix5, PIX *pix6, l_int32 isize);
+l_int32 DoComparisonDwa2(L_REGPARAMS *rp,
+ PIX *pixs, PIX *pix1, PIX *pix2, PIX *pix3,
+ PIX *pix4, PIX *pix5, PIX *pix6, l_int32 size);
+void PixCompareDwa(L_REGPARAMS *rp,
+ l_int32 size, const char *type, PIX *pix1, PIX *pix2,
+ PIX *pix3, PIX *pix4, PIX *pix5, PIX *pix6);
+
+#define TIMING 0
+#define FASTER_TEST 1
+#define SLOWER_TEST 1
+
+ /* Note: this fails on the symmetric case when the added border
+ * is 64 pixels, but the differences are relatively small.
+ * Most of the problem seems to be in the non-dwa code, because we
+ * are doing sequential erosions without an extra border, and
+ * things aren't being properly initialized. To avoid these errors,
+ * add a sufficiently large border for symmetric b.c. The size of
+ * the border needs to be half the size of the largest SE that is
+ * being used. Here we test up to size 240, and a border of 128
+ * pixels is sufficient for symmetric b.c. (For a SE of size 240
+ * with its center in the middle at 120, the maximum translation will
+ * be about 120.)
+ * Note also that asymmetric b.c. are recommended for document image
+ * operations, and this test passes with no added border for
+ * asymmetric b.c. */
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn-fract.tif");
+ TestAll(rp, pixs, FALSE);
+ TestAll(rp, pixs, TRUE);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
+
+void
+TestAll(L_REGPARAMS *rp,
+ PIX *pixs,
+ l_int32 symmetric)
+{
+l_int32 i, n, rsize, fact1, fact2, extra;
+l_int32 size, lastsize;
+l_int32 dwasize[256];
+l_int32 ropsize[256];
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+
+ if (symmetric) {
+ /* This works properly with an added border of 128 */
+ resetMorphBoundaryCondition(SYMMETRIC_MORPH_BC);
+ pix1 = pixAddBorder(pixs, 128, 0);
+ pixTransferAllData(pixs, &pix1, 0, 0);
+ lept_stderr("Testing with symmetric boundary conditions\n");
+ } else {
+ resetMorphBoundaryCondition(ASYMMETRIC_MORPH_BC);
+ lept_stderr("Testing with asymmetric boundary conditions\n");
+ }
+
+ pix1 = pixCreateTemplateNoInit(pixs);
+ pix2 = pixCreateTemplateNoInit(pixs);
+ pix3 = pixCreateTemplateNoInit(pixs);
+ pix4 = pixCreateTemplateNoInit(pixs);
+ pix5 = pixCreateTemplateNoInit(pixs);
+ pix6 = pixCreateTemplateNoInit(pixs);
+
+ /* ---------------------------------------------------------------- *
+ * Faster test; testing fewer sizes *
+ * ---------------------------------------------------------------- */
+#if FASTER_TEST
+ /* Compute the actual sizes used for each input size 'i' */
+ for (i = 0; i < 256; i++) {
+ dwasize[i] = 0;
+ ropsize[i] = 0;
+ }
+ for (i = 65; i < 256; i++) {
+ selectComposableSizes(i, &fact1, &fact2);
+ rsize = fact1 * fact2;
+ ropsize[i] = rsize;
+ getExtendedCompositeParameters(i, &n, &extra, &dwasize[i]);
+ }
+
+ /* Use only values where the resulting sizes are equal */
+ for (i = 65; i < 240; i++) {
+ n = 1 + (l_int32)((i - 63) / 62);
+ extra = i - 63 - (n - 1) * 62 + 1;
+ if (extra == 2) continue; /* don't use this one (e.g., i == 126) */
+ if (ropsize[i] == dwasize[i])
+ DoComparisonDwa1(rp, pixs, pix1, pix2, pix3, pix4, pix5, pix6, i);
+ }
+#endif /* FASTER_TEST */
+
+ /* ---------------------------------------------------------------- *
+ * Slower test; testing maximum number of sizes *
+ * ---------------------------------------------------------------- */
+#if SLOWER_TEST
+ lastsize = 0;
+ for (i = 65; i < 199; i++) {
+ getExtendedCompositeParameters(i, &n, &extra, &size);
+ if (size == lastsize) continue;
+ if (size == 126 || size == 188) continue; /* deliberately off by one */
+ lastsize = size;
+ DoComparisonDwa2(rp, pixs, pix1, pix2, pix3, pix4, pix5, pix6, size);
+ }
+#endif /* SLOWER_TEST */
+
+ lept_stderr("\n");
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+}
+
+
+l_int32
+DoComparisonDwa1(L_REGPARAMS *rp,
+ PIX *pixs,
+ PIX *pix1,
+ PIX *pix2,
+ PIX *pix3,
+ PIX *pix4,
+ PIX *pix5,
+ PIX *pix6,
+ l_int32 isize)
+{
+l_int32 fact1, fact2, size;
+
+ selectComposableSizes(isize, &fact1, &fact2);
+ size = fact1 * fact2;
+
+ lept_stderr("..%d..", size);
+
+ if (TIMING) startTimer();
+ pixDilateCompBrickExtendDwa(pix1, pixs, size, 1);
+ pixDilateCompBrickExtendDwa(pix3, pixs, 1, size);
+ pixDilateCompBrickExtendDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixDilateCompBrick(pix2, pixs, size, 1);
+ pixDilateCompBrick(pix4, pixs, 1, size);
+ pixDilateCompBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "dilate", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixErodeCompBrickExtendDwa(pix1, pixs, size, 1);
+ pixErodeCompBrickExtendDwa(pix3, pixs, 1, size);
+ pixErodeCompBrickExtendDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixErodeCompBrick(pix2, pixs, size, 1);
+ pixErodeCompBrick(pix4, pixs, 1, size);
+ pixErodeCompBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "erode", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixOpenCompBrickExtendDwa(pix1, pixs, size, 1);
+ pixOpenCompBrickExtendDwa(pix3, pixs, 1, size);
+ pixOpenCompBrickExtendDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixOpenCompBrick(pix2, pixs, size, 1);
+ pixOpenCompBrick(pix4, pixs, 1, size);
+ pixOpenCompBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "open", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixCloseCompBrickExtendDwa(pix1, pixs, size, 1);
+ pixCloseCompBrickExtendDwa(pix3, pixs, 1, size);
+ pixCloseCompBrickExtendDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixCloseSafeCompBrick(pix2, pixs, size, 1);
+ pixCloseSafeCompBrick(pix4, pixs, 1, size);
+ pixCloseSafeCompBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "close", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ return 0;
+}
+
+
+l_int32
+DoComparisonDwa2(L_REGPARAMS *rp,
+ PIX *pixs,
+ PIX *pix1,
+ PIX *pix2,
+ PIX *pix3,
+ PIX *pix4,
+ PIX *pix5,
+ PIX *pix6,
+ l_int32 size) /* exactly decomposable */
+{
+ lept_stderr("..%d..", size);
+
+ if (TIMING) startTimer();
+ pixDilateCompBrickExtendDwa(pix1, pixs, size, 1);
+ pixDilateCompBrickExtendDwa(pix3, pixs, 1, size);
+ pixDilateCompBrickExtendDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixDilateBrick(pix2, pixs, size, 1);
+ pixDilateBrick(pix4, pixs, 1, size);
+ pixDilateBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "dilate", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixErodeCompBrickExtendDwa(pix1, pixs, size, 1);
+ pixErodeCompBrickExtendDwa(pix3, pixs, 1, size);
+ pixErodeCompBrickExtendDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixErodeBrick(pix2, pixs, size, 1);
+ pixErodeBrick(pix4, pixs, 1, size);
+ pixErodeBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "erode", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixOpenCompBrickExtendDwa(pix1, pixs, size, 1);
+ pixOpenCompBrickExtendDwa(pix3, pixs, 1, size);
+ pixOpenCompBrickExtendDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixOpenBrick(pix2, pixs, size, 1);
+ pixOpenBrick(pix4, pixs, 1, size);
+ pixOpenBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "open", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ if (TIMING) startTimer();
+ pixCloseCompBrickExtendDwa(pix1, pixs, size, 1);
+ pixCloseCompBrickExtendDwa(pix3, pixs, 1, size);
+ pixCloseCompBrickExtendDwa(pix5, pixs, size, size);
+ if (TIMING) lept_stderr("Time Dwa: %7.3f sec\n", stopTimer());
+ if (TIMING) startTimer();
+ pixCloseSafeBrick(pix2, pixs, size, 1);
+ pixCloseSafeBrick(pix4, pixs, 1, size);
+ pixCloseSafeBrick(pix6, pixs, size, size);
+ if (TIMING) lept_stderr("Time Rop: %7.3f sec\n", stopTimer());
+ PixCompareDwa(rp, size, "close", pix1, pix2, pix3, pix4, pix5, pix6);
+
+ return 0;
+}
+
+
+void
+PixCompareDwa(L_REGPARAMS *rp,
+ l_int32 size,
+ const char *type,
+ PIX *pix1,
+ PIX *pix2,
+ PIX *pix3,
+ PIX *pix4,
+ PIX *pix5,
+ PIX *pix6)
+{
+l_int32 same;
+
+ pixEqual(pix1, pix2, &same);
+ regTestCompareValues(rp, TRUE, same, 0);
+ if (!same)
+ lept_stderr("%s (%d, 1) not same\n", type, size);
+ pixEqual(pix3, pix4, &same);
+ regTestCompareValues(rp, TRUE, same, 0);
+ if (!same)
+ lept_stderr("%s (1, %d) not same\n", type, size);
+ pixEqual(pix5, pix6, &same);
+ regTestCompareValues(rp, TRUE, same, 0);
+ if (!same)
+ lept_stderr("%s (%d, %d) not same\n", type, size, size);
+}
+
diff --git a/leptonica/prog/binmorph6_reg.c b/leptonica/prog/binmorph6_reg.c
new file mode 100644
index 00000000..f45a3f9c
--- /dev/null
+++ b/leptonica/prog/binmorph6_reg.c
@@ -0,0 +1,89 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * binmorph6_reg.c
+ *
+ * Miscellaneous morphological operations.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+BOX *box1;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8;
+PIXA *pixa;
+SEL *sel;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Test making sel from a pix */
+ pixa = pixaCreate(10);
+ pix1 = pixRead("feyn-fract.tif");
+ box1 = boxCreate(507, 65, 60, 36);
+ pix2 = pixClipRectangle(pix1, box1, NULL);
+ sel = selCreateFromPix(pix2, 6, 6, "life"); /* 610 hits */
+
+ /* Note how the closing tries to put the negative
+ * of the sel, inverted spatially, in the background. */
+ pix3 = pixDilate(NULL, pix1, sel); /* note the small holes */
+ pix4 = pixOpen(NULL, pix1, sel); /* just the sel */
+ pix5 = pixCloseSafe(NULL, pix1, sel); /* expands small holes in dilate */
+ pix6 = pixSubtract(NULL, pix3, pix1);
+ pix7 = pixSubtract(NULL, pix1, pix5); /* no pixels because closing
+ * is extensive */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 0 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 1 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 2 */
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 3 */
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 4 */
+ regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 5 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ pixaAddPix(pixa, pix6, L_INSERT);
+ pixaAddPix(pixa, pix7, L_INSERT);
+
+ pix8 = pixaDisplayTiledInColumns(pixa, 2, 0.75, 20, 2);
+ regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pix8, 100, 0, NULL, rp->display);
+ pixDestroy(&pix2);
+ pixDestroy(&pix8);
+ pixaDestroy(&pixa);
+ boxDestroy(&box1);
+ selDestroy(&sel);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/blackwhite_reg.c b/leptonica/prog/blackwhite_reg.c
new file mode 100644
index 00000000..2af4dde9
--- /dev/null
+++ b/leptonica/prog/blackwhite_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * blackwhite_reg.c
+ *
+ * Tests functions that handle black and white pixels in an image.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+const char *fnames[11] = {"test1.png", "speckle2.png", "weasel2.4g.png",
+ "speckle4.png", "weasel4.11c.png",
+ "dreyfus8.png", "weasel8.240c.png",
+ "test16.tif", "marge.jpg",
+ "test-cmap-alpha.png", "test-gray-alpha.png"};
+const l_int32 setsize = 11;
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, spp;
+l_uint32 bval, wval;
+PIX *pixs, *pix1, *pix2, *pix3, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Scale each image and add a white boundary */
+ pixa = pixaCreate(setsize);
+ for (i = 0; i < setsize; i++) {
+ pixs = pixRead(fnames[i]);
+ spp = pixGetSpp(pixs);
+ pixGetBlackOrWhiteVal(pixs, L_GET_WHITE_VAL, &wval);
+ pixGetBlackOrWhiteVal(pixs, L_GET_BLACK_VAL, &bval);
+ lept_stderr("d = %d, spp = %d, bval = %x, wval = %x\n",
+ pixGetDepth(pixs), spp, bval, wval);
+ if (spp == 4) /* remove alpha, using white background */
+ pix1 = pixAlphaBlendUniform(pixs, wval);
+ else
+ pix1 = pixClone(pixs);
+ pix2 = pixScaleToSize(pix1, 150, 150);
+ pixGetBlackOrWhiteVal(pix2, L_GET_WHITE_VAL, &wval);
+ pix3 = pixAddBorderGeneral(pix2, 30, 30, 20, 20, wval);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ pixd = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 1, 30, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixDisplayWithTitle(pixd, 0, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ /* Scale each image and add a black boundary */
+ pixa = pixaCreate(setsize);
+ for (i = 0; i < setsize; i++) {
+ pixs = pixRead(fnames[i]);
+ spp = pixGetSpp(pixs);
+ pixGetBlackOrWhiteVal(pixs, L_GET_WHITE_VAL, &wval);
+ pixGetBlackOrWhiteVal(pixs, L_GET_BLACK_VAL, &bval);
+ lept_stderr("d = %d, spp = %d, bval = %x, wval = %x\n",
+ pixGetDepth(pixs), spp, bval, wval);
+ if (spp == 4) /* remove alpha, using white background */
+ pix1 = pixAlphaBlendUniform(pixs, wval);
+ else
+ pix1 = pixClone(pixs);
+ pix2 = pixScaleToSize(pix1, 150, 150);
+ pixGetBlackOrWhiteVal(pixs, L_GET_BLACK_VAL, &bval);
+ pix3 = pixAddBorderGeneral(pix2, 30, 30, 20, 20, bval);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ pixd = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 30, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixDisplayWithTitle(pixd, 1000, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/blend-green1.jpg b/leptonica/prog/blend-green1.jpg
new file mode 100644
index 00000000..4f45cca9
--- /dev/null
+++ b/leptonica/prog/blend-green1.jpg
Binary files differ
diff --git a/leptonica/prog/blend-green2.png b/leptonica/prog/blend-green2.png
new file mode 100644
index 00000000..bc0e7ec0
--- /dev/null
+++ b/leptonica/prog/blend-green2.png
Binary files differ
diff --git a/leptonica/prog/blend-green3.png b/leptonica/prog/blend-green3.png
new file mode 100644
index 00000000..36acd8d6
--- /dev/null
+++ b/leptonica/prog/blend-green3.png
Binary files differ
diff --git a/leptonica/prog/blend-orange.jpg b/leptonica/prog/blend-orange.jpg
new file mode 100644
index 00000000..c4f229a7
--- /dev/null
+++ b/leptonica/prog/blend-orange.jpg
Binary files differ
diff --git a/leptonica/prog/blend-red.png b/leptonica/prog/blend-red.png
new file mode 100644
index 00000000..6656e0ce
--- /dev/null
+++ b/leptonica/prog/blend-red.png
Binary files differ
diff --git a/leptonica/prog/blend-yellow.jpg b/leptonica/prog/blend-yellow.jpg
new file mode 100644
index 00000000..a65678d0
--- /dev/null
+++ b/leptonica/prog/blend-yellow.jpg
Binary files differ
diff --git a/leptonica/prog/blend1_reg.c b/leptonica/prog/blend1_reg.c
new file mode 100644
index 00000000..7294f570
--- /dev/null
+++ b/leptonica/prog/blend1_reg.c
@@ -0,0 +1,328 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * blend1_reg.c
+ *
+ * Regression test for these functions:
+ * pixBlendGray()
+ * pixBlendGrayAdapt()
+ * pixBlendColor()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void GrayBlend(PIX *pixs, PIX *pixb, l_int32 op, l_float32 fract);
+void AdaptiveGrayBlend(PIX *pixs, PIX *pixb, l_float32 fract);
+void ColorBlend(PIX *pixs, PIX *pixb, l_float32 fract);
+PIX *MakeGrayWash(l_int32 w, l_int32 h);
+PIX *MakeColorWash(l_int32 w, l_int32 h, l_int32 color);
+
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pixg, *pixc, *pix1;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Set up blenders */
+ pixg = pixRead("blender8.png");
+ pix1 = pixRead("weasel4.11c.png");
+ pixc = pixRemoveColormap(pix1, REMOVE_CMAP_TO_FULL_COLOR);
+ pixDestroy(&pix1);
+ pixa = pixaCreate(0);
+
+ /* Gray blend (straight) */
+ pixs = pixRead("test24.jpg");
+ pix1 = pixScale(pixs, 0.4, 0.4);
+ GrayBlend(pix1, pixg, L_BLEND_GRAY, 0.3);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 0 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDisplayWithTitle(pix1, 0, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+
+ pixs = pixRead("marge.jpg");
+ GrayBlend(pixs, pixg, L_BLEND_GRAY, 0.2);
+ regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 1 */
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixDisplayWithTitle(pixs, 100, 100, NULL, rp->display);
+
+ pixs = pixRead("marge.jpg");
+ pix1 = pixConvertRGBToLuminance(pixs);
+ GrayBlend(pix1, pixg, L_BLEND_GRAY, 0.2);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 2 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDisplayWithTitle(pix1, 200, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+
+ /* Gray blend (inverse) */
+ pixs = pixRead("test24.jpg");
+ pix1 = pixScale(pixs, 0.4, 0.4);
+ GrayBlend(pix1, pixg, L_BLEND_GRAY_WITH_INVERSE, 0.6);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 3 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDisplayWithTitle(pix1, 300, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+
+ pixs = pixRead("marge.jpg");
+ GrayBlend(pixs, pixg, L_BLEND_GRAY_WITH_INVERSE, 0.6);
+ regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 4 */
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixDisplayWithTitle(pixs, 400, 100, NULL, rp->display);
+
+ pixs = pixRead("marge.jpg");
+ pix1 = pixConvertRGBToLuminance(pixs);
+ GrayBlend(pix1, pixg, L_BLEND_GRAY_WITH_INVERSE, 0.6);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 5 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDisplayWithTitle(pix1, 500, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+
+ pixs = MakeGrayWash(1000, 120);
+ GrayBlend(pixs, pixg, L_BLEND_GRAY_WITH_INVERSE, 0.3);
+ regTestWritePixAndCheck(rp, pixs, IFF_PNG); /* 6 */
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixDisplayWithTitle(pixs, 0, 600, NULL, rp->display);
+
+ pixs = MakeColorWash(1000, 120, COLOR_RED);
+ GrayBlend(pixs, pixg, L_BLEND_GRAY_WITH_INVERSE, 1.0);
+ regTestWritePixAndCheck(rp, pixs, IFF_PNG); /* 7 */
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixDisplayWithTitle(pixs, 0, 750, NULL, rp->display);
+
+ /* Adaptive gray blend */
+ pixs = pixRead("test24.jpg");
+ pix1 = pixScale(pixs, 0.4, 0.4);
+ AdaptiveGrayBlend(pix1, pixg, 0.8);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 8 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDisplayWithTitle(pix1, 600, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+
+ pixs = pixRead("marge.jpg");
+ AdaptiveGrayBlend(pixs, pixg, 0.8);
+ regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 9 */
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixDisplayWithTitle(pixs, 700, 100, NULL, rp->display);
+
+ pix1 = pixConvertRGBToLuminance(pixs);
+ AdaptiveGrayBlend(pix1, pixg, 0.1);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 10 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDisplayWithTitle(pix1, 800, 100, NULL, rp->display);
+
+ pixs = MakeGrayWash(1000, 120);
+ AdaptiveGrayBlend(pixs, pixg, 0.3);
+ regTestWritePixAndCheck(rp, pixs, IFF_PNG); /* 11 */
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixDisplayWithTitle(pixs, 0, 900, NULL, rp->display);
+
+ pixs = MakeColorWash(1000, 120, COLOR_RED);
+ AdaptiveGrayBlend(pixs, pixg, 0.5);
+ regTestWritePixAndCheck(rp, pixs, IFF_PNG); /* 12 */
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixDisplayWithTitle(pixs, 0, 1050, NULL, rp->display);
+
+ /* Color blend */
+ pixs = pixRead("test24.jpg");
+ pix1 = pixScale(pixs, 0.4, 0.4);
+ ColorBlend(pix1, pixc, 0.3);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 13 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDisplayWithTitle(pix1, 900, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+
+ pixs = pixRead("marge.jpg");
+ ColorBlend(pixs, pixc, 0.30);
+ regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 14 */
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixDisplayWithTitle(pixs, 1000, 100, NULL, rp->display);
+
+ pixs = pixRead("marge.jpg");
+ ColorBlend(pixs, pixc, 0.15);
+ regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 15 */
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixDisplayWithTitle(pixs, 1100, 100, NULL, rp->display);
+
+ /* Mosaic all results */
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1700, 1.0, 0, 20, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 16 */
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+
+ pixDestroy(&pixg);
+ pixDestroy(&pixc);
+ return regTestCleanup(rp);
+}
+
+
+void
+GrayBlend(PIX *pixs,
+ PIX *pixb,
+ l_int32 op,
+ l_float32 fract)
+{
+l_int32 i, j, wb, hb, ws, hs, delx, dely, x, y;
+
+ pixGetDimensions(pixs, &ws, &hs, NULL);
+ pixGetDimensions(pixb, &wb, &hb, NULL);
+ delx = wb + 30;
+ dely = hb + 25;
+ x = 200;
+ y = 300;
+ for (i = 0; i < 20; i++) {
+ y = 20 + i * dely;
+ if (y >= hs - hb)
+ continue;
+ for (j = 0; j < 20; j++) {
+ x = 30 + j * delx;
+ if (x >= ws - wb)
+ continue;
+ pixBlendGray(pixs, pixs, pixb, x, y, fract, op, 1, 255);
+ }
+ }
+}
+
+
+void
+AdaptiveGrayBlend(PIX *pixs,
+ PIX *pixb,
+ l_float32 fract)
+{
+l_int32 i, j, wb, hb, ws, hs, delx, dely, x, y;
+
+ pixGetDimensions(pixs, &ws, &hs, NULL);
+ pixGetDimensions(pixb, &wb, &hb, NULL);
+ delx = wb + 30;
+ dely = hb + 25;
+ x = 200;
+ y = 300;
+ for (i = 0; i < 20; i++) {
+ y = 20 + i * dely;
+ if (y >= hs - hb)
+ continue;
+ for (j = 0; j < 20; j++) {
+ x = 30 + j * delx;
+ if (x >= ws - wb)
+ continue;
+ pixBlendGrayAdapt(pixs, pixs, pixb, x, y, fract, 80);
+ }
+ }
+}
+
+
+void
+ColorBlend(PIX *pixs,
+ PIX *pixb,
+ l_float32 fract)
+{
+l_int32 i, j, wb, hb, ws, hs, delx, dely, x, y;
+
+ pixGetDimensions(pixs, &ws, &hs, NULL);
+ pixGetDimensions(pixb, &wb, &hb, NULL);
+ delx = wb + 30;
+ dely = hb + 25;
+ x = 200;
+ y = 300;
+ for (i = 0; i < 20; i++) {
+ y = 20 + i * dely;
+ if (y >= hs - hb)
+ continue;
+ for (j = 0; j < 20; j++) {
+ x = 30 + j * delx;
+ if (x >= ws - wb)
+ continue;
+ pixBlendColor(pixs, pixs, pixb, x, y, fract, 1, 255);
+ }
+ }
+}
+
+
+PIX *
+MakeGrayWash(l_int32 w,
+ l_int32 h)
+{
+l_int32 i, j, wpl, val;
+l_uint32 *data, *line;
+PIX *pixd;
+
+ pixd = pixCreate(w, h, 8);
+ data = pixGetData(pixd);
+ wpl = pixGetWpl(pixd);
+ for (i = 0; i < h; i++) {
+ line = data + i * wpl;
+ for (j = 0; j < w; j++) {
+ val = (j * 255) / w;
+ SET_DATA_BYTE(line, j, val);
+ }
+ }
+ return pixd;
+}
+
+
+PIX *
+MakeColorWash(l_int32 w,
+ l_int32 h,
+ l_int32 color)
+{
+l_int32 i, j, wpl;
+l_uint32 val;
+l_uint32 *data, *line;
+PIX *pixd;
+
+ pixd = pixCreate(w, h, 32);
+ data = pixGetData(pixd);
+ wpl = pixGetWpl(pixd);
+ for (i = 0; i < h; i++) {
+ line = data + i * wpl;
+ for (j = 0; j < w; j++) {
+ if (color == COLOR_RED)
+ val = ((j * 255) / w) << L_GREEN_SHIFT |
+ ((j * 255) / w) << L_BLUE_SHIFT |
+ 255 << L_RED_SHIFT;
+ else if (color == COLOR_GREEN)
+ val = ((j * 255) / w) << L_RED_SHIFT |
+ ((j * 255) / w) << L_BLUE_SHIFT |
+ 255 << L_GREEN_SHIFT;
+ else
+ val = ((j * 255) / w) << L_RED_SHIFT |
+ ((j * 255) / w) << L_GREEN_SHIFT |
+ 255 << L_BLUE_SHIFT;
+ line[j] = val;
+ }
+ }
+ return pixd;
+}
+
diff --git a/leptonica/prog/blend2_reg.c b/leptonica/prog/blend2_reg.c
new file mode 100644
index 00000000..fb10846e
--- /dev/null
+++ b/leptonica/prog/blend2_reg.c
@@ -0,0 +1,182 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * blend2_reg.c
+ *
+ * Regression test for this function:
+ * pixBlendWithGrayMask()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* In case libpng is not enabled */
+static const char fname_png[64] = "/tmp/lept/regout/blend2.14.png";
+static const char fname_bmp[64] = "/tmp/lept/regout/blend2.14.bmp";
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, w1, h1, w2, h2, w, h;
+BOX *box1, *box2;
+PIX *pixg, *pixs1, *pixs2, *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* --- Set up the 8 bpp blending image --- */
+ pixg = pixCreate(660, 500, 8);
+ for (i = 0; i < 500; i++)
+ for (j = 0; j < 660; j++)
+ pixSetPixel(pixg, j, i, (l_int32)(0.775 * j) % 256);
+
+ /* --- Set up the initial color images to be blended together --- */
+ pixs1 = pixRead("wyom.jpg");
+ pixs2 = pixRead("fish24.jpg");
+ pixGetDimensions(pixs1, &w, &h, NULL);
+ pixGetDimensions(pixs1, &w1, &h1, NULL);
+ pixGetDimensions(pixs2, &w2, &h2, NULL);
+ h = L_MIN(h1, h2);
+ w = L_MIN(w1, w2);
+ box1 = boxCreate(0, 0, w, h);
+ box2 = boxCreate(0, 300, 660, 500);
+ pix1 = pixClipRectangle(pixs1, box1, NULL);
+ pix2 = pixClipRectangle(pixs2, box2, NULL);
+ pixDestroy(&pixs1);
+ pixDestroy(&pixs2);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+
+ /* --- Blend 2 rgb images --- */
+ pixa = pixaCreate(0);
+ pix3 = pixBlendWithGrayMask(pix1, pix2, pixg, 50, 50);
+ pixaAddPix(pixa, pix1, L_COPY);
+ pixaAddPix(pixa, pix2, L_COPY);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ regTestWritePixAndCheck(rp, pixg, IFF_JFIF_JPEG); /* 0 */
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 1 */
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 2 */
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 3 */
+
+ /* --- Blend 2 grayscale images --- */
+ pix3 = pixConvertRGBToLuminance(pix1);
+ pix4 = pixConvertRGBToLuminance(pix2);
+ pix5 = pixBlendWithGrayMask(pix3, pix4, pixg, 50, 50);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 4 */
+ regTestWritePixAndCheck(rp, pix4, IFF_JFIF_JPEG); /* 5 */
+ regTestWritePixAndCheck(rp, pix5, IFF_JFIF_JPEG); /* 6 */
+
+ /* --- Blend a colormap image and an rgb image --- */
+ pix3 = pixFixedOctcubeQuantGenRGB(pix2, 2);
+ pix4 = pixBlendWithGrayMask(pix1, pix3, pixg, 50, 50);
+ pixaAddPix(pixa, pix1, L_COPY);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 7 */
+ regTestWritePixAndCheck(rp, pix4, IFF_JFIF_JPEG); /* 8 */
+
+ /* --- Blend a colormap image and a grayscale image --- */
+ pix3 = pixConvertRGBToLuminance(pix1);
+ pix4 = pixFixedOctcubeQuantGenRGB(pix2, 2);
+ pix5 = pixBlendWithGrayMask(pix3, pix4, pixg, 50, 50);
+ pixaAddPix(pixa, pix3, L_COPY);
+ pixaAddPix(pixa, pix4, L_COPY);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 9 */
+ regTestWritePixAndCheck(rp, pix4, IFF_JFIF_JPEG); /* 10 */
+ regTestWritePixAndCheck(rp, pix5, IFF_JFIF_JPEG); /* 11 */
+ pix5 = pixBlendWithGrayMask(pix3, pix4, pixg, -100, -100);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix5, IFF_JFIF_JPEG); /* 12 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* --------- Test png read/write with alpha channel --------- */
+ /* First make pix2, using pixg as the alpha channel */
+ pix1 = pixRead("fish24.jpg");
+ box1 = boxCreate(0, 300, 660, 500);
+ pix2 = pixClipRectangle(pix1, box1, NULL);
+ boxDestroy(&box1);
+ pixaAddPix(pixa, pix2, L_COPY);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 13 */
+ pixSetRGBComponent(pix2, pixg, L_ALPHA_CHANNEL);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 14 */
+
+ /* To see the alpha channel, blend with a black image */
+ pix3 = pixCreate(660, 500, 32);
+ pix4 = pixBlendWithGrayMask(pix3, pix2, NULL, 0, 0);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ regTestWritePixAndCheck(rp, pix4, IFF_JFIF_JPEG); /* 15 */
+
+ /* Read the RGBA image #14 back */
+#if defined(HAVE_LIBPNG)
+ pix4 = pixRead(fname_png);
+#else
+ pix4 = pixRead(fname_bmp);
+#endif
+
+ /* Make sure that the alpha channel image hasn't changed */
+ pix5 = pixGetRGBComponent(pix4, L_ALPHA_CHANNEL);
+ regTestComparePix(rp, pixg, pix5); /* 16 */
+ pixDestroy(&pix5);
+
+ /* Blend again with a black image */
+ pix5 = pixBlendWithGrayMask(pix3, pix4, NULL, 0, 0);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix5, IFF_JFIF_JPEG); /* 17 */
+
+ /* Blend with a white image */
+ pixSetAll(pix3);
+ pix5 = pixBlendWithGrayMask(pix3, pix4, NULL, 0, 0);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix5, IFF_JFIF_JPEG); /* 18 */
+ pixDestroy(&pixg);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Display results */
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 1.0, 40, 2);
+ pixDisplayWithTitle(pix1, 100, 100, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 19 */
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/blend3_reg.c b/leptonica/prog/blend3_reg.c
new file mode 100644
index 00000000..1f3c35e6
--- /dev/null
+++ b/leptonica/prog/blend3_reg.c
@@ -0,0 +1,216 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+
+/*
+ * blend3_reg.c
+ *
+ * 42 results: 6 input image combinations * 7 blendings
+ */
+
+#include "allheaders.h"
+
+#define X 140
+#define Y 40
+#define ALL 1
+
+static PIX *BlendTest(const char *file1, const char *file2, l_float32 fract);
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixt, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixa = pixaCreate(6);
+
+ pixt = BlendTest("marge.jpg", "feyn-word.tif", 0.5);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_JFIF_JPEG); /* 0 */
+ pixDisplayWithTitle(pixt, 0, 0, NULL, rp->display);
+
+ pixt = BlendTest("marge.jpg", "weasel8.png", 0.3);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_JFIF_JPEG); /* 1 */
+ pixDisplayWithTitle(pixt, 0, 200, NULL, rp->display);
+
+ pixt = BlendTest("marge.jpg", "weasel8.240c.png", 0.3);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_JFIF_JPEG); /* 2 */
+ pixDisplayWithTitle(pixt, 0, 400, NULL, rp->display);
+
+ pixt = BlendTest("test8.jpg", "feyn-word.tif", 0.5);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_JFIF_JPEG); /* 3 */
+ pixDisplayWithTitle(pixt, 0, 600, NULL, rp->display);
+
+ pixt = BlendTest("test8.jpg", "weasel8.png", 0.5);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_JFIF_JPEG); /* 4 */
+ pixDisplayWithTitle(pixt, 0, 800, NULL, rp->display);
+
+ pixt = BlendTest("test8.jpg", "weasel8.240c.png", 0.6);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_JFIF_JPEG); /* 5 */
+ pixDisplayWithTitle(pixt, 0, 1000, NULL, rp->display);
+
+ pixd = pixaDisplayTiledInRows(pixa, 32, 1800, 1.0, 0, 20, 2);
+ pixWrite("/tmp/lept/regout/blendall.jpg", pixd, IFF_JFIF_JPEG);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+
+ return regTestCleanup(rp);
+}
+
+
+static PIX *
+BlendTest(const char *file1,
+ const char *file2,
+ l_float32 fract)
+{
+l_int32 d1, d2;
+PIX *pixs1, *pixs2, *pix1, *pix2, *pix3, *pix4, *pix5, *pixd;
+PIXA *pixa;
+
+ pixs1 = pixRead(file1);
+ pixs2 = pixRead(file2);
+ d1 = pixGetDepth(pixs1);
+ d2 = pixGetDepth(pixs2);
+ pixa = pixaCreate(7);
+
+#if ALL
+ if (d1 == 1) {
+ pix1 = pixBlend(pixs1, pixs2, X, Y, fract);
+ pix2 = pixBlend(pix1, pixs2, X, Y + 60, fract);
+ pix3 = pixBlend(pix2, pixs2, X, Y + 120, fract);
+ pix4 = pixBlend(pix3, pixs2, X, Y + 180, fract);
+ pix5 = pixBlend(pix4, pixs2, X, Y + 240, fract);
+ pixd = pixBlend(pix5, pixs2, X, Y + 300, fract);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ } else {
+ pix1 = pixBlend(pixs1, pixs2, X, Y, fract);
+ pix2 = pixBlend(pix1, pixs2, X + 80, Y + 80, fract);
+ pix3 = pixBlend(pix2, pixs2, X + 160, Y + 160, fract);
+ pix4 = pixBlend(pix3, pixs2, X + 240, Y + 240, fract);
+ pix5 = pixBlend(pix4, pixs2, X + 320, Y + 320, fract);
+ pixd = pixBlend(pix5, pixs2, X + 360, Y + 360, fract);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ }
+ pixaAddPix(pixa, pixd, L_INSERT);
+#endif
+
+#if ALL
+ /* Gray blend */
+ if (d2 >= 8)
+ pixSnapColor(pixs2, pixs2, 0xff, 0xff, 50);
+ pixd = pixBlendGray(NULL, pixs1, pixs2, 200, 100, fract,
+ L_BLEND_GRAY, 1, 0xff);
+ pixBlendGray(pixd, pixd, pixs2, 200, 200, fract,
+ L_BLEND_GRAY, 1, 0xff);
+ pixBlendGray(pixd, pixd, pixs2, 200, 260, fract,
+ L_BLEND_GRAY, 1, 0xff);
+ pixBlendGray(pixd, pixd, pixs2, 200, 340, fract,
+ L_BLEND_GRAY, 1, 0xff);
+ pixaAddPix(pixa, pixd, L_INSERT);
+#endif
+
+#if ALL /* Gray blend (with inverse) */
+ if (d2 >= 8)
+ pixSnapColor(pixs2, pixs2, 0xff, 0xff, 50);
+ pixd = pixBlendGray(NULL, pixs1, pixs2, 200, 100, fract,
+ L_BLEND_GRAY_WITH_INVERSE, 1, 0xff);
+ pixBlendGray(pixd, pixd, pixs2, 200, 200, fract,
+ L_BLEND_GRAY_WITH_INVERSE, 1, 0xff);
+ pixBlendGray(pixd, pixd, pixs2, 200, 260, fract,
+ L_BLEND_GRAY_WITH_INVERSE, 1, 0xff);
+ pixBlendGray(pixd, pixd, pixs2, 200, 340, fract,
+ L_BLEND_GRAY_WITH_INVERSE, 1, 0xff);
+ pixaAddPix(pixa, pixd, L_INSERT);
+#endif
+
+#if ALL /* Blend Gray for robustness */
+ if (d2 >= 8)
+ pixSnapColor(pixs2, pixs2, 0xff, 0xff, 50);
+ pixd = pixBlendGrayInverse(NULL, pixs1, pixs2, 200, 100, fract);
+ pixBlendGrayInverse(pixd, pixd, pixs2, 200, 200, fract);
+ pixBlendGrayInverse(pixd, pixd, pixs2, 200, 260, fract);
+ pixBlendGrayInverse(pixd, pixd, pixs2, 200, 340, fract);
+ pixaAddPix(pixa, pixd, L_INSERT);
+#endif
+
+#if ALL /* Blend Gray adapted */
+ if (d2 >= 8)
+ pixSnapColor(pixs2, pixs2, 0xff, 0xff, 50);
+ pixd = pixBlendGrayAdapt(NULL, pixs1, pixs2, 200, 100, fract, 120);
+ pixBlendGrayAdapt(pixd, pixd, pixs2, 200, 200, fract, 120);
+ pixBlendGrayAdapt(pixd, pixd, pixs2, 200, 260, fract, 120);
+ pixBlendGrayAdapt(pixd, pixd, pixs2, 200, 340, fract, 120);
+ pixaAddPix(pixa, pixd, L_INSERT);
+#endif
+
+#if ALL /* Blend color */
+ if (d2 >= 8)
+ pixSnapColor(pixs2, pixs2, 0xffffff00, 0xffffff00, 50);
+ pixd = pixBlendColor(NULL, pixs1, pixs2, 200, 100, fract, 1, 0xffffff00);
+ pixBlendColor(pixd, pixd, pixs2, 200, 200, fract, 1, 0xffffff00);
+ pixBlendColor(pixd, pixd, pixs2, 200, 260, fract, 1, 0xffffff00);
+ pixBlendColor(pixd, pixd, pixs2, 200, 340, fract, 1, 0xffffff00);
+ pixaAddPix(pixa, pixd, L_INSERT);
+#endif
+
+#if ALL /* Blend color by channel */
+ if (d2 >= 8)
+ pixSnapColor(pixs2, pixs2, 0xffffff00, 0xffffff00, 50);
+ pixd = pixBlendColorByChannel(NULL, pixs1, pixs2, 200, 100, 1.6 * fract,
+ fract, 0.5 * fract, 1, 0xffffff00);
+ pixBlendColorByChannel(pixd, pixd, pixs2, 200, 200, 1.2 * fract,
+ fract, 0.2 * fract, 1, 0xffffff00);
+ pixBlendColorByChannel(pixd, pixd, pixs2, 200, 260, 1.6 * fract,
+ 1.8 * fract, 0.3 * fract, 1, 0xffffff00);
+ pixBlendColorByChannel(pixd, pixd, pixs2, 200, 340, 0.4 * fract,
+ 1.3 * fract, 1.8 * fract, 1, 0xffffff00);
+ pixaAddPix(pixa, pixd, L_INSERT);
+#endif
+
+ pixd = pixaDisplayTiledInRows(pixa, 32, 2500, 0.5, 0, 20, 2);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs1);
+ pixDestroy(&pixs2);
+ return pixd;
+}
+
diff --git a/leptonica/prog/blend4_reg.c b/leptonica/prog/blend4_reg.c
new file mode 100644
index 00000000..d923f72c
--- /dev/null
+++ b/leptonica/prog/blend4_reg.c
@@ -0,0 +1,102 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * blend4_reg.c
+ *
+ * Regression test for this function:
+ * pixAddAlphaToBlend()
+ *
+ * Blending is done using pixBlendWithGrayMask()
+ */
+
+#include "allheaders.h"
+
+static const char *blenders[] =
+ {"feyn-word.tif", "weasel4.16c.png", "karen8.jpg"};
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, w, h;
+PIX *pix0, *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixa = pixaCreate(0);
+
+ /* Blending on a light image */
+ pix1 = pixRead("fish24.jpg");
+ pixGetDimensions(pix1, &w, &h, NULL);
+ for (i = 0; i < 3; i++) {
+ pix2 = pixRead(blenders[i]);
+ if (i == 2) {
+ pix3 = pixScale(pix2, 0.5, 0.5);
+ pixDestroy(&pix2);
+ pix2 = pix3;
+ }
+ pix3 = pixAddAlphaToBlend(pix2, 0.3, 0);
+ pix4 = pixMirroredTiling(pix3, w, h);
+ pix5 = pixBlendWithGrayMask(pix1, pix4, NULL, 0, 0);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix5, IFF_JFIF_JPEG); /* 0 - 2 */
+ pixDisplayWithTitle(pix5, 200 * i, 0, NULL, rp->display);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ }
+ pixDestroy(&pix1);
+
+ /* Blending on a dark image */
+ pix0 = pixRead("karen8.jpg");
+ pix1 = pixScale(pix0, 2.0, 2.0);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ for (i = 0; i < 2; i++) {
+ pix2 = pixRead(blenders[i]);
+ pix3 = pixAddAlphaToBlend(pix2, 0.3, 1);
+ pix4 = pixMirroredTiling(pix3, w, h);
+ pix5 = pixBlendWithGrayMask(pix1, pix4, NULL, 0, 0);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix5, IFF_JFIF_JPEG); /* 3 - 4 */
+ pixDisplayWithTitle(pix5, 600 + 200 * i, 0, NULL, rp->display);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ }
+
+ pixaConvertToPdf(pixa, 100, 1.0, L_JPEG_ENCODE, 0,
+ "Blendings: blend4_reg", "/tmp/lept/regout/blend.pdf");
+ L_INFO("Output pdf: /tmp/lept/regout/blend.pdf\n", rp->testname);
+ pixDestroy(&pix0);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/blend5_reg.c b/leptonica/prog/blend5_reg.c
new file mode 100644
index 00000000..3da53ff0
--- /dev/null
+++ b/leptonica/prog/blend5_reg.c
@@ -0,0 +1,182 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * blend5_reg.c
+ *
+ * Regression test for these functions in blend.c:
+ * - pixSnapColor(): used here to color the background on images
+ * in index.html
+ * - pixLinearEdgeFade()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_uint32 LEPTONICA_YELLOW = 0xffffe400;
+
+int main(int argc,
+ char **argv)
+{
+l_uint32 val32;
+PIX *pixs, *pix1, *pix2;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixa = pixaCreate(0);
+
+ /* First, snap the color directly on the input rgb image. */
+ pixs = pixRead("Leptonica.jpg");
+ pixaAddPix(pixa, pixs, L_COPY);
+ pixDisplayWithTitle(pixs, 0, 0, NULL, rp->display);
+ pix1 = pixSnapColor(NULL, pixs, 0xffffff00, LEPTONICA_YELLOW, 30);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 0 */
+ pixDisplayWithTitle(pix1, 480, 0, NULL, rp->display);
+
+ /* Then make a colormapped version and snap the color */
+ pix1 = pixOctreeQuantNumColors(pixs, 250, 0);
+ pixaAddPix(pixa, pix1, L_COPY);
+ pixSnapColor(pix1, pix1, 0xffffff00, LEPTONICA_YELLOW, 30);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix1, 880, 0, NULL, rp->display);
+ pixDestroy(&pixs);
+
+ /* Set the background of the google searchbox to yellow.
+ * The input image is colormapped with all 256 colors used. */
+ pixs = pixRead("google-searchbox.png");
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixDisplayWithTitle(pixs, 0, 200, NULL, rp->display);
+ pix1 = pixSnapColor(NULL, pixs, 0xffffff00, LEPTONICA_YELLOW, 30);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix1, 220, 200, NULL, rp->display);
+
+ /* A couple of more, setting pixels near white to strange colors */
+ pixs = pixRead("weasel4.11c.png");
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixDisplayWithTitle(pixs, 0, 300, NULL, rp->display);
+ pix1 = pixSnapColor(NULL, pixs, 0xfefefe00, 0x80800000, 50);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pix1, 200, 300, NULL, rp->display);
+
+ pixs = pixRead("wyom.jpg");
+ pix1 = pixFixedOctcubeQuant256(pixs, 0);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix1, 0, 450, NULL, rp->display);
+ pix2 = pixSnapColor(NULL, pix1, 0xf0f0f000, 0x80008000, 100);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix2, 900, 450, NULL, rp->display);
+ pixDestroy(&pixs);
+
+ /* --- Display results --- */
+ pix1 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 6 */
+ pixDisplayWithTitle(pix1, 500, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ /* Test linear fade to black */
+ composeRGBPixel(90, 170, 145, &val32);
+ pix1 = pixCreate(300, 300, 32);
+ pixSetAllArbitrary(pix1, val32);
+ pixLinearEdgeFade(pix1, L_FROM_LEFT, L_BLEND_TO_BLACK, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_RIGHT, L_BLEND_TO_BLACK, 0.5, 0.8);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 7 */
+ pixDisplayWithTitle(pix1, 900, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pix1 = pixCreate(300, 300, 32);
+ pixSetAllArbitrary(pix1, val32);
+ pixLinearEdgeFade(pix1, L_FROM_TOP, L_BLEND_TO_BLACK, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_BOT, L_BLEND_TO_BLACK, 0.5, 0.8);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 8 */
+ pixDisplayWithTitle(pix1, 1250, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pix1 = pixCreate(300, 300, 32);
+ pixSetAllArbitrary(pix1, val32);
+ pixLinearEdgeFade(pix1, L_FROM_LEFT, L_BLEND_TO_BLACK, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_RIGHT, L_BLEND_TO_BLACK, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_TOP, L_BLEND_TO_BLACK, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_BOT, L_BLEND_TO_BLACK, 0.5, 0.8);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 9 */
+ pixDisplayWithTitle(pix1, 1600, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pix1 = pixCreate(300, 300, 8); /* 8 bpp */
+ pixSetAll(pix1);
+ pixLinearEdgeFade(pix1, L_FROM_LEFT, L_BLEND_TO_BLACK, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_RIGHT, L_BLEND_TO_BLACK, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_TOP, L_BLEND_TO_BLACK, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_BOT, L_BLEND_TO_BLACK, 0.5, 0.8);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10 */
+ pixDisplayWithTitle(pix1, 1950, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ /* Test linear fade to white */
+ composeRGBPixel(170, 110, 200, &val32);
+ pix1 = pixCreate(300, 300, 32);
+ pixSetAllArbitrary(pix1, val32);
+ pixLinearEdgeFade(pix1, L_FROM_LEFT, L_BLEND_TO_WHITE, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_RIGHT, L_BLEND_TO_WHITE, 0.5, 0.8);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 11 */
+ pixDisplayWithTitle(pix1, 900, 380, NULL, rp->display);
+ pixDestroy(&pix1);
+ pix1 = pixCreate(300, 300, 32);
+ pixSetAllArbitrary(pix1, val32);
+ pixLinearEdgeFade(pix1, L_FROM_TOP, L_BLEND_TO_WHITE, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_BOT, L_BLEND_TO_WHITE, 0.5, 0.8);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 12 */
+ pixDisplayWithTitle(pix1, 1250, 380, NULL, rp->display);
+ pixDestroy(&pix1);
+ pix1 = pixCreate(300, 300, 32);
+ pixSetAllArbitrary(pix1, val32);
+ pixLinearEdgeFade(pix1, L_FROM_LEFT, L_BLEND_TO_WHITE, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_RIGHT, L_BLEND_TO_WHITE, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_TOP, L_BLEND_TO_WHITE, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_BOT, L_BLEND_TO_WHITE, 0.5, 0.8);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 13 */
+ pixDisplayWithTitle(pix1, 1600, 380, NULL, rp->display);
+ pixDestroy(&pix1);
+ pix1 = pixCreate(300, 300, 8); /* 8 bpp */
+ pixLinearEdgeFade(pix1, L_FROM_LEFT, L_BLEND_TO_WHITE, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_RIGHT, L_BLEND_TO_WHITE, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_TOP, L_BLEND_TO_WHITE, 0.5, 0.8);
+ pixLinearEdgeFade(pix1, L_FROM_BOT, L_BLEND_TO_WHITE, 0.5, 0.8);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 14 */
+ pixDisplayWithTitle(pix1, 1950, 380, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/blendcmaptest.c b/leptonica/prog/blendcmaptest.c
new file mode 100644
index 00000000..00a05099
--- /dev/null
+++ b/leptonica/prog/blendcmaptest.c
@@ -0,0 +1,112 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * blendcmaptest.c
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 NX = 4;
+static const l_int32 NY = 5;
+static const l_float32 FADE_FRACTION = 0.75;
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, sindex, wb, hb, ws, hs, delx, dely, x, y, y0;
+PIX *pixs, *pixb, *pix1, *pix2;
+PIXA *pixa;
+PIXCMAP *cmap;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/blend");
+ pixa = pixaCreate(0);
+
+ pixs = pixRead("rabi.png"); /* blendee */
+ pixb = pixRead("weasel4.11c.png"); /* blender */
+
+ /* Fade the blender */
+ pixcmapShiftIntensity(pixGetColormap(pixb), FADE_FRACTION);
+
+ /* Downscale the input */
+ wb = pixGetWidth(pixb);
+ hb = pixGetHeight(pixb);
+ pix1 = pixScaleToGray4(pixs);
+
+ /* Threshold to 5 levels, 4 bpp */
+ ws = pixGetWidth(pix1);
+ hs = pixGetHeight(pix1);
+ pix2 = pixThresholdTo4bpp(pix1, 5, 1);
+ pixaAddPix(pixa, pix2, L_COPY);
+ pixaAddPix(pixa, pixb, L_COPY);
+ cmap = pixGetColormap(pix2);
+ pixcmapWriteStream(stderr, cmap);
+
+ /* Overwrite the white pixels (at sindex in pix2) */
+ pixcmapGetIndex(cmap, 255, 255, 255, &sindex);
+
+ /* Blend the weasel 20 times */
+ delx = ws / NX;
+ dely = hs / NY;
+ for (i = 0; i < NY; i++) {
+ y = 20 + i * dely;
+ if (y >= hs + hb)
+ continue;
+ for (j = 0; j < NX; j++) {
+ x = 30 + j * delx;
+ y0 = y;
+ if (j & 1) {
+ y0 = y + dely / 2;
+ if (y0 >= hs + hb)
+ continue;
+ }
+ if (x >= ws + wb)
+ continue;
+ pixBlendCmap(pix2, pixb, x, y0, sindex);
+ }
+ }
+
+ pixaAddPix(pixa, pix2, L_COPY);
+ cmap = pixGetColormap(pix2);
+ pixcmapWriteStream(stderr, cmap);
+ lept_stderr("Writing to: /tmp/lept/blend/blendcmap.pdf\n");
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, "cmap-blendtest",
+ "/tmp/lept/blend/blendcmap.pdf");
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixb);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa);
+ return 0;
+}
+
diff --git a/leptonica/prog/blender1.tif b/leptonica/prog/blender1.tif
new file mode 100644
index 00000000..b570706d
--- /dev/null
+++ b/leptonica/prog/blender1.tif
Binary files differ
diff --git a/leptonica/prog/blender8.png b/leptonica/prog/blender8.png
new file mode 100644
index 00000000..122802ca
--- /dev/null
+++ b/leptonica/prog/blender8.png
Binary files differ
diff --git a/leptonica/prog/blendtext.tif b/leptonica/prog/blendtext.tif
new file mode 100644
index 00000000..b570706d
--- /dev/null
+++ b/leptonica/prog/blendtext.tif
Binary files differ
diff --git a/leptonica/prog/bois-2.tif b/leptonica/prog/bois-2.tif
new file mode 100644
index 00000000..c6820674
--- /dev/null
+++ b/leptonica/prog/bois-2.tif
Binary files differ
diff --git a/leptonica/prog/bois-3.tif b/leptonica/prog/bois-3.tif
new file mode 100644
index 00000000..fc85fd94
--- /dev/null
+++ b/leptonica/prog/bois-3.tif
Binary files differ
diff --git a/leptonica/prog/bois-4.tif b/leptonica/prog/bois-4.tif
new file mode 100644
index 00000000..9cef270f
--- /dev/null
+++ b/leptonica/prog/bois-4.tif
Binary files differ
diff --git a/leptonica/prog/bois-5.tif b/leptonica/prog/bois-5.tif
new file mode 100644
index 00000000..b6314233
--- /dev/null
+++ b/leptonica/prog/bois-5.tif
Binary files differ
diff --git a/leptonica/prog/books_logo.png b/leptonica/prog/books_logo.png
new file mode 100644
index 00000000..92f901d9
--- /dev/null
+++ b/leptonica/prog/books_logo.png
Binary files differ
diff --git a/leptonica/prog/boxa1.ba b/leptonica/prog/boxa1.ba
new file mode 100644
index 00000000..d87b6b07
--- /dev/null
+++ b/leptonica/prog/boxa1.ba
@@ -0,0 +1,47 @@
+
+Boxa Version 2
+Number of boxes = 44
+ Box[0]: x = 282, y = 0, w = 1832, h = 2697
+ Box[1]: x = 479, y = 410, w = 1862, h = 2322
+ Box[2]: x = 216, y = 0, w = 1746, h = 2732
+ Box[3]: x = 514, y = 409, w = 1711, h = 2248
+ Box[4]: x = 204, y = 0, w = 1769, h = 2732
+ Box[5]: x = 508, y = 409, w = 1752, h = 2248
+ Box[6]: x = 199, y = 0, w = 1769, h = 2721
+ Box[7]: x = 508, y = 409, w = 1728, h = 2248
+ Box[8]: x = 210, y = 0, w = 1757, h = 2721
+ Box[9]: x = 496, y = 409, w = 1734, h = 2248
+ Box[10]: x = 216, y = 0, w = 1752, h = 2721
+ Box[11]: x = 502, y = 409, w = 1762, h = 2248
+ Box[12]: x = 193, y = 0, w = 1781, h = 2727
+ Box[13]: x = 502, y = 409, w = 1763, h = 2248
+ Box[14]: x = 204, y = 0, w = 1763, h = 2721
+ Box[15]: x = 502, y = 409, w = 1766, h = 2236
+ Box[16]: x = 187, y = 0, w = 1827, h = 2721
+ Box[17]: x = 508, y = 409, w = 1762, h = 2236
+ Box[18]: x = 199, y = 0, w = 1775, h = 2715
+ Box[19]: x = 508, y = 409, w = 1763, h = 2248
+ Box[20]: x = 187, y = 0, w = 1833, h = 2337
+ Box[21]: x = 473, y = 18, w = 1792, h = 2341
+ Box[22]: x = 239, y = 0, w = 1775, h = 2337
+ Box[23]: x = 473, y = 18, w = 1792, h = 2347
+ Box[24]: x = 245, y = 0, w = 1775, h = 2343
+ Box[25]: x = 473, y = 23, w = 1792, h = 2347
+ Box[26]: x = 234, y = 0, w = 1781, h = 2325
+ Box[27]: x = 473, y = 18, w = 1792, h = 2353
+ Box[28]: x = 234, y = 0, w = 1781, h = 2721
+ Box[29]: x = 496, y = 397, w = 1798, h = 2265
+ Box[30]: x = 216, y = 0, w = 1746, h = 2721
+ Box[31]: x = 496, y = 397, w = 1798, h = 2265
+ Box[32]: x = 216, y = 0, w = 1746, h = 2709
+ Box[33]: x = 485, y = 397, w = 1810, h = 2265
+ Box[34]: x = 228, y = 0, w = 1734, h = 2703
+ Box[35]: x = 490, y = 397, w = 1792, h = 2248
+ Box[36]: x = 234, y = 0, w = 1746, h = 2639
+ Box[37]: x = 479, y = 347, w = 1806, h = 2403
+ Box[38]: x = 234, y = 0, w = 1746, h = 2639
+ Box[39]: x = 485, y = 340, w = 1801, h = 2404
+ Box[40]: x = 234, y = 0, w = 1746, h = 2639
+ Box[41]: x = 479, y = 343, w = 1808, h = 2407
+ Box[42]: x = 234, y = 0, w = 1746, h = 2651
+ Box[43]: x = 479, y = 336, w = 1810, h = 2408
diff --git a/leptonica/prog/boxa1_reg.c b/leptonica/prog/boxa1_reg.c
new file mode 100644
index 00000000..6f29badb
--- /dev/null
+++ b/leptonica/prog/boxa1_reg.c
@@ -0,0 +1,161 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * boxa1_reg.c
+ *
+ * This carries out various operations on boxa, including
+ * region comparison, transforms and display.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static PIX *DisplayBoxa(BOXA *boxa);
+
+int main(int argc,
+ char **argv)
+{
+l_uint8 *data1, *data2;
+l_int32 same;
+size_t size1, size2;
+l_float32 diffarea, diffxor;
+BOX *box;
+BOXA *boxa1, *boxa2, *boxa3;
+PIX *pix1;
+PTA *pta;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/boxa");
+
+ /* Make a boxa and display its contents */
+ boxa1 = boxaCreate(6);
+ box = boxCreate(60, 60, 40, 20);
+ boxaAddBox(boxa1, box, L_INSERT);
+ box = boxCreate(120, 50, 20, 50);
+ boxaAddBox(boxa1, box, L_INSERT);
+ box = boxCreate(50, 140, 46, 60);
+ boxaAddBox(boxa1, box, L_INSERT);
+ box = boxCreate(166, 130, 64, 28);
+ boxaAddBox(boxa1, box, L_INSERT);
+ box = boxCreate(64, 224, 44, 34);
+ boxaAddBox(boxa1, box, L_INSERT);
+ box = boxCreate(117, 206, 26, 74);
+ boxaAddBox(boxa1, box, L_INSERT);
+ pix1 = DisplayBoxa(boxa1);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ boxaCompareRegions(boxa1, boxa1, 100, &same, &diffarea, &diffxor, NULL);
+ regTestCompareValues(rp, 1, same, 0.0); /* 1 */
+ regTestCompareValues(rp, 0.0, diffarea, 0.0); /* 2 */
+ regTestCompareValues(rp, 0.0, diffxor, 0.0); /* 3 */
+
+ boxa2 = boxaTransform(boxa1, -13, -13, 1.0, 1.0);
+ boxaCompareRegions(boxa1, boxa2, 10, &same, &diffarea, &diffxor, NULL);
+ regTestCompareValues(rp, 1, same, 0.0); /* 4 */
+ regTestCompareValues(rp, 0.0, diffarea, 0.0); /* 5 */
+ regTestCompareValues(rp, 0.0, diffxor, 0.0); /* 6 */
+ boxaDestroy(&boxa2);
+
+ pta = boxaExtractCorners(boxa1, L_UPPER_LEFT);
+ boxa2 = boxaShiftWithPta(boxa1, pta, 1); /* shift */
+ boxa3 = boxaShiftWithPta(boxa2, pta, -1); /* inverse shift */
+ boxaWriteMem(&data1, &size1, boxa1);
+ boxaWriteMem(&data2, &size2, boxa3);
+ regTestCompareStrings(rp, data1, size1, data2, size2); /* 7 */
+ ptaDestroy(&pta);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+ lept_free(data1);
+ lept_free(data2);
+
+ boxa2 = boxaReconcileEvenOddHeight(boxa1, L_ADJUST_TOP_AND_BOT, 6,
+ L_ADJUST_CHOOSE_MIN, 1.0, 0);
+ pix1 = DisplayBoxa(boxa2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 8 */
+ pixDisplayWithTitle(pix1, 200, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ boxaCompareRegions(boxa1, boxa2, 10, &same, &diffarea, &diffxor, &pix1);
+ regTestCompareValues(rp, 1, same, 0.0); /* 9 */
+ regTestCompareValues(rp, 0.053, diffarea, 0.002); /* 10 */
+ regTestCompareValues(rp, 0.240, diffxor, 0.002); /* 11 */
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 12 */
+ pixDisplayWithTitle(pix1, 400, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+
+ /* Test serialized boxa I/O to and from memory */
+ data1 = l_binaryRead("boxa2.ba", &size1);
+ boxa1 = boxaReadMem(data1, size1);
+ boxaWriteMem(&data2, &size2, boxa1);
+ boxa2 = boxaReadMem(data2, size2);
+ boxaWrite("/tmp/lept/boxa/boxa1.ba", boxa1);
+ boxaWrite("/tmp/lept/boxa/boxa2.ba", boxa2);
+ filesAreIdentical("/tmp/lept/boxa/boxa1.ba", "/tmp/lept/boxa/boxa2.ba",
+ &same);
+ regTestCompareValues(rp, 1, same, 0.0); /* 13 */
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ lept_free(data1);
+ lept_free(data2);
+
+ return regTestCleanup(rp);
+}
+
+
+static PIX *
+DisplayBoxa(BOXA *boxa)
+{
+l_int32 w, h;
+BOX *box;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa;
+
+ pixa = pixaCreate(2);
+ boxaGetExtent(boxa, &w, &h, &box);
+ pix1 = pixCreate(w, h, 1);
+ pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixCreate(w, h, 32);
+ pixSetAll(pix2);
+ pixRenderBoxaArb(pix2, boxa, 2, 0, 255, 0);
+ pixRenderBoxArb(pix2, box, 3, 255, 0, 0);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix3 = pixaDisplayTiledInRows(pixa, 32, 1000, 1.0, 0, 30, 2);
+ boxDestroy(&box);
+ pixaDestroy(&pixa);
+ return pix3;
+}
diff --git a/leptonica/prog/boxa2.ba b/leptonica/prog/boxa2.ba
new file mode 100644
index 00000000..ed7dcf13
--- /dev/null
+++ b/leptonica/prog/boxa2.ba
@@ -0,0 +1,379 @@
+
+Boxa Version 2
+Number of boxes = 376
+ Box[0]: x = 0, y = 0, w = 0, h = 0
+ Box[1]: x = 0, y = 0, w = 0, h = 0
+ Box[2]: x = 0, y = 0, w = 0, h = 0
+ Box[3]: x = 0, y = 0, w = 0, h = 0
+ Box[4]: x = 0, y = 0, w = 0, h = 0
+ Box[5]: x = 0, y = 0, w = 0, h = 0
+ Box[6]: x = 1070, y = 128, w = 1617, h = 4049
+ Box[7]: x = 250, y = 175, w = 2531, h = 3793
+ Box[8]: x = 355, y = 134, w = 2374, h = 4043
+ Box[9]: x = 209, y = 105, w = 2263, h = 3753
+ Box[10]: x = 465, y = 140, w = 2257, h = 4043
+ Box[11]: x = 268, y = 105, w = 2222, h = 3747
+ Box[12]: x = 465, y = 134, w = 2234, h = 4043
+ Box[13]: x = 244, y = 111, w = 2246, h = 3741
+ Box[14]: x = 465, y = 128, w = 2257, h = 4049
+ Box[15]: x = 279, y = 966, w = 1740, h = 1984
+ Box[16]: x = 919, y = 943, w = 1815, h = 2025
+ Box[17]: x = 337, y = 134, w = 2106, h = 3113
+ Box[18]: x = 535, y = 122, w = 2170, h = 3130
+ Box[19]: x = 227, y = 134, w = 2217, h = 3124
+ Box[20]: x = 448, y = 116, w = 2263, h = 3142
+ Box[21]: x = 239, y = 128, w = 2217, h = 3130
+ Box[22]: x = 524, y = 70, w = 2188, h = 3142
+ Box[23]: x = 239, y = 128, w = 2205, h = 3130
+ Box[24]: x = 495, y = 128, w = 2217, h = 3142
+ Box[25]: x = 227, y = 116, w = 2217, h = 3130
+ Box[26]: x = 495, y = 134, w = 2217, h = 3142
+ Box[27]: x = 209, y = 116, w = 2234, h = 3130
+ Box[28]: x = 477, y = 99, w = 2228, h = 3118
+ Box[29]: x = 227, y = 122, w = 2222, h = 3130
+ Box[30]: x = 477, y = 134, w = 2298, h = 3153
+ Box[31]: x = 233, y = 116, w = 2211, h = 3124
+ Box[32]: x = 460, y = 76, w = 2246, h = 3153
+ Box[33]: x = 233, y = 122, w = 2222, h = 3130
+ Box[34]: x = 460, y = 140, w = 2321, h = 3147
+ Box[35]: x = 233, y = 111, w = 2217, h = 3130
+ Box[36]: x = 460, y = 134, w = 2321, h = 3153
+ Box[37]: x = 221, y = 128, w = 2240, h = 3130
+ Box[38]: x = 471, y = 145, w = 2298, h = 3159
+ Box[39]: x = 239, y = 111, w = 2240, h = 3130
+ Box[40]: x = 465, y = 70, w = 2327, h = 3159
+ Box[41]: x = 233, y = 204, w = 2257, h = 3049
+ Box[42]: x = 465, y = 41, w = 2327, h = 3188
+ Box[43]: x = 239, y = 157, w = 2252, h = 3095
+ Box[44]: x = 460, y = 93, w = 2327, h = 3142
+ Box[45]: x = 471, y = 157, w = 2019, h = 3107
+ Box[46]: x = 465, y = 111, w = 2327, h = 3118
+ Box[47]: x = 233, y = 140, w = 2257, h = 3107
+ Box[48]: x = 460, y = 99, w = 2333, h = 3101
+ Box[49]: x = 233, y = 128, w = 2252, h = 3113
+ Box[50]: x = 140, y = 58, w = 2641, h = 4020
+ Box[51]: x = 239, y = 134, w = 2246, h = 3107
+ Box[52]: x = 0, y = 58, w = 2781, h = 4078
+ Box[53]: x = 227, y = 128, w = 2257, h = 3113
+ Box[54]: x = 465, y = 111, w = 2327, h = 3136
+ Box[55]: x = 227, y = 111, w = 2263, h = 3118
+ Box[56]: x = 489, y = 111, w = 2304, h = 3130
+ Box[57]: x = 227, y = 116, w = 2257, h = 3118
+ Box[58]: x = 500, y = 105, w = 2292, h = 3136
+ Box[59]: x = 227, y = 111, w = 2257, h = 3118
+ Box[60]: x = 495, y = 145, w = 2281, h = 3159
+ Box[61]: x = 390, y = 105, w = 2100, h = 3107
+ Box[62]: x = 483, y = 140, w = 2286, h = 3165
+ Box[63]: x = 582, y = 105, w = 1914, h = 3101
+ Box[64]: x = 460, y = 105, w = 2333, h = 3142
+ Box[65]: x = 268, y = 0, w = 2508, h = 3974
+ Box[66]: x = 448, y = 0, w = 2339, h = 4177
+ Box[67]: x = 256, y = 0, w = 2525, h = 3863
+ Box[68]: x = 448, y = 0, w = 2333, h = 4177
+ Box[69]: x = 273, y = 0, w = 2508, h = 3863
+ Box[70]: x = 442, y = 0, w = 2316, h = 4177
+ Box[71]: x = 367, y = 0, w = 2414, h = 3863
+ Box[72]: x = 442, y = 0, w = 2339, h = 4177
+ Box[73]: x = 285, y = 0, w = 2496, h = 3869
+ Box[74]: x = 442, y = 0, w = 2316, h = 4177
+ Box[75]: x = 367, y = 0, w = 2409, h = 3869
+ Box[76]: x = 442, y = 0, w = 2327, h = 4177
+ Box[77]: x = 273, y = 0, w = 2246, h = 3869
+ Box[78]: x = 442, y = 0, w = 2339, h = 4177
+ Box[79]: x = 378, y = 0, w = 2397, h = 3869
+ Box[80]: x = 442, y = 0, w = 2339, h = 4177
+ Box[81]: x = 367, y = 0, w = 2409, h = 3863
+ Box[82]: x = 442, y = 0, w = 2339, h = 4177
+ Box[83]: x = 268, y = 0, w = 2257, h = 3869
+ Box[84]: x = 442, y = 0, w = 2316, h = 4177
+ Box[85]: x = 372, y = 0, w = 2403, h = 3869
+ Box[86]: x = 442, y = 0, w = 2350, h = 4177
+ Box[87]: x = 291, y = 0, w = 2484, h = 3869
+ Box[88]: x = 442, y = 0, w = 2345, h = 4177
+ Box[89]: x = 413, y = 0, w = 2362, h = 3869
+ Box[90]: x = 442, y = 0, w = 2345, h = 4177
+ Box[91]: x = 419, y = 0, w = 2356, h = 3869
+ Box[92]: x = 442, y = 0, w = 2327, h = 4177
+ Box[93]: x = 367, y = 0, w = 2158, h = 3875
+ Box[94]: x = 442, y = 0, w = 2345, h = 4177
+ Box[95]: x = 262, y = 0, w = 2263, h = 3881
+ Box[96]: x = 448, y = 0, w = 2350, h = 4177
+ Box[97]: x = 384, y = 0, w = 2019, h = 3875
+ Box[98]: x = 483, y = 0, w = 2316, h = 4177
+ Box[99]: x = 384, y = 0, w = 2025, h = 3886
+ Box[100]: x = 483, y = 0, w = 2316, h = 4177
+ Box[101]: x = 303, y = 0, w = 2118, h = 3875
+ Box[102]: x = 442, y = 0, w = 2356, h = 4177
+ Box[103]: x = 378, y = 0, w = 2013, h = 3863
+ Box[104]: x = 687, y = 0, w = 2118, h = 4177
+ Box[105]: x = 303, y = 0, w = 2083, h = 3875
+ Box[106]: x = 442, y = 0, w = 2362, h = 4177
+ Box[107]: x = 390, y = 0, w = 1949, h = 3886
+ Box[108]: x = 442, y = 0, w = 2356, h = 4177
+ Box[109]: x = 250, y = 151, w = 1745, h = 2321
+ Box[110]: x = 1390, y = 140, w = 1460, h = 2356
+ Box[111]: x = 384, y = 157, w = 1606, h = 2327
+ Box[112]: x = 1385, y = 227, w = 1484, h = 2339
+ Box[113]: x = 349, y = 12, w = 1728, h = 3979
+ Box[114]: x = 1338, y = 209, w = 1484, h = 2339
+ Box[115]: x = 314, y = 6, w = 1728, h = 3979
+ Box[116]: x = 1338, y = 215, w = 1478, h = 2333
+ Box[117]: x = 314, y = 6, w = 1699, h = 3979
+ Box[118]: x = 1344, y = 215, w = 1484, h = 2333
+ Box[119]: x = 314, y = 0, w = 1693, h = 3985
+ Box[120]: x = 1350, y = 209, w = 1478, h = 2345
+ Box[121]: x = 314, y = 6, w = 1693, h = 3985
+ Box[122]: x = 1350, y = 209, w = 1478, h = 2345
+ Box[123]: x = 314, y = 0, w = 1652, h = 3979
+ Box[124]: x = 1344, y = 198, w = 1484, h = 2356
+ Box[125]: x = 314, y = 0, w = 1693, h = 3974
+ Box[126]: x = 1350, y = 198, w = 1489, h = 2339
+ Box[127]: x = 314, y = 0, w = 1646, h = 3974
+ Box[128]: x = 1332, y = 163, w = 1536, h = 2356
+ Box[129]: x = 413, y = 122, w = 1548, h = 2339
+ Box[130]: x = 1338, y = 169, w = 1536, h = 2345
+ Box[131]: x = 320, y = 0, w = 1641, h = 3974
+ Box[132]: x = 1338, y = 169, w = 1495, h = 2350
+ Box[133]: x = 314, y = 0, w = 1693, h = 3985
+ Box[134]: x = 1344, y = 163, w = 1495, h = 2356
+ Box[135]: x = 349, y = 111, w = 1635, h = 2327
+ Box[136]: x = 1332, y = 163, w = 1501, h = 2356
+ Box[137]: x = 308, y = 0, w = 1699, h = 3979
+ Box[138]: x = 1344, y = 175, w = 1478, h = 2345
+ Box[139]: x = 465, y = 105, w = 1518, h = 2316
+ Box[140]: x = 1332, y = 163, w = 1484, h = 2362
+ Box[141]: x = 489, y = 111, w = 1530, h = 3869
+ Box[142]: x = 1332, y = 169, w = 1495, h = 2356
+ Box[143]: x = 465, y = 105, w = 1518, h = 2327
+ Box[144]: x = 1332, y = 169, w = 1484, h = 2356
+ Box[145]: x = 297, y = 87, w = 1687, h = 2316
+ Box[146]: x = 1332, y = 169, w = 1478, h = 2356
+ Box[147]: x = 471, y = 70, w = 1513, h = 2321
+ Box[148]: x = 1332, y = 169, w = 1484, h = 2356
+ Box[149]: x = 471, y = 52, w = 1518, h = 2310
+ Box[150]: x = 1344, y = 134, w = 1472, h = 2391
+ Box[151]: x = 500, y = 122, w = 1484, h = 2316
+ Box[152]: x = 1332, y = 163, w = 1484, h = 2362
+ Box[153]: x = 471, y = 122, w = 1513, h = 2316
+ Box[154]: x = 1338, y = 157, w = 1513, h = 2374
+ Box[155]: x = 471, y = 128, w = 1513, h = 2321
+ Box[156]: x = 1332, y = 163, w = 1489, h = 2368
+ Box[157]: x = 524, y = 128, w = 1454, h = 2316
+ Box[158]: x = 1338, y = 163, w = 1530, h = 2368
+ Box[159]: x = 372, y = 128, w = 1612, h = 2316
+ Box[160]: x = 1332, y = 169, w = 1536, h = 2362
+ Box[161]: x = 343, y = 128, w = 1641, h = 2316
+ Box[162]: x = 1326, y = 134, w = 1530, h = 2397
+ Box[163]: x = 320, y = 140, w = 1670, h = 2310
+ Box[164]: x = 1332, y = 163, w = 1472, h = 2374
+ Box[165]: x = 372, y = 128, w = 1612, h = 2316
+ Box[166]: x = 1332, y = 163, w = 1530, h = 2368
+ Box[167]: x = 367, y = 128, w = 1617, h = 2310
+ Box[168]: x = 1326, y = 163, w = 1536, h = 2368
+ Box[169]: x = 471, y = 128, w = 1513, h = 2316
+ Box[170]: x = 1332, y = 163, w = 1536, h = 2374
+ Box[171]: x = 471, y = 134, w = 1513, h = 2316
+ Box[172]: x = 1332, y = 163, w = 1530, h = 2374
+ Box[173]: x = 471, y = 134, w = 1513, h = 2316
+ Box[174]: x = 1326, y = 163, w = 1478, h = 2374
+ Box[175]: x = 471, y = 151, w = 1513, h = 2310
+ Box[176]: x = 1367, y = 157, w = 1495, h = 2385
+ Box[177]: x = 477, y = 134, w = 1501, h = 2316
+ Box[178]: x = 1338, y = 157, w = 1530, h = 2380
+ Box[179]: x = 471, y = 134, w = 1507, h = 2316
+ Box[180]: x = 1344, y = 157, w = 1518, h = 2380
+ Box[181]: x = 471, y = 134, w = 1513, h = 2316
+ Box[182]: x = 1338, y = 175, w = 1524, h = 2374
+ Box[183]: x = 367, y = 0, w = 1658, h = 3991
+ Box[184]: x = 1373, y = 175, w = 1501, h = 2374
+ Box[185]: x = 390, y = 0, w = 1623, h = 3991
+ Box[186]: x = 1344, y = 145, w = 1530, h = 2380
+ Box[187]: x = 372, y = 169, w = 1617, h = 2321
+ Box[188]: x = 1367, y = 163, w = 1507, h = 2374
+ Box[189]: x = 372, y = 157, w = 1612, h = 2339
+ Box[190]: x = 1344, y = 157, w = 1530, h = 2380
+ Box[191]: x = 372, y = 169, w = 1612, h = 2327
+ Box[192]: x = 1344, y = 163, w = 1530, h = 2380
+ Box[193]: x = 372, y = 169, w = 1612, h = 2321
+ Box[194]: x = 1350, y = 163, w = 1530, h = 2374
+ Box[195]: x = 367, y = 157, w = 1617, h = 2327
+ Box[196]: x = 1344, y = 163, w = 1530, h = 2374
+ Box[197]: x = 361, y = 6, w = 1600, h = 3985
+ Box[198]: x = 1367, y = 151, w = 1507, h = 2380
+ Box[199]: x = 372, y = 175, w = 1617, h = 2321
+ Box[200]: x = 1344, y = 157, w = 1530, h = 2380
+ Box[201]: x = 343, y = 169, w = 1641, h = 2321
+ Box[202]: x = 1385, y = 186, w = 1891, h = 2345
+ Box[203]: x = 303, y = 175, w = 1681, h = 2321
+ Box[204]: x = 1367, y = 128, w = 1449, h = 2409
+ Box[205]: x = 367, y = 163, w = 1600, h = 2333
+ Box[206]: x = 1356, y = 128, w = 1518, h = 2409
+ Box[207]: x = 367, y = 163, w = 1617, h = 2333
+ Box[208]: x = 1350, y = 169, w = 1926, h = 2368
+ Box[209]: x = 372, y = 169, w = 1617, h = 2321
+ Box[210]: x = 1332, y = 128, w = 1524, h = 2409
+ Box[211]: x = 308, y = 180, w = 1687, h = 2321
+ Box[212]: x = 1350, y = 140, w = 1449, h = 2397
+ Box[213]: x = 372, y = 175, w = 1617, h = 2321
+ Box[214]: x = 1326, y = 163, w = 1536, h = 2385
+ Box[215]: x = 372, y = 163, w = 1617, h = 2333
+ Box[216]: x = 1332, y = 134, w = 1472, h = 2397
+ Box[217]: x = 372, y = 175, w = 1617, h = 2327
+ Box[218]: x = 1350, y = 151, w = 1454, h = 2391
+ Box[219]: x = 308, y = 163, w = 1681, h = 2327
+ Box[220]: x = 1396, y = 128, w = 1414, h = 2414
+ Box[221]: x = 343, y = 163, w = 1623, h = 2333
+ Box[222]: x = 1356, y = 180, w = 1902, h = 2362
+ Box[223]: x = 367, y = 169, w = 1617, h = 2327
+ Box[224]: x = 1379, y = 128, w = 1484, h = 2414
+ Box[225]: x = 343, y = 163, w = 1635, h = 2327
+ Box[226]: x = 1420, y = 128, w = 1484, h = 2409
+ Box[227]: x = 303, y = 163, w = 1676, h = 2327
+ Box[228]: x = 1367, y = 128, w = 1443, h = 2414
+ Box[229]: x = 378, y = 6, w = 1582, h = 3991
+ Box[230]: x = 1350, y = 134, w = 1454, h = 2409
+ Box[231]: x = 308, y = 169, w = 1681, h = 2321
+ Box[232]: x = 1332, y = 122, w = 1518, h = 2403
+ Box[233]: x = 407, y = 175, w = 1588, h = 2327
+ Box[234]: x = 1361, y = 128, w = 1443, h = 2403
+ Box[235]: x = 349, y = 175, w = 1646, h = 2327
+ Box[236]: x = 1338, y = 122, w = 1466, h = 2409
+ Box[237]: x = 320, y = 163, w = 1670, h = 2339
+ Box[238]: x = 1373, y = 192, w = 1425, h = 2397
+ Box[239]: x = 337, y = 180, w = 1658, h = 2321
+ Box[240]: x = 1379, y = 204, w = 1484, h = 2403
+ Box[241]: x = 361, y = 163, w = 1606, h = 2339
+ Box[242]: x = 1326, y = 215, w = 1530, h = 2391
+ Box[243]: x = 465, y = 122, w = 1501, h = 2356
+ Box[244]: x = 1326, y = 186, w = 1472, h = 2414
+ Box[245]: x = 367, y = 140, w = 1623, h = 2339
+ Box[246]: x = 1326, y = 186, w = 1513, h = 2414
+ Box[247]: x = 477, y = 122, w = 1513, h = 2345
+ Box[248]: x = 1350, y = 186, w = 1518, h = 2420
+ Box[249]: x = 477, y = 111, w = 1507, h = 2345
+ Box[250]: x = 1326, y = 209, w = 1524, h = 2391
+ Box[251]: x = 332, y = 111, w = 1652, h = 2350
+ Box[252]: x = 1373, y = 186, w = 1431, h = 2409
+ Box[253]: x = 320, y = 105, w = 1664, h = 2380
+ Box[254]: x = 1350, y = 186, w = 1553, h = 2414
+ Box[255]: x = 471, y = 116, w = 1518, h = 2339
+ Box[256]: x = 1367, y = 186, w = 1437, h = 2409
+ Box[257]: x = 326, y = 134, w = 1664, h = 2339
+ Box[258]: x = 1356, y = 186, w = 1507, h = 2409
+ Box[259]: x = 326, y = 128, w = 1664, h = 2362
+ Box[260]: x = 1332, y = 186, w = 1577, h = 2414
+ Box[261]: x = 326, y = 128, w = 1664, h = 2362
+ Box[262]: x = 1361, y = 186, w = 1553, h = 2420
+ Box[263]: x = 332, y = 122, w = 1652, h = 2362
+ Box[264]: x = 1338, y = 180, w = 1524, h = 2420
+ Box[265]: x = 332, y = 116, w = 1652, h = 2362
+ Box[266]: x = 1338, y = 186, w = 1518, h = 2420
+ Box[267]: x = 332, y = 111, w = 1658, h = 2345
+ Box[268]: x = 1321, y = 186, w = 1588, h = 2414
+ Box[269]: x = 337, y = 105, w = 1646, h = 2368
+ Box[270]: x = 1379, y = 180, w = 1484, h = 2420
+ Box[271]: x = 332, y = 116, w = 1652, h = 2333
+ Box[272]: x = 1292, y = 198, w = 1612, h = 3974
+ Box[273]: x = 337, y = 99, w = 1646, h = 2374
+ Box[274]: x = 1414, y = 180, w = 1449, h = 2414
+ Box[275]: x = 337, y = 116, w = 1652, h = 2345
+ Box[276]: x = 1292, y = 198, w = 1617, h = 3974
+ Box[277]: x = 361, y = 122, w = 1629, h = 2339
+ Box[278]: x = 1373, y = 180, w = 1484, h = 2414
+ Box[279]: x = 361, y = 99, w = 1629, h = 2356
+ Box[280]: x = 1292, y = 198, w = 1629, h = 3974
+ Box[281]: x = 343, y = 116, w = 1641, h = 2310
+ Box[282]: x = 1443, y = 175, w = 1466, h = 2426
+ Box[283]: x = 367, y = 111, w = 1623, h = 2339
+ Box[284]: x = 1373, y = 180, w = 1489, h = 2426
+ Box[285]: x = 390, y = 111, w = 1600, h = 2333
+ Box[286]: x = 1454, y = 175, w = 1402, h = 2426
+ Box[287]: x = 390, y = 105, w = 1600, h = 2333
+ Box[288]: x = 1420, y = 186, w = 1443, h = 2420
+ Box[289]: x = 390, y = 111, w = 1600, h = 2339
+ Box[290]: x = 1408, y = 180, w = 1454, h = 2426
+ Box[291]: x = 372, y = 105, w = 1612, h = 2345
+ Box[292]: x = 1437, y = 175, w = 1501, h = 2426
+ Box[293]: x = 361, y = 105, w = 1629, h = 2345
+ Box[294]: x = 1356, y = 175, w = 1501, h = 2432
+ Box[295]: x = 390, y = 105, w = 1594, h = 2339
+ Box[296]: x = 1385, y = 175, w = 1466, h = 2432
+ Box[297]: x = 355, y = 99, w = 1635, h = 2339
+ Box[298]: x = 1443, y = 175, w = 1420, h = 2420
+ Box[299]: x = 367, y = 105, w = 1623, h = 2327
+ Box[300]: x = 1408, y = 175, w = 1507, h = 2420
+ Box[301]: x = 390, y = 105, w = 1600, h = 2327
+ Box[302]: x = 1443, y = 175, w = 1507, h = 2432
+ Box[303]: x = 401, y = 99, w = 1588, h = 2327
+ Box[304]: x = 1292, y = 198, w = 1641, h = 3968
+ Box[305]: x = 401, y = 93, w = 1582, h = 2321
+ Box[306]: x = 1286, y = 192, w = 1646, h = 3974
+ Box[307]: x = 384, y = 93, w = 1600, h = 2327
+ Box[308]: x = 1286, y = 198, w = 1641, h = 3968
+ Box[309]: x = 401, y = 93, w = 1588, h = 2316
+ Box[310]: x = 1379, y = 169, w = 1524, h = 2438
+ Box[311]: x = 390, y = 93, w = 1600, h = 2339
+ Box[312]: x = 1292, y = 192, w = 1641, h = 3974
+ Box[313]: x = 367, y = 93, w = 1617, h = 2321
+ Box[314]: x = 1449, y = 163, w = 1414, h = 2438
+ Box[315]: x = 367, y = 93, w = 1617, h = 2333
+ Box[316]: x = 1396, y = 175, w = 1513, h = 2444
+ Box[317]: x = 384, y = 99, w = 1606, h = 2321
+ Box[318]: x = 1425, y = 175, w = 1437, h = 2438
+ Box[319]: x = 326, y = 122, w = 1658, h = 2333
+ Box[320]: x = 1390, y = 192, w = 1466, h = 2420
+ Box[321]: x = 384, y = 128, w = 1600, h = 2327
+ Box[322]: x = 1286, y = 192, w = 1658, h = 3974
+ Box[323]: x = 425, y = 122, w = 1559, h = 2333
+ Box[324]: x = 1420, y = 180, w = 1437, h = 2432
+ Box[325]: x = 431, y = 122, w = 1553, h = 2333
+ Box[326]: x = 1425, y = 180, w = 1437, h = 2432
+ Box[327]: x = 431, y = 122, w = 1553, h = 2327
+ Box[328]: x = 1286, y = 192, w = 1606, h = 3968
+ Box[329]: x = 407, y = 111, w = 1577, h = 2339
+ Box[330]: x = 1466, y = 180, w = 1484, h = 2438
+ Box[331]: x = 407, y = 122, w = 1571, h = 2339
+ Box[332]: x = 1437, y = 180, w = 1460, h = 2444
+ Box[333]: x = 349, y = 122, w = 1635, h = 2339
+ Box[334]: x = 1332, y = 215, w = 1559, h = 2414
+ Box[335]: x = 390, y = 116, w = 1588, h = 2339
+ Box[336]: x = 1402, y = 198, w = 1495, h = 2432
+ Box[337]: x = 506, y = 116, w = 1472, h = 2339
+ Box[338]: x = 1437, y = 175, w = 1414, h = 2455
+ Box[339]: x = 448, y = 116, w = 1530, h = 2339
+ Box[340]: x = 1460, y = 180, w = 1408, h = 2455
+ Box[341]: x = 454, y = 116, w = 1524, h = 2345
+ Box[342]: x = 1472, y = 192, w = 1379, h = 2438
+ Box[343]: x = 448, y = 157, w = 1530, h = 2345
+ Box[344]: x = 1507, y = 180, w = 1356, h = 2449
+ Box[345]: x = 419, y = 93, w = 1553, h = 2339
+ Box[346]: x = 1472, y = 169, w = 1472, h = 2461
+ Box[347]: x = 396, y = 93, w = 1582, h = 2333
+ Box[348]: x = 1443, y = 192, w = 1507, h = 2438
+ Box[349]: x = 431, y = 99, w = 1548, h = 2327
+ Box[350]: x = 1449, y = 169, w = 1536, h = 2461
+ Box[351]: x = 465, y = 93, w = 1513, h = 2327
+ Box[352]: x = 1449, y = 169, w = 1530, h = 2467
+ Box[353]: x = 454, y = 76, w = 1518, h = 2333
+ Box[354]: x = 1286, y = 105, w = 1606, h = 4049
+ Box[355]: x = 460, y = 76, w = 1518, h = 2333
+ Box[356]: x = 1460, y = 186, w = 1524, h = 2444
+ Box[357]: x = 465, y = 76, w = 1513, h = 2327
+ Box[358]: x = 1454, y = 169, w = 1530, h = 2438
+ Box[359]: x = 460, y = 70, w = 1513, h = 2327
+ Box[360]: x = 1449, y = 192, w = 1507, h = 2444
+ Box[361]: x = 460, y = 58, w = 1513, h = 2333
+ Box[362]: x = 1420, y = 192, w = 1553, h = 2438
+ Box[363]: x = 460, y = 58, w = 1518, h = 2333
+ Box[364]: x = 1466, y = 175, w = 1484, h = 2467
+ Box[365]: x = 460, y = 58, w = 1513, h = 2327
+ Box[366]: x = 1379, y = 204, w = 1513, h = 2432
+ Box[367]: x = 460, y = 58, w = 1513, h = 2327
+ Box[368]: x = 1367, y = 204, w = 1582, h = 2438
+ Box[369]: x = 465, y = 47, w = 2356, h = 3962
+ Box[370]: x = 640, y = 0, w = 2292, h = 3991
+ Box[371]: x = 442, y = 47, w = 2380, h = 3915
+ Box[372]: x = 495, y = 0, w = 2455, h = 4177
+ Box[373]: x = 471, y = 140, w = 2350, h = 3881
+ Box[374]: x = 0, y = 0, w = 0, h = 0
+ Box[375]: x = 0, y = 0, w = 0, h = 0
diff --git a/leptonica/prog/boxa2_reg.c b/leptonica/prog/boxa2_reg.c
new file mode 100644
index 00000000..55d65355
--- /dev/null
+++ b/leptonica/prog/boxa2_reg.c
@@ -0,0 +1,196 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * boxa2_reg.c
+ *
+ * Low-level statistical operations that can be used to identify anomalous
+ * sized boxes in a boxa, where the widths and heights of the boxes
+ * are expected to be similar.
+ *
+ * This tests a number of operations on boxes in a boxa, including
+ * separating them into subsets of different parity.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i, n, val, ne, no, nbins, minw, maxw, minh, maxh;
+l_int32 mine, mino, maxe, maxo;
+l_int32 w_diff, h_diff, median_w_diff, median_h_diff;
+l_int32 noutw, nouth;
+l_float32 medwe, medhe, medwo, medho;
+BOXA *boxa1, *boxa2, *boxae, *boxao;
+NUMA *na1, *nawe, *nahe, *nawo, *naho;
+NUMA *nadiffw, *nadiffh; /* diff from median w and h */
+NUMA *naiw, *naih; /* indicator arrays for small outlier dimensions */
+NUMA *narbwe, *narbhe, *narbwo, *narbho; /* rank-binned w and h */
+PIX *pix1;
+PIXA *pixa1;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/boxa");
+ boxa1 = boxaRead("boxa4.ba");
+
+ /* Fill invalid boxes */
+ n = boxaGetCount(boxa1);
+ na1 = boxaFindInvalidBoxes(boxa1);
+ if (na1)
+ boxa2 = boxaFillSequence(boxa1, L_USE_SAME_PARITY_BOXES, 0);
+ else
+ boxa2 = boxaCopy(boxa1, L_CLONE);
+ boxaDestroy(&boxa1);
+
+ /* Get the widths and heights for even and odd parity */
+ boxaSplitEvenOdd(boxa2, 0, &boxae, &boxao);
+ boxaGetSizes(boxae, &nawe, &nahe);
+ boxaGetSizes(boxao, &nawo, &naho);
+ boxaDestroy(&boxa2);
+
+ /* Find the medians */
+ numaGetMedian(nawe, &medwe);
+ numaGetMedian(nahe, &medhe);
+ numaGetMedian(nawo, &medwo);
+ numaGetMedian(naho, &medho);
+
+ /* Find the median even/odd differences for width and height */
+ median_w_diff = L_ABS(medwe - medwo);
+ median_h_diff = L_ABS(medhe - medho);
+ regTestCompareValues(rp, 210, median_w_diff, 0.0); /* 0 */
+ regTestCompareValues(rp, 15, median_h_diff, 0.0); /* 1 */
+ if (rp->display) {
+ lept_stderr("diff of e/o median widths = %d\n", median_w_diff);
+ lept_stderr("diff of e/o median heights = %d\n", median_h_diff);
+ }
+
+ /* Find the differences of box width and height from the median */
+ nadiffw = numaMakeConstant(0, n);
+ nadiffh = numaMakeConstant(0, n);
+ ne = numaGetCount(nawe);
+ no = numaGetCount(nawo);
+ for (i = 0; i < ne; i++) {
+ numaGetIValue(nawe, i, &val);
+ numaSetValue(nadiffw, 2 * i, L_ABS(val - medwe));
+ numaGetIValue(nahe, i, &val);
+ numaSetValue(nadiffh, 2 * i, L_ABS(val - medhe));
+ }
+ for (i = 0; i < no; i++) {
+ numaGetIValue(nawo, i, &val);
+ numaSetValue(nadiffw, 2 * i + 1, L_ABS(val - medwo));
+ numaGetIValue(naho, i, &val);
+ numaSetValue(nadiffh, 2 * i + 1, L_ABS(val - medho));
+ }
+
+ /* Don't count invalid boxes; set the diffs to 0 for them */
+ if (na1) {
+ for (i = 0; i < n; i++) {
+ numaGetIValue(na1, i, &val);
+ if (val == 1) {
+ numaSetValue(nadiffw, i, 0);
+ numaSetValue(nadiffh, i, 0);
+ }
+ }
+ }
+
+ /* Make an indicator array for boxes that differ from the
+ * median by more than a threshold value for outliers */
+ naiw = numaMakeThresholdIndicator(nadiffw, 90, L_SELECT_IF_GT);
+ naih = numaMakeThresholdIndicator(nadiffh, 90, L_SELECT_IF_GT);
+ numaGetCountRelativeToZero(naiw, L_GREATER_THAN_ZERO, &noutw);
+ numaGetCountRelativeToZero(naih, L_GREATER_THAN_ZERO, &nouth);
+ regTestCompareValues(rp, 24, noutw, 0.0); /* 2 */
+ regTestCompareValues(rp, 0, nouth, 0.0); /* 3 */
+ if (rp->display)
+ lept_stderr("num width outliers = %d, num height outliers = %d\n",
+ noutw, nouth);
+ numaDestroy(&nadiffw);
+ numaDestroy(&nadiffh);
+ numaDestroy(&naiw);
+ numaDestroy(&naih);
+
+ /* Find the rank bins for width and height */
+ nbins = L_MAX(5, ne / 50); // up to 50 pages/bin
+ numaGetRankBinValues(nawe, nbins, &narbwe);
+ numaGetRankBinValues(nawo, nbins, &narbwo);
+ numaGetRankBinValues(nahe, nbins, &narbhe);
+ numaGetRankBinValues(naho, nbins, &narbho);
+ numaDestroy(&nawe);
+ numaDestroy(&nawo);
+ numaDestroy(&nahe);
+ numaDestroy(&naho);
+
+ /* Find min and max binned widths and heights; get the max diffs */
+ numaGetIValue(narbwe, 0, &mine);
+ numaGetIValue(narbwe, nbins - 1, &maxe);
+ numaGetIValue(narbwo, 0, &mino);
+ numaGetIValue(narbwo, nbins - 1, &maxo);
+ minw = L_MIN(mine, mino);
+ maxw = L_MAX(maxe, maxo);
+ w_diff = maxw - minw;
+ numaGetIValue(narbhe, 0, &mine);
+ numaGetIValue(narbhe, nbins - 1, &maxe);
+ numaGetIValue(narbho, 0, &mino);
+ numaGetIValue(narbho, nbins - 1, &maxo);
+ minh = L_MIN(mine, mino);
+ maxh = L_MAX(maxe, maxo);
+ h_diff = maxh - minh;
+ numaDestroy(&narbwe);
+ numaDestroy(&narbhe);
+ numaDestroy(&narbwo);
+ numaDestroy(&narbho);
+ regTestCompareValues(rp, 409, w_diff, 0.0); /* 4 */
+ regTestCompareValues(rp, 54, h_diff, 0.0); /* 5 */
+ if (rp->display)
+ lept_stderr("Binned rank results: w_diff = %d, h_diff = %d\n",
+ w_diff, h_diff);
+
+ /* Plot the results */
+ if (noutw > 0 || nouth > 0) {
+ pixa1 = pixaCreate(2);
+ boxaPlotSizes(boxae, "even", NULL, NULL, &pix1);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ boxaPlotSizes(boxao, "odd", NULL, NULL, &pix1);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pix1 = pixaDisplayTiledInRows(pixa1, 32, 1500, 1.0, 0, 30, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pix1, 100, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+ }
+
+ boxaDestroy(&boxae);
+ boxaDestroy(&boxao);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/boxa3.ba b/leptonica/prog/boxa3.ba
new file mode 100644
index 00000000..eacb6a78
--- /dev/null
+++ b/leptonica/prog/boxa3.ba
@@ -0,0 +1,379 @@
+
+Boxa Version 2
+Number of boxes = 376
+ Box[0]: x = 0, y = 0, w = 0, h = 0
+ Box[1]: x = 0, y = 0, w = 0, h = 0
+ Box[2]: x = 0, y = 0, w = 0, h = 0
+ Box[3]: x = 0, y = 0, w = 0, h = 0
+ Box[4]: x = 0, y = 0, w = 0, h = 0
+ Box[5]: x = 0, y = 0, w = 0, h = 0
+ Box[6]: x = 524, y = 128, w = 2182, h = 4049
+ Box[7]: x = 250, y = 134, w = 2313, h = 3790
+ Box[8]: x = 451, y = 128, w = 2278, h = 4049
+ Box[9]: x = 209, y = 105, w = 2281, h = 3753
+ Box[10]: x = 465, y = 129, w = 2257, h = 4054
+ Box[11]: x = 250, y = 105, w = 2240, h = 3747
+ Box[12]: x = 465, y = 129, w = 2238, h = 4048
+ Box[13]: x = 244, y = 111, w = 2246, h = 3741
+ Box[14]: x = 465, y = 128, w = 2257, h = 4049
+ Box[15]: x = 244, y = 129, w = 2211, h = 3130
+ Box[16]: x = 493, y = 135, w = 2241, h = 3153
+ Box[17]: x = 239, y = 129, w = 2217, h = 3130
+ Box[18]: x = 476, y = 122, w = 2234, h = 3161
+ Box[19]: x = 227, y = 128, w = 2228, h = 3130
+ Box[20]: x = 448, y = 116, w = 2263, h = 3171
+ Box[21]: x = 239, y = 128, w = 2217, h = 3130
+ Box[22]: x = 470, y = 70, w = 2241, h = 3177
+ Box[23]: x = 239, y = 127, w = 2217, h = 3131
+ Box[24]: x = 467, y = 128, w = 2257, h = 3156
+ Box[25]: x = 227, y = 116, w = 2228, h = 3136
+ Box[26]: x = 467, y = 132, w = 2257, h = 3146
+ Box[27]: x = 209, y = 116, w = 2246, h = 3136
+ Box[28]: x = 467, y = 90, w = 2263, h = 3145
+ Box[29]: x = 227, y = 122, w = 2228, h = 3130
+ Box[30]: x = 467, y = 134, w = 2308, h = 3153
+ Box[31]: x = 233, y = 116, w = 2222, h = 3136
+ Box[32]: x = 460, y = 76, w = 2310, h = 3153
+ Box[33]: x = 233, y = 122, w = 2222, h = 3130
+ Box[34]: x = 460, y = 134, w = 2321, h = 3153
+ Box[35]: x = 233, y = 111, w = 2222, h = 3136
+ Box[36]: x = 460, y = 134, w = 2321, h = 3153
+ Box[37]: x = 221, y = 128, w = 2240, h = 3130
+ Box[38]: x = 464, y = 135, w = 2316, h = 3169
+ Box[39]: x = 233, y = 111, w = 2246, h = 3136
+ Box[40]: x = 465, y = 70, w = 2327, h = 3165
+ Box[41]: x = 233, y = 122, w = 2257, h = 3130
+ Box[42]: x = 465, y = 41, w = 2327, h = 3188
+ Box[43]: x = 233, y = 122, w = 2257, h = 3130
+ Box[44]: x = 460, y = 93, w = 2327, h = 3147
+ Box[45]: x = 233, y = 116, w = 2257, h = 3147
+ Box[46]: x = 465, y = 93, w = 2327, h = 3147
+ Box[47]: x = 233, y = 116, w = 2257, h = 3130
+ Box[48]: x = 460, y = 93, w = 2333, h = 3149
+ Box[49]: x = 233, y = 116, w = 2252, h = 3124
+ Box[50]: x = 388, y = 58, w = 2394, h = 3249
+ Box[51]: x = 233, y = 116, w = 2257, h = 3124
+ Box[52]: x = 388, y = 65, w = 2394, h = 3305
+ Box[53]: x = 227, y = 111, w = 2263, h = 3136
+ Box[54]: x = 461, y = 87, w = 2332, h = 3165
+ Box[55]: x = 227, y = 111, w = 2263, h = 3136
+ Box[56]: x = 460, y = 81, w = 2333, h = 3165
+ Box[57]: x = 227, y = 111, w = 2263, h = 3142
+ Box[58]: x = 461, y = 76, w = 2332, h = 3182
+ Box[59]: x = 227, y = 111, w = 2263, h = 3142
+ Box[60]: x = 460, y = 122, w = 2323, h = 3188
+ Box[61]: x = 268, y = 105, w = 2222, h = 3146
+ Box[62]: x = 448, y = 105, w = 2334, h = 4033
+ Box[63]: x = 268, y = 105, w = 2228, h = 3158
+ Box[64]: x = 448, y = 0, w = 2345, h = 4080
+ Box[65]: x = 268, y = 0, w = 2324, h = 3936
+ Box[66]: x = 442, y = 0, w = 2345, h = 4177
+ Box[67]: x = 256, y = 0, w = 2342, h = 3863
+ Box[68]: x = 442, y = 0, w = 2339, h = 4177
+ Box[69]: x = 273, y = 0, w = 2508, h = 3863
+ Box[70]: x = 442, y = 0, w = 2339, h = 4177
+ Box[71]: x = 285, y = 0, w = 2496, h = 3863
+ Box[72]: x = 442, y = 0, w = 2339, h = 4177
+ Box[73]: x = 285, y = 0, w = 2496, h = 3869
+ Box[74]: x = 442, y = 0, w = 2339, h = 4177
+ Box[75]: x = 291, y = 0, w = 2484, h = 3869
+ Box[76]: x = 442, y = 0, w = 2340, h = 4177
+ Box[77]: x = 294, y = 0, w = 2481, h = 3869
+ Box[78]: x = 442, y = 0, w = 2340, h = 4177
+ Box[79]: x = 367, y = 0, w = 2409, h = 3869
+ Box[80]: x = 442, y = 0, w = 2340, h = 4177
+ Box[81]: x = 367, y = 0, w = 2409, h = 3869
+ Box[82]: x = 442, y = 0, w = 2340, h = 4177
+ Box[83]: x = 294, y = 0, w = 2481, h = 3869
+ Box[84]: x = 442, y = 0, w = 2343, h = 4177
+ Box[85]: x = 367, y = 0, w = 2409, h = 3869
+ Box[86]: x = 442, y = 0, w = 2350, h = 4177
+ Box[87]: x = 294, y = 0, w = 2481, h = 3869
+ Box[88]: x = 442, y = 0, w = 2345, h = 4177
+ Box[89]: x = 367, y = 0, w = 2231, h = 3869
+ Box[90]: x = 442, y = 0, w = 2345, h = 4177
+ Box[91]: x = 367, y = 0, w = 2231, h = 3869
+ Box[92]: x = 442, y = 0, w = 2345, h = 4177
+ Box[93]: x = 367, y = 0, w = 2158, h = 3875
+ Box[94]: x = 442, y = 0, w = 2352, h = 4177
+ Box[95]: x = 294, y = 0, w = 2231, h = 3881
+ Box[96]: x = 444, y = 0, w = 2355, h = 4177
+ Box[97]: x = 367, y = 0, w = 2054, h = 3875
+ Box[98]: x = 444, y = 0, w = 2355, h = 4177
+ Box[99]: x = 367, y = 0, w = 2042, h = 3888
+ Box[100]: x = 444, y = 0, w = 2355, h = 4177
+ Box[101]: x = 303, y = 0, w = 2118, h = 3876
+ Box[102]: x = 442, y = 0, w = 2356, h = 4177
+ Box[103]: x = 314, y = 0, w = 2077, h = 3876
+ Box[104]: x = 483, y = 0, w = 2321, h = 4177
+ Box[105]: x = 303, y = 0, w = 2083, h = 3876
+ Box[106]: x = 442, y = 0, w = 2362, h = 4177
+ Box[107]: x = 314, y = 0, w = 2025, h = 3888
+ Box[108]: x = 614, y = 0, w = 2190, h = 4177
+ Box[109]: x = 250, y = 7, w = 1827, h = 3881
+ Box[110]: x = 1361, y = 140, w = 1489, h = 2364
+ Box[111]: x = 314, y = 19, w = 1728, h = 3888
+ Box[112]: x = 1379, y = 215, w = 1489, h = 2356
+ Box[113]: x = 314, y = 12, w = 1763, h = 3974
+ Box[114]: x = 1338, y = 198, w = 1484, h = 2356
+ Box[115]: x = 314, y = 6, w = 1728, h = 3974
+ Box[116]: x = 1332, y = 198, w = 1484, h = 2356
+ Box[117]: x = 314, y = 6, w = 1699, h = 3979
+ Box[118]: x = 1344, y = 204, w = 1484, h = 2350
+ Box[119]: x = 314, y = 0, w = 1693, h = 3985
+ Box[120]: x = 1344, y = 204, w = 1484, h = 2350
+ Box[121]: x = 314, y = 6, w = 1693, h = 3985
+ Box[122]: x = 1350, y = 204, w = 1484, h = 2350
+ Box[123]: x = 314, y = 0, w = 1693, h = 3979
+ Box[124]: x = 1338, y = 198, w = 1489, h = 2356
+ Box[125]: x = 314, y = 0, w = 1693, h = 3974
+ Box[126]: x = 1350, y = 192, w = 1489, h = 2345
+ Box[127]: x = 314, y = 0, w = 1681, h = 3974
+ Box[128]: x = 1332, y = 163, w = 1536, h = 2356
+ Box[129]: x = 314, y = 29, w = 1676, h = 3950
+ Box[130]: x = 1338, y = 169, w = 1536, h = 2345
+ Box[131]: x = 320, y = 0, w = 1670, h = 3974
+ Box[132]: x = 1338, y = 169, w = 1495, h = 2350
+ Box[133]: x = 314, y = 0, w = 1693, h = 3985
+ Box[134]: x = 1344, y = 163, w = 1495, h = 2356
+ Box[135]: x = 320, y = 99, w = 1664, h = 3857
+ Box[136]: x = 1332, y = 163, w = 1501, h = 2356
+ Box[137]: x = 308, y = 32, w = 1699, h = 2478
+ Box[138]: x = 1344, y = 163, w = 1484, h = 2356
+ Box[139]: x = 372, y = 99, w = 1612, h = 2332
+ Box[140]: x = 1332, y = 163, w = 1495, h = 2362
+ Box[141]: x = 372, y = 97, w = 1646, h = 2396
+ Box[142]: x = 1332, y = 169, w = 1495, h = 2356
+ Box[143]: x = 372, y = 103, w = 1612, h = 2329
+ Box[144]: x = 1332, y = 163, w = 1489, h = 2362
+ Box[145]: x = 300, y = 87, w = 1684, h = 2321
+ Box[146]: x = 1332, y = 163, w = 1489, h = 2362
+ Box[147]: x = 372, y = 70, w = 1612, h = 2321
+ Box[148]: x = 1332, y = 163, w = 1501, h = 2362
+ Box[149]: x = 413, y = 52, w = 1577, h = 2326
+ Box[150]: x = 1338, y = 134, w = 1495, h = 2391
+ Box[151]: x = 465, y = 122, w = 1518, h = 2321
+ Box[152]: x = 1332, y = 163, w = 1501, h = 2362
+ Box[153]: x = 465, y = 122, w = 1518, h = 2321
+ Box[154]: x = 1332, y = 157, w = 1518, h = 2374
+ Box[155]: x = 471, y = 128, w = 1513, h = 2321
+ Box[156]: x = 1332, y = 163, w = 1495, h = 2368
+ Box[157]: x = 471, y = 128, w = 1513, h = 2316
+ Box[158]: x = 1332, y = 163, w = 1536, h = 2368
+ Box[159]: x = 399, y = 128, w = 1585, h = 2316
+ Box[160]: x = 1332, y = 163, w = 1536, h = 2368
+ Box[161]: x = 399, y = 128, w = 1585, h = 2317
+ Box[162]: x = 1326, y = 134, w = 1530, h = 2397
+ Box[163]: x = 399, y = 128, w = 1591, h = 2321
+ Box[164]: x = 1332, y = 163, w = 1524, h = 2374
+ Box[165]: x = 399, y = 128, w = 1585, h = 2317
+ Box[166]: x = 1332, y = 157, w = 1530, h = 2374
+ Box[167]: x = 399, y = 128, w = 1585, h = 2317
+ Box[168]: x = 1326, y = 157, w = 1536, h = 2374
+ Box[169]: x = 471, y = 128, w = 1513, h = 2321
+ Box[170]: x = 1332, y = 163, w = 1536, h = 2374
+ Box[171]: x = 471, y = 128, w = 1513, h = 2321
+ Box[172]: x = 1332, y = 163, w = 1530, h = 2374
+ Box[173]: x = 390, y = 127, w = 1594, h = 2323
+ Box[174]: x = 1326, y = 157, w = 1530, h = 2380
+ Box[175]: x = 372, y = 127, w = 1612, h = 2334
+ Box[176]: x = 1332, y = 157, w = 1530, h = 2385
+ Box[177]: x = 372, y = 127, w = 1612, h = 2323
+ Box[178]: x = 1332, y = 157, w = 1536, h = 2380
+ Box[179]: x = 372, y = 127, w = 1612, h = 2323
+ Box[180]: x = 1332, y = 157, w = 1530, h = 2380
+ Box[181]: x = 372, y = 127, w = 1612, h = 2323
+ Box[182]: x = 1326, y = 175, w = 1536, h = 2380
+ Box[183]: x = 367, y = 54, w = 1658, h = 2468
+ Box[184]: x = 1350, y = 175, w = 1524, h = 2380
+ Box[185]: x = 372, y = 54, w = 1641, h = 2468
+ Box[186]: x = 1344, y = 145, w = 1530, h = 2397
+ Box[187]: x = 372, y = 169, w = 1617, h = 2327
+ Box[188]: x = 1344, y = 157, w = 1530, h = 2380
+ Box[189]: x = 372, y = 157, w = 1612, h = 2339
+ Box[190]: x = 1344, y = 157, w = 1530, h = 2380
+ Box[191]: x = 372, y = 169, w = 1612, h = 2327
+ Box[192]: x = 1344, y = 163, w = 1530, h = 2380
+ Box[193]: x = 372, y = 169, w = 1612, h = 2327
+ Box[194]: x = 1350, y = 157, w = 1530, h = 2380
+ Box[195]: x = 367, y = 157, w = 1617, h = 2339
+ Box[196]: x = 1344, y = 157, w = 1530, h = 2380
+ Box[197]: x = 361, y = 96, w = 1623, h = 2473
+ Box[198]: x = 1350, y = 151, w = 1524, h = 2385
+ Box[199]: x = 372, y = 169, w = 1617, h = 2327
+ Box[200]: x = 1344, y = 157, w = 1530, h = 2380
+ Box[201]: x = 343, y = 169, w = 1641, h = 2327
+ Box[202]: x = 1361, y = 157, w = 1591, h = 2380
+ Box[203]: x = 303, y = 169, w = 1681, h = 2327
+ Box[204]: x = 1367, y = 128, w = 1513, h = 2409
+ Box[205]: x = 367, y = 163, w = 1617, h = 2333
+ Box[206]: x = 1356, y = 128, w = 1518, h = 2409
+ Box[207]: x = 367, y = 163, w = 1617, h = 2333
+ Box[208]: x = 1350, y = 151, w = 1603, h = 2385
+ Box[209]: x = 367, y = 163, w = 1623, h = 2327
+ Box[210]: x = 1332, y = 128, w = 1524, h = 2409
+ Box[211]: x = 308, y = 169, w = 1687, h = 2333
+ Box[212]: x = 1350, y = 140, w = 1513, h = 2397
+ Box[213]: x = 367, y = 169, w = 1623, h = 2327
+ Box[214]: x = 1326, y = 134, w = 1536, h = 2414
+ Box[215]: x = 367, y = 163, w = 1623, h = 2333
+ Box[216]: x = 1332, y = 134, w = 1530, h = 2403
+ Box[217]: x = 361, y = 169, w = 1629, h = 2333
+ Box[218]: x = 1350, y = 134, w = 1501, h = 2409
+ Box[219]: x = 308, y = 163, w = 1681, h = 2333
+ Box[220]: x = 1356, y = 128, w = 1501, h = 2414
+ Box[221]: x = 343, y = 163, w = 1646, h = 2333
+ Box[222]: x = 1350, y = 134, w = 1574, h = 2409
+ Box[223]: x = 361, y = 169, w = 1629, h = 2327
+ Box[224]: x = 1350, y = 128, w = 1513, h = 2414
+ Box[225]: x = 343, y = 163, w = 1646, h = 2333
+ Box[226]: x = 1356, y = 128, w = 1548, h = 2414
+ Box[227]: x = 303, y = 163, w = 1687, h = 2333
+ Box[228]: x = 1356, y = 128, w = 1489, h = 2414
+ Box[229]: x = 367, y = 90, w = 1623, h = 2473
+ Box[230]: x = 1350, y = 128, w = 1489, h = 2414
+ Box[231]: x = 308, y = 157, w = 1681, h = 2339
+ Box[232]: x = 1332, y = 122, w = 1518, h = 2414
+ Box[233]: x = 361, y = 163, w = 1635, h = 2339
+ Box[234]: x = 1350, y = 122, w = 1489, h = 2414
+ Box[235]: x = 349, y = 163, w = 1646, h = 2339
+ Box[236]: x = 1338, y = 122, w = 1466, h = 2414
+ Box[237]: x = 320, y = 163, w = 1670, h = 2339
+ Box[238]: x = 1350, y = 192, w = 1489, h = 2414
+ Box[239]: x = 337, y = 163, w = 1658, h = 2339
+ Box[240]: x = 1356, y = 192, w = 1507, h = 2414
+ Box[241]: x = 343, y = 163, w = 1646, h = 2339
+ Box[242]: x = 1326, y = 186, w = 1530, h = 2420
+ Box[243]: x = 343, y = 122, w = 1646, h = 2356
+ Box[244]: x = 1326, y = 186, w = 1524, h = 2414
+ Box[245]: x = 337, y = 140, w = 1652, h = 2339
+ Box[246]: x = 1326, y = 186, w = 1524, h = 2414
+ Box[247]: x = 332, y = 122, w = 1658, h = 2350
+ Box[248]: x = 1350, y = 186, w = 1518, h = 2420
+ Box[249]: x = 337, y = 111, w = 1652, h = 2350
+ Box[250]: x = 1326, y = 186, w = 1530, h = 2414
+ Box[251]: x = 332, y = 111, w = 1658, h = 2350
+ Box[252]: x = 1344, y = 186, w = 1513, h = 2414
+ Box[253]: x = 320, y = 105, w = 1670, h = 2380
+ Box[254]: x = 1338, y = 186, w = 1565, h = 2414
+ Box[255]: x = 337, y = 116, w = 1652, h = 2339
+ Box[256]: x = 1344, y = 186, w = 1513, h = 2414
+ Box[257]: x = 326, y = 128, w = 1664, h = 2345
+ Box[258]: x = 1350, y = 186, w = 1513, h = 2414
+ Box[259]: x = 326, y = 128, w = 1664, h = 2362
+ Box[260]: x = 1332, y = 186, w = 1577, h = 2414
+ Box[261]: x = 326, y = 128, w = 1664, h = 2362
+ Box[262]: x = 1344, y = 186, w = 1571, h = 2420
+ Box[263]: x = 332, y = 122, w = 1658, h = 2362
+ Box[264]: x = 1338, y = 180, w = 1524, h = 2420
+ Box[265]: x = 332, y = 116, w = 1658, h = 2362
+ Box[266]: x = 1338, y = 186, w = 1518, h = 2420
+ Box[267]: x = 332, y = 111, w = 1658, h = 2362
+ Box[268]: x = 1321, y = 186, w = 1588, h = 2420
+ Box[269]: x = 337, y = 105, w = 1652, h = 2368
+ Box[270]: x = 1361, y = 180, w = 1501, h = 2420
+ Box[271]: x = 332, y = 116, w = 1658, h = 2350
+ Box[272]: x = 1300, y = 180, w = 1603, h = 2493
+ Box[273]: x = 337, y = 99, w = 1652, h = 2374
+ Box[274]: x = 1361, y = 180, w = 1501, h = 2420
+ Box[275]: x = 337, y = 111, w = 1652, h = 2350
+ Box[276]: x = 1300, y = 180, w = 1609, h = 2493
+ Box[277]: x = 343, y = 111, w = 1646, h = 2350
+ Box[278]: x = 1367, y = 180, w = 1489, h = 2420
+ Box[279]: x = 355, y = 99, w = 1635, h = 2356
+ Box[280]: x = 1306, y = 180, w = 1614, h = 2493
+ Box[281]: x = 343, y = 105, w = 1646, h = 2345
+ Box[282]: x = 1379, y = 175, w = 1530, h = 2426
+ Box[283]: x = 361, y = 111, w = 1629, h = 2339
+ Box[284]: x = 1373, y = 180, w = 1489, h = 2426
+ Box[285]: x = 361, y = 105, w = 1629, h = 2339
+ Box[286]: x = 1379, y = 175, w = 1484, h = 2432
+ Box[287]: x = 367, y = 105, w = 1623, h = 2339
+ Box[288]: x = 1379, y = 180, w = 1524, h = 2426
+ Box[289]: x = 367, y = 111, w = 1623, h = 2339
+ Box[290]: x = 1379, y = 175, w = 1518, h = 2432
+ Box[291]: x = 372, y = 105, w = 1617, h = 2345
+ Box[292]: x = 1379, y = 175, w = 1559, h = 2432
+ Box[293]: x = 361, y = 105, w = 1629, h = 2345
+ Box[294]: x = 1356, y = 175, w = 1536, h = 2432
+ Box[295]: x = 372, y = 105, w = 1617, h = 2339
+ Box[296]: x = 1385, y = 175, w = 1524, h = 2432
+ Box[297]: x = 355, y = 99, w = 1635, h = 2339
+ Box[298]: x = 1396, y = 175, w = 1501, h = 2432
+ Box[299]: x = 367, y = 93, w = 1623, h = 2339
+ Box[300]: x = 1396, y = 175, w = 1518, h = 2432
+ Box[301]: x = 384, y = 93, w = 1606, h = 2339
+ Box[302]: x = 1396, y = 175, w = 1553, h = 2432
+ Box[303]: x = 384, y = 93, w = 1606, h = 2333
+ Box[304]: x = 1329, y = 175, w = 1603, h = 2505
+ Box[305]: x = 390, y = 93, w = 1600, h = 2333
+ Box[306]: x = 1341, y = 175, w = 1591, h = 2510
+ Box[307]: x = 384, y = 93, w = 1600, h = 2339
+ Box[308]: x = 1324, y = 180, w = 1603, h = 2505
+ Box[309]: x = 390, y = 93, w = 1600, h = 2327
+ Box[310]: x = 1379, y = 169, w = 1524, h = 2438
+ Box[311]: x = 390, y = 93, w = 1600, h = 2339
+ Box[312]: x = 1329, y = 180, w = 1603, h = 2510
+ Box[313]: x = 367, y = 93, w = 1617, h = 2327
+ Box[314]: x = 1390, y = 163, w = 1507, h = 2449
+ Box[315]: x = 367, y = 93, w = 1617, h = 2333
+ Box[316]: x = 1396, y = 175, w = 1513, h = 2444
+ Box[317]: x = 384, y = 87, w = 1606, h = 2333
+ Box[318]: x = 1402, y = 175, w = 1495, h = 2444
+ Box[319]: x = 326, y = 122, w = 1658, h = 2333
+ Box[320]: x = 1390, y = 186, w = 1507, h = 2438
+ Box[321]: x = 384, y = 122, w = 1600, h = 2333
+ Box[322]: x = 1329, y = 186, w = 1614, h = 2516
+ Box[323]: x = 401, y = 122, w = 1582, h = 2333
+ Box[324]: x = 1402, y = 180, w = 1495, h = 2444
+ Box[325]: x = 401, y = 122, w = 1582, h = 2333
+ Box[326]: x = 1420, y = 180, w = 1478, h = 2444
+ Box[327]: x = 401, y = 122, w = 1582, h = 2327
+ Box[328]: x = 1353, y = 180, w = 1545, h = 2516
+ Box[329]: x = 407, y = 111, w = 1577, h = 2339
+ Box[330]: x = 1425, y = 180, w = 1524, h = 2449
+ Box[331]: x = 407, y = 122, w = 1577, h = 2339
+ Box[332]: x = 1437, y = 180, w = 1460, h = 2449
+ Box[333]: x = 349, y = 122, w = 1635, h = 2339
+ Box[334]: x = 1358, y = 180, w = 1533, h = 2449
+ Box[335]: x = 390, y = 116, w = 1588, h = 2339
+ Box[336]: x = 1402, y = 186, w = 1495, h = 2444
+ Box[337]: x = 431, y = 116, w = 1548, h = 2339
+ Box[338]: x = 1431, y = 175, w = 1460, h = 2455
+ Box[339]: x = 431, y = 116, w = 1548, h = 2339
+ Box[340]: x = 1449, y = 180, w = 1454, h = 2455
+ Box[341]: x = 431, y = 116, w = 1548, h = 2345
+ Box[342]: x = 1443, y = 186, w = 1454, h = 2449
+ Box[343]: x = 448, y = 157, w = 1530, h = 2345
+ Box[344]: x = 1449, y = 180, w = 1454, h = 2449
+ Box[345]: x = 419, y = 93, w = 1559, h = 2339
+ Box[346]: x = 1443, y = 169, w = 1501, h = 2461
+ Box[347]: x = 396, y = 93, w = 1582, h = 2333
+ Box[348]: x = 1443, y = 186, w = 1507, h = 2449
+ Box[349]: x = 431, y = 93, w = 1548, h = 2333
+ Box[350]: x = 1443, y = 169, w = 1542, h = 2461
+ Box[351]: x = 454, y = 87, w = 1524, h = 2333
+ Box[352]: x = 1443, y = 169, w = 1536, h = 2467
+ Box[353]: x = 454, y = 76, w = 1524, h = 2333
+ Box[354]: x = 1376, y = 105, w = 1574, h = 2598
+ Box[355]: x = 460, y = 76, w = 1518, h = 2333
+ Box[356]: x = 1449, y = 175, w = 1536, h = 2455
+ Box[357]: x = 460, y = 70, w = 1518, h = 2333
+ Box[358]: x = 1449, y = 169, w = 1536, h = 2467
+ Box[359]: x = 460, y = 64, w = 1518, h = 2333
+ Box[360]: x = 1443, y = 169, w = 1513, h = 2467
+ Box[361]: x = 460, y = 58, w = 1518, h = 2333
+ Box[362]: x = 1420, y = 169, w = 1553, h = 2467
+ Box[363]: x = 460, y = 58, w = 1518, h = 2339
+ Box[364]: x = 1385, y = 169, w = 1565, h = 2473
+ Box[365]: x = 460, y = 58, w = 1518, h = 2333
+ Box[366]: x = 1379, y = 169, w = 1565, h = 2473
+ Box[367]: x = 460, y = 58, w = 1518, h = 2333
+ Box[368]: x = 1367, y = 169, w = 1593, h = 2473
+ Box[369]: x = 460, y = 35, w = 1591, h = 2406
+ Box[370]: x = 1308, y = 121, w = 1642, h = 2612
+ Box[371]: x = 442, y = 47, w = 1609, h = 2406
+ Box[372]: x = 1308, y = 121, w = 1642, h = 2612
+ Box[373]: x = 460, y = 103, w = 1591, h = 2406
+ Box[374]: x = 0, y = 0, w = 0, h = 0
+ Box[375]: x = 0, y = 0, w = 0, h = 0
diff --git a/leptonica/prog/boxa3_reg.c b/leptonica/prog/boxa3_reg.c
new file mode 100644
index 00000000..a3a5729a
--- /dev/null
+++ b/leptonica/prog/boxa3_reg.c
@@ -0,0 +1,202 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * boxa3_reg.c
+ *
+ * Higher-level operations that can search for anomalous-sized boxes
+ * in a boxa, where the widths and heights of the boxes are expected
+ * to be similar. These can be corrected by moving the appropriate
+ * sides of the outlier boxes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const char *boxafiles[3] = {"boxap1.ba", "boxap2.ba", "boxap3.ba"};
+
+static void TestBoxa(L_REGPARAMS *rp, l_int32 index);
+static void PlotBoxa(L_REGPARAMS *rp, l_int32 index);
+static l_float32 varp[3] = {0.0165, 0.0432, 0.0716};
+static l_float32 varm[3] = {0.0088, 0.0213, 0.0357};
+static l_int32 same[3] = {1, -1, -1};
+static l_float32 devwidth[3] = {0.0864, 0.0895, 0.1174};
+static l_float32 devheight[3] = {0.0048, 0.0294, 0.0023};
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "boxa3_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ for (i = 0; i < 3; i++)
+ TestBoxa(rp, i);
+ for (i = 0; i < 3; i++)
+ PlotBoxa(rp, i);
+ return regTestCleanup(rp);
+}
+
+static void
+TestBoxa(L_REGPARAMS *rp,
+ l_int32 index)
+{
+l_uint8 *data;
+l_int32 w, h, medw, medh, isame;
+size_t size;
+l_float32 scalefact, devw, devh, ratiowh, fvarp, fvarm;
+BOXA *boxa1, *boxa2, *boxa3;
+PIX *pix1;
+
+ boxa1 = boxaRead(boxafiles[index]);
+
+ /* Read and display initial boxa */
+ boxaGetExtent(boxa1, &w, &h, NULL);
+ scalefact = 100.0 / (l_float32)w;
+ boxa2 = boxaTransform(boxa1, 0, 0, scalefact, scalefact);
+ boxaWriteMem(&data, &size, boxa2);
+ regTestWriteDataAndCheck(rp, data, size, "ba"); /* 0, 13, 26 */
+ lept_free(data);
+ pix1 = boxaDisplayTiled(boxa2, NULL, 0, -1, 2200, 2, 1.0, 0, 3, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1, 14, 27 */
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ /* Find the median sizes */
+ boxaMedianDimensions(boxa2, &medw, &medh, NULL, NULL, NULL, NULL,
+ NULL, NULL);
+ if (rp->display)
+ lept_stderr("median width = %d, median height = %d\n", medw, medh);
+
+ /* Check for deviations from median by pairs: method 1 */
+ boxaSizeConsistency1(boxa2, L_CHECK_HEIGHT, 0.0, 0.0,
+ &fvarp, &fvarm, &isame);
+ regTestCompareValues(rp, varp[index], fvarp, 0.003); /* 2, 15, 28 */
+ regTestCompareValues(rp, varm[index], fvarm, 0.003); /* 3, 16, 29 */
+ regTestCompareValues(rp, same[index], isame, 0); /* 4, 17, 30 */
+ if (rp->display)
+ lept_stderr("fvarp = %7.4f, fvarm = %7.4f, same = %d\n",
+ fvarp, fvarm, isame);
+
+ /* Check for deviations from median by pairs: method 2 */
+ boxaSizeConsistency2(boxa2, &devw, &devh, 0);
+ regTestCompareValues(rp, devwidth[index], devw, 0.001); /* 5, 18, 31 */
+ regTestCompareValues(rp, devheight[index], devh, 0.001); /* 6, 19, 32 */
+ if (rp->display)
+ lept_stderr("dev width = %7.4f, dev height = %7.4f\n", devw, devh);
+
+ /* Reconcile widths */
+ boxa3 = boxaReconcileSizeByMedian(boxa2, L_CHECK_WIDTH, 0.05, 0.04, 1.03,
+ NULL, NULL, &ratiowh);
+ boxaWriteMem(&data, &size, boxa3);
+ regTestWriteDataAndCheck(rp, data, size, "ba"); /* 7, 20, 33 */
+ lept_free(data);
+ pix1 = boxaDisplayTiled(boxa3, NULL, 0, -1, 2200, 2, 1.0, 0, 3, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 8, 21, 34 */
+ pixDisplayWithTitle(pix1, 500, 0, NULL, rp->display);
+ if (rp->display)
+ lept_stderr("ratio median width/height = %6.3f\n", ratiowh);
+ boxaDestroy(&boxa3);
+ pixDestroy(&pix1);
+
+ /* Reconcile heights */
+ boxa3 = boxaReconcileSizeByMedian(boxa2, L_CHECK_HEIGHT, 0.05, 0.04, 1.03,
+ NULL, NULL, NULL);
+ boxaWriteMem(&data, &size, boxa3);
+ regTestWriteDataAndCheck(rp, data, size, "ba"); /* 9, 22, 35 */
+ lept_free(data);
+ pix1 = boxaDisplayTiled(boxa3, NULL, 0, -1, 2200, 2, 1.0, 0, 3, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10, 23, 36 */
+ pixDisplayWithTitle(pix1, 1000, 0, NULL, rp->display);
+ boxaDestroy(&boxa3);
+ pixDestroy(&pix1);
+
+ /* Reconcile both widths and heights */
+ boxa3 = boxaReconcileSizeByMedian(boxa2, L_CHECK_BOTH, 0.05, 0.04, 1.03,
+ NULL, NULL, NULL);
+ boxaWriteMem(&data, &size, boxa3);
+ regTestWriteDataAndCheck(rp, data, size, "ba"); /* 11, 24, 37 */
+ lept_free(data);
+ pix1 = boxaDisplayTiled(boxa3, NULL, 0, -1, 2200, 2, 1.0, 0, 3, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 12, 25, 38 */
+ pixDisplayWithTitle(pix1, 1500, 0, NULL, rp->display);
+ boxaDestroy(&boxa3);
+ pixDestroy(&pix1);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+}
+
+
+static void
+PlotBoxa(L_REGPARAMS *rp,
+ l_int32 index)
+{
+BOXA *boxa1, *boxa2;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa;
+
+ boxa1 = boxaRead(boxafiles[index]);
+
+ /* Read and display initial boxa */
+ boxaPlotSizes(boxa1, NULL, NULL, NULL, &pix1);
+ boxaPlotSides(boxa1, NULL, NULL, NULL, NULL, NULL, &pix2);
+ pixa = pixaCreate(2);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix3 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 2);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 39, 41, 43 */
+ pixDisplayWithTitle(pix3, 0 + 800 * index, 500, NULL, rp->display);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+
+ /* Read and display reconciled boxa */
+ boxa2 = boxaReconcileSizeByMedian(boxa1, L_CHECK_BOTH, 0.05, 0.04, 1.03,
+ NULL, NULL, NULL);
+ boxaPlotSizes(boxa2, NULL, NULL, NULL, &pix1);
+ boxaPlotSides(boxa2, NULL, NULL, NULL, NULL, NULL, &pix2);
+ pixa = pixaCreate(2);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix3 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 2);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 40, 42, 44 */
+ pixDisplayWithTitle(pix3, 0 + 800 * index, 920, NULL, rp->display);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+}
diff --git a/leptonica/prog/boxa4.ba b/leptonica/prog/boxa4.ba
new file mode 100644
index 00000000..3590453a
--- /dev/null
+++ b/leptonica/prog/boxa4.ba
@@ -0,0 +1,55 @@
+
+Boxa Version 2
+Number of boxes = 52
+ Box[0]: x = 313, y = 369, w = 892, h = 1255
+ Box[1]: x = 877, y = 379, w = 908, h = 1293
+ Box[2]: x = 300, y = 355, w = 952, h = 1283
+ Box[3]: x = 744, y = 376, w = 896, h = 1305
+ Box[4]: x = 333, y = 390, w = 985, h = 1281
+ Box[5]: x = 697, y = 490, w = 849, h = 1279
+ Box[6]: x = 379, y = 315, w = 992, h = 1328
+ Box[7]: x = 801, y = 372, w = 713, h = 1293
+ Box[8]: x = 373, y = 383, w = 1040, h = 1273
+ Box[9]: x = 713, y = 374, w = 705, h = 1291
+ Box[10]: x = 398, y = 376, w = 996, h = 1273
+ Box[11]: x = 797, y = 371, w = 726, h = 1296
+ Box[12]: x = 421, y = 373, w = 1138, h = 1258
+ Box[13]: x = 799, y = 369, w = 725, h = 1294
+ Box[14]: x = 379, y = 379, w = 1028, h = 1271
+ Box[15]: x = 839, y = 371, w = 709, h = 1292
+ Box[16]: x = 399, y = 377, w = 1050, h = 1305
+ Box[17]: x = 735, y = 371, w = 742, h = 1292
+ Box[18]: x = 363, y = 386, w = 989, h = 1275
+ Box[19]: x = 711, y = 386, w = 730, h = 1282
+ Box[20]: x = 395, y = 375, w = 1062, h = 1288
+ Box[21]: x = 595, y = 491, w = 776, h = 1286
+ Box[22]: x = 387, y = 390, w = 963, h = 1271
+ Box[23]: x = 845, y = 392, w = 933, h = 1307
+ Box[24]: x = 397, y = 412, w = 933, h = 1281
+ Box[25]: x = 805, y = 391, w = 912, h = 1273
+ Box[26]: x = 351, y = 375, w = 950, h = 1271
+ Box[27]: x = 737, y = 405, w = 901, h = 1319
+ Box[28]: x = 390, y = 443, w = 984, h = 1305
+ Box[29]: x = 806, y = 381, w = 836, h = 1302
+ Box[30]: x = 401, y = 373, w = 988, h = 1348
+ Box[31]: x = 645, y = 443, w = 745, h = 1305
+ Box[32]: x = 660, y = 390, w = 685, h = 1277
+ Box[33]: x = 736, y = 398, w = 741, h = 1287
+ Box[34]: x = 671, y = 355, w = 645, h = 1277
+ Box[35]: x = 741, y = 391, w = 817, h = 1299
+ Box[36]: x = 756, y = 365, w = 671, h = 1275
+ Box[37]: x = 689, y = 381, w = 853, h = 1314
+ Box[38]: x = 728, y = 364, w = 691, h = 1287
+ Box[39]: x = 849, y = 389, w = 718, h = 1282
+ Box[40]: x = 717, y = 372, w = 665, h = 1276
+ Box[41]: x = 792, y = 394, w = 822, h = 1258
+ Box[42]: x = 719, y = 374, w = 653, h = 1246
+ Box[43]: x = 772, y = 374, w = 653, h = 1275
+ Box[44]: x = 570, y = 374, w = 783, h = 1302
+ Box[45]: x = 747, y = 370, w = 623, h = 1268
+ Box[46]: x = 709, y = 442, w = 674, h = 1269
+ Box[47]: x = 812, y = 384, w = 626, h = 1273
+ Box[48]: x = 576, y = 384, w = 802, h = 1285
+ Box[49]: x = 714, y = 387, w = 641, h = 1276
+ Box[50]: x = 705, y = 356, w = 690, h = 1266
+ Box[51]: x = 875, y = 380, w = 670, h = 1275
diff --git a/leptonica/prog/boxa4_reg.c b/leptonica/prog/boxa4_reg.c
new file mode 100644
index 00000000..cb7a4797
--- /dev/null
+++ b/leptonica/prog/boxa4_reg.c
@@ -0,0 +1,226 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * boxa4_reg.c
+ *
+ * This carries out some smoothing and display operations on boxa.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_uint8 *data;
+l_int32 i, same, w, h, width, success, nb;
+size_t size;
+l_float32 scalefact;
+BOXA *boxa1, *boxa1e, *boxa1o, *boxa2, *boxa2e, *boxa2o;
+BOXA *boxa3, *boxa3e, *boxa3o;
+BOXAA *baa1, *baa2, *baa3;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa1, *pixa2;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "boxa4_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/boxa");
+
+ /* Input is a fairly clean boxa */
+ boxa1 = boxaRead("boxa1.ba");
+ boxa2 = boxaReconcileEvenOddHeight(boxa1, L_ADJUST_TOP, 80,
+ L_ADJUST_CHOOSE_MIN, 1.05, 1);
+ width = 100;
+ boxaGetExtent(boxa2, &w, &h, NULL);
+ scalefact = (l_float32)width / (l_float32)w;
+ boxa3 = boxaTransform(boxa2, 0, 0, scalefact, scalefact);
+ pix1 = boxaDisplayTiled(boxa3, NULL, 0, -1, 1500, 2, 1.0, 0, 3, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix1, 600, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+
+ /* Input is an unsmoothed and noisy boxa */
+ boxa1 = boxaRead("boxa2.ba");
+ boxa2 = boxaReconcileEvenOddHeight(boxa1, L_ADJUST_TOP, 80,
+ L_ADJUST_CHOOSE_MIN, 1.05, 1);
+ width = 100;
+ boxaGetExtent(boxa2, &w, &h, NULL);
+ scalefact = (l_float32)width / (l_float32)w;
+ boxa3 = boxaTransform(boxa2, 0, 0, scalefact, scalefact);
+ pix1 = boxaDisplayTiled(boxa3, NULL, 0, -1, 1500, 2, 1.0, 0, 3, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix1, 800, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+
+ /* Input is an unsmoothed and noisy boxa */
+ boxa1 = boxaRead("boxa2.ba");
+ boxa2 = boxaSmoothSequenceMedian(boxa1, 10, L_SUB_ON_LOC_DIFF, 80, 20, 1);
+ boxa3 = boxaSmoothSequenceMedian(boxa1, 10, L_SUB_ON_SIZE_DIFF, 80, 20, 1);
+ boxaPlotSides(boxa1, "initial", NULL, NULL, NULL, NULL, &pix1);
+ boxaPlotSides(boxa2, "side-smoothing", NULL, NULL, NULL, NULL, &pix2);
+ boxaPlotSides(boxa3, "size-smoothing", NULL, NULL, NULL, NULL, &pix3);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 3 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix1, 1300, 0, NULL, rp->display);
+ pixDisplayWithTitle(pix2, 1300, 500, NULL, rp->display);
+ pixDisplayWithTitle(pix3, 1300, 1000, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+
+ /* Reconcile all sides by median */
+ boxa1 = boxaRead("boxa5.ba");
+ pixa1 = pixaCreate(0);
+ boxa2 = boxaReconcileAllByMedian(boxa1, L_ADJUST_LEFT_AND_RIGHT,
+ L_ADJUST_TOP_AND_BOT, 50, 0, pixa1);
+ boxaWriteMem(&data, &size, boxa2);
+ regTestWriteDataAndCheck(rp, data, size, "ba"); /* 5 */
+ pix1 = pixRead("/tmp/lept/boxa/recon_sides.png");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ lept_free(data);
+ pixaDestroy(&pixa1);
+ boxaDestroy(&boxa2);
+ pixDestroy(&pix1);
+
+ /* Reconcile top/bot sides by median */
+ pixa1 = pixaCreate(0);
+ boxa2 = boxaReconcileAllByMedian(boxa1, L_ADJUST_SKIP,
+ L_ADJUST_TOP_AND_BOT, 50, 0, pixa1);
+ boxaWriteMem(&data, &size, boxa2);
+ regTestWriteDataAndCheck(rp, data, size, "ba"); /* 7 */
+ pix1 = pixRead("/tmp/lept/boxa/recon_sides.png");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 8 */
+ pixDisplayWithTitle(pix1, 0, 300, NULL, rp->display);
+ lept_free(data);
+ pixaDestroy(&pixa1);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ pixDestroy(&pix1);
+
+ /* Split even/odd and reconcile all sides by median */
+ boxa1 = boxaRead("boxa5.ba");
+ pixa1 = pixaCreate(0);
+ boxaSplitEvenOdd(boxa1, 0, &boxa1e, &boxa1o);
+ boxa2e = boxaReconcileSidesByMedian(boxa1e, L_ADJUST_TOP_AND_BOT, 50,
+ 0, pixa1);
+ boxa3e = boxaReconcileSidesByMedian(boxa2e, L_ADJUST_LEFT_AND_RIGHT, 50,
+ 0, pixa1);
+ boxa2o = boxaReconcileSidesByMedian(boxa1o, L_ADJUST_TOP_AND_BOT, 50,
+ 0, pixa1);
+ boxa3o = boxaReconcileSidesByMedian(boxa2o, L_ADJUST_LEFT_AND_RIGHT, 50,
+ 0, pixa1);
+ boxa3 = boxaMergeEvenOdd(boxa3e, boxa3o, 0);
+ boxaWriteMem(&data, &size, boxa3);
+ regTestWriteDataAndCheck(rp, data, size, "ba"); /* 9 */
+ if (rp->display) {
+ pix1 = pixaDisplayTiledInRows(pixa1, 32, 1800, 0.5, 0, 30, 2);
+ pixDisplay(pix1, 800, 500);
+ pixDestroy(&pix1);
+ }
+ lept_free(data);
+ pixaDestroy(&pixa1);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa1e);
+ boxaDestroy(&boxa1o);
+ boxaDestroy(&boxa2e);
+ boxaDestroy(&boxa2o);
+ boxaDestroy(&boxa3);
+ boxaDestroy(&boxa3e);
+ boxaDestroy(&boxa3o);
+
+ /* Input is a boxa smoothed with a median window filter */
+ boxa1 = boxaRead("boxa3.ba");
+ boxa2 = boxaReconcileEvenOddHeight(boxa1, L_ADJUST_TOP, 80,
+ L_ADJUST_CHOOSE_MIN, 1.05, 1);
+ width = 100;
+ boxaGetExtent(boxa2, &w, &h, NULL);
+ scalefact = (l_float32)width / (l_float32)w;
+ boxa3 = boxaTransform(boxa2, 0, 0, scalefact, scalefact);
+ pix1 = boxaDisplayTiled(boxa3, NULL, 0, -1, 1500, 2, 1.0, 0, 3, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10 */
+ pixDisplayWithTitle(pix1, 1000, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+
+ /* ----------- Test pixaDisplayBoxaa() ------------ */
+ pixa1 = pixaReadBoth("showboxes.pac");
+ baa1 = boxaaRead("showboxes1.baa");
+ baa2 = boxaaTranspose(baa1);
+ baa3 = boxaaTranspose(baa2);
+ nb = boxaaGetCount(baa1);
+ success = TRUE;
+ for (i = 0; i < nb; i++) {
+ boxa1 = boxaaGetBoxa(baa1, i, L_CLONE);
+ boxa2 = boxaaGetBoxa(baa3, i, L_CLONE);
+ boxaEqual(boxa1, boxa2, 0, NULL, &same);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ if (!same) success = FALSE;
+ }
+
+ /* Check that the transpose is reversible */
+ regTestCompareValues(rp, 1, success, 0.0); /* 11 */
+ pixa2 = pixaDisplayBoxaa(pixa1, baa2, L_DRAW_RGB, 2);
+ pix1 = pixaDisplayTiledInRows(pixa2, 32, 1400, 1.0, 0, 10, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 12 */
+ pixDisplayWithTitle(pix1, 0, 600, NULL, rp->display);
+ lept_stderr("Writing to: /tmp/lept/boxa/show.pdf\n");
+ l_pdfSetDateAndVersion(FALSE);
+ pixaConvertToPdf(pixa2, 75, 0.6, 0, 0, NULL, "/tmp/lept/boxa/show.pdf");
+ regTestCheckFile(rp, "/tmp/lept/boxa/show.pdf"); /* 13 */
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ boxaaDestroy(&baa1);
+ boxaaDestroy(&baa2);
+ boxaaDestroy(&baa3);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/boxa5.ba b/leptonica/prog/boxa5.ba
new file mode 100644
index 00000000..f501930e
--- /dev/null
+++ b/leptonica/prog/boxa5.ba
@@ -0,0 +1,21 @@
+
+Boxa Version 2
+Number of boxes = 18
+ Box[0]: x = 800, y = 808, w = 1600, h = 2016
+ Box[1]: x = 0, y = 1318, w = 1956, h = 2406
+ Box[2]: x = 800, y = 808, w = 1600, h = 2016
+ Box[3]: x = 0, y = 1318, w = 1956, h = 2406
+ Box[4]: x = 800, y = 808, w = 1600, h = 2016
+ Box[5]: x = 0, y = 1368, w = 1956, h = 2356
+ Box[6]: x = 720, y = 1332, w = 1952, h = 2380
+ Box[7]: x = 0, y = 1368, w = 1956, h = 2356
+ Box[8]: x = 720, y = 1332, w = 1952, h = 2376
+ Box[9]: x = 0, y = 1368, w = 1956, h = 2356
+ Box[10]: x = 720, y = 1332, w = 1952, h = 2380
+ Box[11]: x = 0, y = 1368, w = 1956, h = 2356
+ Box[12]: x = 720, y = 1332, w = 1952, h = 2376
+ Box[13]: x = 0, y = 1368, w = 1956, h = 2356
+ Box[14]: x = 720, y = 1332, w = 1952, h = 2376
+ Box[15]: x = 0, y = 1368, w = 1956, h = 2356
+ Box[16]: x = 700, y = 1332, w = 1972, h = 2376
+ Box[17]: x = 0, y = 1368, w = 1956, h = 2356
diff --git a/leptonica/prog/boxap1.ba b/leptonica/prog/boxap1.ba
new file mode 100644
index 00000000..52983fce
--- /dev/null
+++ b/leptonica/prog/boxap1.ba
@@ -0,0 +1,129 @@
+
+Boxa Version 2
+Number of boxes = 126
+ Box[0]: x = 0, y = 0, w = 0, h = 0
+ Box[1]: x = 0, y = 0, w = 0, h = 0
+ Box[2]: x = 0, y = 0, w = 0, h = 0
+ Box[3]: x = 0, y = 0, w = 0, h = 0
+ Box[4]: x = 0, y = 0, w = 0, h = 0
+ Box[5]: x = 0, y = 0, w = 2627, h = 3321
+ Box[6]: x = 0, y = 59, w = 2575, h = 3292
+ Box[7]: x = 558, y = 0, w = 1998, h = 3227
+ Box[8]: x = 364, y = 59, w = 2345, h = 3198
+ Box[9]: x = 270, y = 0, w = 2433, h = 3245
+ Box[10]: x = 335, y = 59, w = 2369, h = 3209
+ Box[11]: x = 259, y = 0, w = 2445, h = 3245
+ Box[12]: x = 323, y = 59, w = 2375, h = 3274
+ Box[13]: x = 247, y = 0, w = 2457, h = 3250
+ Box[14]: x = 347, y = 59, w = 2416, h = 3292
+ Box[15]: x = 270, y = 0, w = 2433, h = 3245
+ Box[16]: x = 312, y = 59, w = 2369, h = 3209
+ Box[17]: x = 247, y = 0, w = 2463, h = 3250
+ Box[18]: x = 306, y = 59, w = 1910, h = 3203
+ Box[19]: x = 241, y = 0, w = 1211, h = 3250
+ Box[20]: x = 323, y = 59, w = 2363, h = 3215
+ Box[21]: x = 253, y = 0, w = 2451, h = 3250
+ Box[22]: x = 294, y = 59, w = 2433, h = 3198
+ Box[23]: x = 247, y = 0, w = 1252, h = 3250
+ Box[24]: x = 229, y = 59, w = 2433, h = 3292
+ Box[25]: x = 223, y = 0, w = 2486, h = 3262
+ Box[26]: x = 265, y = 59, w = 2451, h = 3274
+ Box[27]: x = 229, y = 0, w = 2475, h = 3256
+ Box[28]: x = 294, y = 59, w = 2433, h = 3286
+ Box[29]: x = 241, y = 0, w = 2463, h = 3250
+ Box[30]: x = 300, y = 59, w = 2428, h = 3203
+ Box[31]: x = 259, y = 0, w = 2445, h = 3250
+ Box[32]: x = 300, y = 59, w = 2433, h = 3227
+ Box[33]: x = 253, y = 0, w = 2451, h = 3250
+ Box[34]: x = 282, y = 59, w = 2439, h = 3209
+ Box[35]: x = 235, y = 0, w = 2469, h = 3250
+ Box[36]: x = 270, y = 59, w = 1969, h = 3227
+ Box[37]: x = 229, y = 0, w = 2463, h = 3256
+ Box[38]: x = 282, y = 59, w = 1998, h = 3198
+ Box[39]: x = 235, y = 0, w = 1869, h = 3256
+ Box[40]: x = 282, y = 59, w = 1987, h = 3209
+ Box[41]: x = 247, y = 0, w = 2451, h = 3262
+ Box[42]: x = 288, y = 59, w = 2439, h = 3215
+ Box[43]: x = 259, y = 0, w = 2445, h = 3256
+ Box[44]: x = 282, y = 59, w = 2463, h = 3215
+ Box[45]: x = 265, y = 0, w = 2433, h = 3256
+ Box[46]: x = 241, y = 59, w = 2398, h = 3221
+ Box[47]: x = 0, y = 0, w = 2680, h = 3350
+ Box[48]: x = 194, y = 59, w = 2457, h = 3250
+ Box[49]: x = 247, y = 0, w = 2433, h = 3292
+ Box[50]: x = 223, y = 59, w = 2433, h = 3239
+ Box[51]: x = 188, y = 0, w = 2486, h = 3280
+ Box[52]: x = 170, y = 59, w = 2463, h = 3321
+ Box[53]: x = 206, y = 0, w = 2480, h = 3280
+ Box[54]: x = 170, y = 59, w = 2492, h = 3245
+ Box[55]: x = 212, y = 0, w = 2463, h = 3286
+ Box[56]: x = 200, y = 59, w = 2457, h = 3245
+ Box[57]: x = 194, y = 0, w = 2469, h = 3280
+ Box[58]: x = 223, y = 59, w = 2433, h = 3221
+ Box[59]: x = 165, y = 0, w = 2498, h = 3286
+ Box[60]: x = 217, y = 59, w = 2433, h = 3233
+ Box[61]: x = 182, y = 0, w = 2463, h = 3286
+ Box[62]: x = 235, y = 59, w = 2422, h = 3262
+ Box[63]: x = 170, y = 0, w = 2486, h = 3286
+ Box[64]: x = 217, y = 59, w = 2433, h = 3239
+ Box[65]: x = 182, y = 0, w = 2457, h = 3286
+ Box[66]: x = 212, y = 59, w = 2439, h = 3245
+ Box[67]: x = 159, y = 0, w = 2480, h = 3286
+ Box[68]: x = 217, y = 59, w = 2433, h = 3227
+ Box[69]: x = 176, y = 0, w = 2463, h = 3286
+ Box[70]: x = 165, y = 59, w = 2492, h = 3233
+ Box[71]: x = 153, y = 0, w = 2480, h = 3292
+ Box[72]: x = 194, y = 59, w = 2451, h = 3221
+ Box[73]: x = 165, y = 0, w = 1740, h = 3292
+ Box[74]: x = 165, y = 59, w = 2486, h = 3239
+ Box[75]: x = 147, y = 0, w = 1769, h = 3292
+ Box[76]: x = 176, y = 59, w = 2463, h = 3239
+ Box[77]: x = 165, y = 0, w = 1487, h = 3292
+ Box[78]: x = 147, y = 59, w = 2134, h = 3215
+ Box[79]: x = 129, y = 0, w = 2486, h = 3298
+ Box[80]: x = 147, y = 59, w = 2128, h = 3215
+ Box[81]: x = 364, y = 0, w = 2269, h = 3292
+ Box[82]: x = 147, y = 59, w = 2492, h = 3215
+ Box[83]: x = 118, y = 0, w = 1417, h = 3292
+ Box[84]: x = 176, y = 59, w = 1934, h = 3215
+ Box[85]: x = 535, y = 0, w = 2116, h = 3292
+ Box[86]: x = 188, y = 59, w = 2451, h = 3221
+ Box[87]: x = 159, y = 0, w = 1440, h = 3298
+ Box[88]: x = 176, y = 59, w = 2093, h = 3233
+ Box[89]: x = 370, y = 0, w = 2063, h = 3292
+ Box[90]: x = 170, y = 59, w = 2081, h = 3233
+ Box[91]: x = 170, y = 0, w = 1399, h = 3292
+ Box[92]: x = 182, y = 59, w = 2169, h = 3221
+ Box[93]: x = 170, y = 0, w = 1258, h = 3298
+ Box[94]: x = 176, y = 59, w = 2040, h = 3215
+ Box[95]: x = 170, y = 0, w = 1505, h = 3298
+ Box[96]: x = 135, y = 59, w = 2087, h = 3233
+ Box[97]: x = 165, y = 0, w = 2428, h = 3298
+ Box[98]: x = 135, y = 59, w = 2469, h = 3239
+ Box[99]: x = 182, y = 0, w = 1734, h = 3298
+ Box[100]: x = 165, y = 59, w = 2028, h = 3221
+ Box[101]: x = 165, y = 0, w = 2457, h = 3298
+ Box[102]: x = 182, y = 59, w = 2075, h = 3262
+ Box[103]: x = 165, y = 0, w = 1217, h = 3298
+ Box[104]: x = 165, y = 59, w = 2439, h = 3245
+ Box[105]: x = 123, y = 0, w = 1775, h = 3292
+ Box[106]: x = 123, y = 59, w = 2110, h = 3233
+ Box[107]: x = 170, y = 0, w = 1793, h = 3298
+ Box[108]: x = 165, y = 59, w = 1916, h = 3209
+ Box[109]: x = 153, y = 0, w = 1181, h = 3292
+ Box[110]: x = 159, y = 59, w = 2093, h = 3245
+ Box[111]: x = 159, y = 0, w = 1387, h = 3298
+ Box[112]: x = 153, y = 59, w = 2439, h = 3233
+ Box[113]: x = 159, y = 0, w = 1352, h = 3298
+ Box[114]: x = 153, y = 59, w = 2439, h = 3280
+ Box[115]: x = 165, y = 0, w = 1375, h = 3298
+ Box[116]: x = 141, y = 59, w = 2110, h = 3227
+ Box[117]: x = 147, y = 0, w = 1763, h = 3298
+ Box[118]: x = 94, y = 59, w = 2469, h = 3256
+ Box[119]: x = 135, y = 0, w = 1328, h = 3298
+ Box[120]: x = 141, y = 59, w = 2551, h = 3215
+ Box[121]: x = 276, y = 0, w = 2363, h = 3274
+ Box[122]: x = 694, y = 59, w = 1752, h = 3268
+ Box[123]: x = 147, y = 0, w = 1963, h = 3386
+ Box[124]: x = 200, y = 59, w = 2663, h = 3303
+ Box[125]: x = 0, y = 0, w = 0, h = 0
diff --git a/leptonica/prog/boxap2.ba b/leptonica/prog/boxap2.ba
new file mode 100644
index 00000000..0adf6aa4
--- /dev/null
+++ b/leptonica/prog/boxap2.ba
@@ -0,0 +1,303 @@
+
+Boxa Version 2
+Number of boxes = 300
+ Box[0]: x = 0, y = 0, w = 0, h = 0
+ Box[1]: x = 0, y = 0, w = 0, h = 0
+ Box[2]: x = 0, y = 0, w = 0, h = 0
+ Box[3]: x = 0, y = 0, w = 0, h = 0
+ Box[4]: x = 0, y = 0, w = 0, h = 0
+ Box[5]: x = 398, y = 0, w = 2133, h = 3011
+ Box[6]: x = 78, y = 39, w = 1891, h = 3015
+ Box[7]: x = 257, y = 0, w = 2129, h = 3015
+ Box[8]: x = 55, y = 39, w = 2235, h = 3038
+ Box[9]: x = 335, y = 0, w = 2122, h = 3003
+ Box[10]: x = 55, y = 39, w = 2242, h = 3034
+ Box[11]: x = 335, y = 0, w = 2122, h = 2995
+ Box[12]: x = 62, y = 39, w = 2231, h = 3050
+ Box[13]: x = 390, y = 0, w = 2071, h = 2995
+ Box[14]: x = 62, y = 39, w = 2262, h = 3054
+ Box[15]: x = 472, y = 0, w = 1993, h = 2995
+ Box[16]: x = 62, y = 39, w = 2246, h = 3054
+ Box[17]: x = 472, y = 0, w = 1993, h = 2999
+ Box[18]: x = 66, y = 39, w = 2231, h = 3054
+ Box[19]: x = 558, y = 0, w = 1915, h = 2999
+ Box[20]: x = 82, y = 39, w = 2219, h = 3054
+ Box[21]: x = 495, y = 0, w = 1981, h = 2999
+ Box[22]: x = 66, y = 39, w = 2242, h = 3057
+ Box[23]: x = 499, y = 0, w = 1977, h = 2995
+ Box[24]: x = 70, y = 39, w = 2235, h = 3061
+ Box[25]: x = 530, y = 0, w = 1946, h = 2999
+ Box[26]: x = 66, y = 39, w = 2239, h = 3061
+ Box[27]: x = 534, y = 0, w = 1942, h = 2991
+ Box[28]: x = 66, y = 39, w = 2242, h = 3061
+ Box[29]: x = 480, y = 0, w = 1997, h = 2999
+ Box[30]: x = 66, y = 39, w = 2242, h = 3054
+ Box[31]: x = 433, y = 0, w = 2040, h = 2995
+ Box[32]: x = 70, y = 39, w = 2239, h = 3057
+ Box[33]: x = 452, y = 0, w = 2036, h = 2987
+ Box[34]: x = 74, y = 39, w = 2239, h = 3054
+ Box[35]: x = 499, y = 0, w = 1985, h = 2991
+ Box[36]: x = 74, y = 39, w = 2235, h = 3054
+ Box[37]: x = 468, y = 0, w = 2020, h = 2995
+ Box[38]: x = 74, y = 39, w = 2239, h = 3050
+ Box[39]: x = 511, y = 0, w = 1977, h = 2995
+ Box[40]: x = 74, y = 39, w = 2239, h = 3050
+ Box[41]: x = 569, y = 0, w = 1915, h = 2995
+ Box[42]: x = 70, y = 39, w = 2242, h = 3050
+ Box[43]: x = 468, y = 0, w = 2024, h = 2991
+ Box[44]: x = 74, y = 39, w = 2242, h = 3050
+ Box[45]: x = 749, y = 0, w = 1732, h = 2987
+ Box[46]: x = 70, y = 39, w = 2246, h = 3050
+ Box[47]: x = 460, y = 0, w = 2028, h = 2991
+ Box[48]: x = 70, y = 39, w = 2250, h = 3054
+ Box[49]: x = 562, y = 0, w = 1930, h = 2987
+ Box[50]: x = 74, y = 39, w = 2242, h = 3054
+ Box[51]: x = 628, y = 0, w = 1860, h = 2991
+ Box[52]: x = 70, y = 39, w = 2250, h = 3057
+ Box[53]: x = 456, y = 0, w = 2028, h = 2991
+ Box[54]: x = 70, y = 39, w = 2250, h = 3065
+ Box[55]: x = 659, y = 0, w = 1833, h = 2991
+ Box[56]: x = 74, y = 39, w = 2246, h = 3050
+ Box[57]: x = 538, y = 0, w = 1950, h = 2991
+ Box[58]: x = 74, y = 39, w = 2250, h = 3054
+ Box[59]: x = 581, y = 0, w = 1911, h = 2991
+ Box[60]: x = 74, y = 39, w = 2250, h = 3050
+ Box[61]: x = 554, y = 0, w = 1938, h = 2991
+ Box[62]: x = 78, y = 39, w = 2246, h = 3054
+ Box[63]: x = 651, y = 0, w = 1841, h = 2991
+ Box[64]: x = 78, y = 39, w = 2250, h = 3057
+ Box[65]: x = 632, y = 0, w = 1860, h = 2987
+ Box[66]: x = 74, y = 39, w = 2254, h = 3061
+ Box[67]: x = 511, y = 0, w = 1981, h = 2991
+ Box[68]: x = 74, y = 39, w = 2258, h = 3057
+ Box[69]: x = 530, y = 0, w = 1962, h = 2987
+ Box[70]: x = 78, y = 39, w = 2250, h = 3057
+ Box[71]: x = 651, y = 0, w = 1841, h = 2987
+ Box[72]: x = 105, y = 39, w = 2227, h = 3054
+ Box[73]: x = 550, y = 0, w = 1950, h = 2987
+ Box[74]: x = 74, y = 39, w = 2258, h = 3065
+ Box[75]: x = 526, y = 0, w = 1973, h = 2983
+ Box[76]: x = 117, y = 39, w = 2215, h = 3073
+ Box[77]: x = 620, y = 0, w = 1876, h = 2987
+ Box[78]: x = 109, y = 39, w = 2223, h = 3065
+ Box[79]: x = 694, y = 0, w = 1806, h = 2991
+ Box[80]: x = 156, y = 39, w = 2231, h = 3069
+ Box[81]: x = 647, y = 0, w = 1849, h = 2987
+ Box[82]: x = 156, y = 39, w = 2231, h = 3065
+ Box[83]: x = 577, y = 0, w = 1919, h = 2987
+ Box[84]: x = 168, y = 39, w = 2219, h = 3073
+ Box[85]: x = 768, y = 0, w = 1728, h = 2991
+ Box[86]: x = 183, y = 39, w = 2211, h = 3065
+ Box[87]: x = 745, y = 0, w = 1759, h = 2991
+ Box[88]: x = 164, y = 39, w = 2231, h = 3069
+ Box[89]: x = 328, y = 0, w = 2176, h = 2987
+ Box[90]: x = 164, y = 39, w = 2227, h = 3057
+ Box[91]: x = 550, y = 0, w = 1958, h = 2987
+ Box[92]: x = 164, y = 39, w = 2235, h = 3061
+ Box[93]: x = 640, y = 0, w = 1868, h = 2987
+ Box[94]: x = 164, y = 39, w = 2239, h = 3061
+ Box[95]: x = 640, y = 0, w = 1868, h = 2991
+ Box[96]: x = 168, y = 39, w = 2239, h = 3050
+ Box[97]: x = 339, y = 0, w = 2168, h = 2991
+ Box[98]: x = 136, y = 39, w = 2266, h = 3057
+ Box[99]: x = 686, y = 0, w = 1802, h = 2991
+ Box[100]: x = 113, y = 39, w = 2301, h = 3038
+ Box[101]: x = 359, y = 0, w = 2122, h = 2987
+ Box[102]: x = 55, y = 39, w = 2211, h = 3022
+ Box[103]: x = 214, y = 0, w = 2196, h = 3003
+ Box[104]: x = 0, y = 39, w = 2371, h = 3022
+ Box[105]: x = 218, y = 0, w = 2192, h = 3003
+ Box[106]: x = 58, y = 39, w = 2090, h = 3022
+ Box[107]: x = 222, y = 0, w = 2188, h = 3003
+ Box[108]: x = 0, y = 39, w = 1973, h = 3022
+ Box[109]: x = 218, y = 0, w = 2188, h = 3011
+ Box[110]: x = 74, y = 39, w = 2200, h = 3022
+ Box[111]: x = 296, y = 0, w = 2110, h = 3007
+ Box[112]: x = 62, y = 39, w = 2301, h = 3026
+ Box[113]: x = 222, y = 0, w = 2188, h = 3007
+ Box[114]: x = 58, y = 39, w = 2207, h = 3026
+ Box[115]: x = 413, y = 0, w = 1997, h = 3007
+ Box[116]: x = 70, y = 39, w = 2012, h = 3030
+ Box[117]: x = 179, y = 0, w = 2235, h = 3003
+ Box[118]: x = 66, y = 39, w = 2161, h = 3030
+ Box[119]: x = 370, y = 0, w = 2036, h = 3003
+ Box[120]: x = 43, y = 39, w = 1989, h = 3030
+ Box[121]: x = 331, y = 0, w = 2079, h = 2999
+ Box[122]: x = 43, y = 39, w = 2231, h = 3026
+ Box[123]: x = 316, y = 0, w = 2090, h = 3003
+ Box[124]: x = 43, y = 39, w = 2227, h = 3030
+ Box[125]: x = 562, y = 0, w = 1849, h = 2999
+ Box[126]: x = 47, y = 39, w = 2200, h = 3030
+ Box[127]: x = 507, y = 0, w = 1895, h = 3003
+ Box[128]: x = 43, y = 39, w = 2149, h = 3030
+ Box[129]: x = 370, y = 0, w = 2036, h = 3007
+ Box[130]: x = 43, y = 39, w = 2293, h = 3034
+ Box[131]: x = 183, y = 0, w = 2223, h = 3003
+ Box[132]: x = 43, y = 39, w = 2348, h = 3030
+ Box[133]: x = 312, y = 0, w = 2098, h = 3003
+ Box[134]: x = 43, y = 39, w = 2231, h = 3038
+ Box[135]: x = 339, y = 0, w = 2063, h = 3003
+ Box[136]: x = 43, y = 39, w = 1802, h = 3030
+ Box[137]: x = 499, y = 0, w = 1907, h = 3003
+ Box[138]: x = 43, y = 39, w = 2262, h = 3038
+ Box[139]: x = 402, y = 0, w = 2001, h = 3003
+ Box[140]: x = 43, y = 39, w = 2153, h = 3030
+ Box[141]: x = 616, y = 0, w = 1794, h = 2999
+ Box[142]: x = 43, y = 39, w = 2227, h = 3030
+ Box[143]: x = 456, y = 0, w = 1950, h = 2999
+ Box[144]: x = 43, y = 39, w = 2005, h = 3022
+ Box[145]: x = 573, y = 0, w = 1837, h = 2999
+ Box[146]: x = 43, y = 39, w = 2301, h = 3015
+ Box[147]: x = 367, y = 0, w = 2040, h = 3007
+ Box[148]: x = 43, y = 39, w = 2305, h = 3034
+ Box[149]: x = 343, y = 0, w = 2063, h = 3007
+ Box[150]: x = 43, y = 39, w = 2110, h = 3026
+ Box[151]: x = 409, y = 0, w = 1989, h = 3007
+ Box[152]: x = 43, y = 39, w = 2274, h = 3034
+ Box[153]: x = 550, y = 0, w = 1852, h = 3007
+ Box[154]: x = 43, y = 39, w = 2305, h = 3038
+ Box[155]: x = 367, y = 0, w = 2036, h = 3007
+ Box[156]: x = 43, y = 39, w = 2196, h = 3030
+ Box[157]: x = 363, y = 0, w = 2040, h = 3003
+ Box[158]: x = 43, y = 39, w = 2223, h = 3030
+ Box[159]: x = 246, y = 0, w = 2149, h = 3003
+ Box[160]: x = 43, y = 39, w = 2219, h = 3026
+ Box[161]: x = 242, y = 0, w = 2176, h = 3003
+ Box[162]: x = 47, y = 39, w = 2305, h = 3030
+ Box[163]: x = 647, y = 0, w = 1767, h = 3030
+ Box[164]: x = 43, y = 39, w = 2309, h = 3015
+ Box[165]: x = 421, y = 0, w = 1993, h = 2999
+ Box[166]: x = 43, y = 39, w = 2352, h = 3022
+ Box[167]: x = 472, y = 0, w = 1938, h = 2999
+ Box[168]: x = 43, y = 39, w = 2313, h = 3015
+ Box[169]: x = 367, y = 0, w = 2047, h = 2999
+ Box[170]: x = 43, y = 39, w = 2313, h = 3015
+ Box[171]: x = 261, y = 0, w = 2149, h = 3030
+ Box[172]: x = 47, y = 39, w = 2313, h = 3042
+ Box[173]: x = 390, y = 0, w = 2020, h = 3026
+ Box[174]: x = 43, y = 39, w = 2317, h = 3038
+ Box[175]: x = 312, y = 0, w = 2086, h = 3003
+ Box[176]: x = 47, y = 39, w = 2254, h = 3030
+ Box[177]: x = 491, y = 0, w = 1915, h = 2999
+ Box[178]: x = 43, y = 39, w = 2313, h = 3034
+ Box[179]: x = 417, y = 0, w = 1985, h = 2999
+ Box[180]: x = 43, y = 39, w = 2317, h = 3030
+ Box[181]: x = 355, y = 0, w = 2051, h = 2999
+ Box[182]: x = 43, y = 39, w = 2317, h = 3034
+ Box[183]: x = 370, y = 0, w = 2044, h = 2999
+ Box[184]: x = 43, y = 39, w = 2317, h = 3015
+ Box[185]: x = 363, y = 0, w = 2036, h = 3007
+ Box[186]: x = 43, y = 39, w = 2320, h = 3022
+ Box[187]: x = 421, y = 0, w = 1981, h = 3007
+ Box[188]: x = 43, y = 39, w = 2317, h = 3011
+ Box[189]: x = 374, y = 0, w = 2020, h = 3007
+ Box[190]: x = 43, y = 39, w = 2317, h = 3026
+ Box[191]: x = 601, y = 0, w = 1802, h = 3003
+ Box[192]: x = 47, y = 39, w = 2309, h = 3022
+ Box[193]: x = 265, y = 0, w = 2129, h = 2999
+ Box[194]: x = 43, y = 39, w = 2320, h = 3038
+ Box[195]: x = 285, y = 0, w = 2118, h = 3007
+ Box[196]: x = 43, y = 39, w = 2320, h = 3015
+ Box[197]: x = 281, y = 0, w = 2122, h = 2999
+ Box[198]: x = 47, y = 39, w = 2305, h = 3038
+ Box[199]: x = 292, y = 0, w = 2098, h = 3007
+ Box[200]: x = 47, y = 39, w = 2317, h = 3015
+ Box[201]: x = 273, y = 0, w = 2114, h = 3034
+ Box[202]: x = 47, y = 39, w = 2313, h = 3030
+ Box[203]: x = 277, y = 0, w = 2242, h = 2995
+ Box[204]: x = 195, y = 39, w = 2086, h = 3026
+ Box[205]: x = 261, y = 23, w = 1544, h = 2991
+ Box[206]: x = 772, y = 292, w = 1634, h = 2714
+ Box[207]: x = 265, y = 0, w = 1669, h = 3003
+ Box[208]: x = 772, y = 289, w = 1638, h = 2718
+ Box[209]: x = 269, y = 0, w = 1657, h = 2999
+ Box[210]: x = 788, y = 289, w = 1622, h = 2730
+ Box[211]: x = 273, y = 0, w = 1661, h = 2995
+ Box[212]: x = 784, y = 289, w = 1626, h = 2710
+ Box[213]: x = 273, y = 0, w = 1661, h = 2991
+ Box[214]: x = 788, y = 289, w = 1626, h = 2726
+ Box[215]: x = 277, y = 0, w = 1661, h = 2995
+ Box[216]: x = 784, y = 292, w = 1634, h = 2714
+ Box[217]: x = 273, y = 0, w = 1650, h = 3018
+ Box[218]: x = 792, y = 292, w = 1630, h = 2706
+ Box[219]: x = 273, y = 0, w = 1661, h = 3003
+ Box[220]: x = 788, y = 292, w = 1634, h = 2718
+ Box[221]: x = 328, y = 0, w = 1611, h = 3018
+ Box[222]: x = 792, y = 292, w = 1630, h = 2710
+ Box[223]: x = 577, y = 0, w = 1365, h = 3018
+ Box[224]: x = 796, y = 292, w = 1630, h = 2706
+ Box[225]: x = 277, y = 0, w = 1657, h = 3003
+ Box[226]: x = 796, y = 292, w = 1634, h = 2703
+ Box[227]: x = 277, y = 0, w = 1657, h = 3018
+ Box[228]: x = 799, y = 292, w = 1634, h = 2706
+ Box[229]: x = 281, y = 0, w = 1654, h = 3018
+ Box[230]: x = 799, y = 292, w = 1638, h = 2706
+ Box[231]: x = 281, y = 0, w = 1650, h = 3015
+ Box[232]: x = 803, y = 292, w = 1634, h = 2703
+ Box[233]: x = 281, y = 0, w = 1650, h = 3015
+ Box[234]: x = 799, y = 292, w = 1642, h = 2706
+ Box[235]: x = 331, y = 0, w = 1607, h = 3018
+ Box[236]: x = 799, y = 300, w = 1642, h = 2703
+ Box[237]: x = 285, y = 0, w = 1650, h = 3018
+ Box[238]: x = 792, y = 257, w = 1650, h = 2722
+ Box[239]: x = 300, y = 0, w = 1646, h = 3034
+ Box[240]: x = 803, y = 257, w = 1642, h = 2714
+ Box[241]: x = 285, y = 0, w = 1654, h = 3034
+ Box[242]: x = 803, y = 257, w = 1642, h = 2722
+ Box[243]: x = 347, y = 0, w = 1595, h = 3034
+ Box[244]: x = 807, y = 261, w = 1642, h = 2718
+ Box[245]: x = 347, y = 0, w = 1587, h = 3038
+ Box[246]: x = 811, y = 257, w = 1638, h = 2722
+ Box[247]: x = 292, y = 0, w = 1654, h = 3026
+ Box[248]: x = 807, y = 257, w = 1646, h = 2718
+ Box[249]: x = 308, y = 0, w = 1630, h = 3034
+ Box[250]: x = 811, y = 261, w = 1646, h = 2718
+ Box[251]: x = 292, y = 0, w = 1646, h = 3038
+ Box[252]: x = 811, y = 257, w = 1642, h = 2714
+ Box[253]: x = 312, y = 0, w = 1630, h = 3034
+ Box[254]: x = 819, y = 257, w = 1642, h = 2718
+ Box[255]: x = 312, y = 0, w = 1622, h = 3038
+ Box[256]: x = 819, y = 257, w = 1642, h = 2718
+ Box[257]: x = 296, y = 0, w = 1646, h = 3038
+ Box[258]: x = 815, y = 257, w = 1646, h = 2718
+ Box[259]: x = 316, y = 0, w = 1626, h = 3038
+ Box[260]: x = 823, y = 257, w = 1646, h = 2718
+ Box[261]: x = 316, y = 0, w = 1630, h = 3038
+ Box[262]: x = 815, y = 257, w = 1650, h = 2722
+ Box[263]: x = 296, y = 0, w = 1642, h = 3042
+ Box[264]: x = 819, y = 257, w = 1654, h = 2718
+ Box[265]: x = 300, y = 0, w = 1634, h = 3042
+ Box[266]: x = 819, y = 257, w = 1654, h = 2718
+ Box[267]: x = 316, y = 0, w = 1630, h = 3038
+ Box[268]: x = 819, y = 257, w = 1657, h = 2714
+ Box[269]: x = 370, y = 0, w = 1576, h = 3038
+ Box[270]: x = 819, y = 261, w = 1657, h = 2714
+ Box[271]: x = 316, y = 0, w = 1630, h = 3038
+ Box[272]: x = 823, y = 261, w = 1654, h = 2714
+ Box[273]: x = 351, y = 0, w = 1591, h = 3042
+ Box[274]: x = 827, y = 261, w = 1654, h = 2718
+ Box[275]: x = 398, y = 0, w = 1548, h = 3038
+ Box[276]: x = 827, y = 261, w = 1654, h = 2714
+ Box[277]: x = 367, y = 0, w = 1572, h = 3042
+ Box[278]: x = 827, y = 261, w = 1661, h = 2714
+ Box[279]: x = 347, y = 0, w = 1603, h = 3038
+ Box[280]: x = 827, y = 261, w = 1661, h = 2710
+ Box[281]: x = 355, y = 0, w = 1591, h = 3042
+ Box[282]: x = 823, y = 261, w = 1661, h = 2718
+ Box[283]: x = 304, y = 0, w = 1638, h = 3042
+ Box[284]: x = 815, y = 261, w = 1669, h = 2718
+ Box[285]: x = 324, y = 0, w = 1622, h = 3042
+ Box[286]: x = 815, y = 257, w = 1673, h = 2718
+ Box[287]: x = 324, y = 0, w = 1626, h = 3042
+ Box[288]: x = 807, y = 257, w = 1677, h = 2718
+ Box[289]: x = 347, y = 0, w = 1603, h = 3042
+ Box[290]: x = 811, y = 257, w = 1681, h = 2718
+ Box[291]: x = 304, y = 0, w = 1650, h = 3042
+ Box[292]: x = 811, y = 261, w = 1681, h = 2718
+ Box[293]: x = 308, y = 0, w = 1650, h = 3038
+ Box[294]: x = 776, y = 257, w = 1716, h = 2703
+ Box[295]: x = 304, y = 0, w = 2258, h = 3034
+ Box[296]: x = 285, y = 39, w = 2168, h = 2952
+ Box[297]: x = 530, y = 0, w = 1147, h = 3061
+ Box[298]: x = 94, y = 39, w = 2036, h = 2976
+ Box[299]: x = 0, y = 0, w = 0, h = 0
diff --git a/leptonica/prog/boxap3.ba b/leptonica/prog/boxap3.ba
new file mode 100644
index 00000000..f5068f8f
--- /dev/null
+++ b/leptonica/prog/boxap3.ba
@@ -0,0 +1,15 @@
+
+Boxa Version 2
+Number of boxes = 12
+ Box[0]: x = 75, y = 58, w = 2093, h = 2747
+ Box[1]: x = 162, y = 12, w = 1550, h = 2510
+ Box[2]: x = 69, y = 58, w = 2087, h = 2764
+ Box[3]: x = 168, y = 17, w = 1550, h = 2510
+ Box[4]: x = 75, y = 58, w = 2087, h = 2619
+ Box[5]: x = 168, y = 12, w = 1544, h = 2515
+ Box[6]: x = 69, y = 58, w = 2267, h = 2648
+ Box[7]: x = 578, y = 17, w = 1151, h = 2510
+ Box[8]: x = 81, y = 40, w = 1827, h = 2770
+ Box[9]: x = 0, y = 0, w = 0, h = 0
+ Box[10]: x = 0, y = 0, w = 0, h = 0
+ Box[11]: x = 0, y = 0, w = 0, h = 0
diff --git a/leptonica/prog/boxap4.ba b/leptonica/prog/boxap4.ba
new file mode 100644
index 00000000..1df4206d
--- /dev/null
+++ b/leptonica/prog/boxap4.ba
@@ -0,0 +1,53 @@
+
+Boxa Version 2
+Number of boxes = 50
+ Box[0]: x = 0, y = 0, w = 0, h = 0
+ Box[1]: x = 0, y = 0, w = 0, h = 0
+ Box[2]: x = 0, y = 0, w = 0, h = 0
+ Box[3]: x = 0, y = 0, w = 0, h = 0
+ Box[4]: x = 0, y = 0, w = 0, h = 0
+ Box[5]: x = 346, y = 0, w = 2437, h = 3598
+ Box[6]: x = 39, y = 39, w = 2433, h = 3776
+ Box[7]: x = 354, y = 47, w = 2390, h = 3571
+ Box[8]: x = 75, y = 39, w = 2417, h = 3724
+ Box[9]: x = 394, y = 24, w = 2331, h = 3622
+ Box[10]: x = 75, y = 39, w = 2413, h = 3724
+ Box[11]: x = 382, y = 28, w = 2346, h = 3610
+ Box[12]: x = 75, y = 39, w = 2476, h = 3736
+ Box[13]: x = 358, y = 31, w = 2370, h = 3606
+ Box[14]: x = 75, y = 39, w = 2449, h = 3736
+ Box[15]: x = 327, y = 39, w = 2394, h = 3598
+ Box[16]: x = 75, y = 39, w = 2457, h = 3740
+ Box[17]: x = 0, y = 43, w = 2724, h = 3610
+ Box[18]: x = 79, y = 39, w = 2472, h = 3732
+ Box[19]: x = 350, y = 43, w = 2374, h = 3606
+ Box[20]: x = 79, y = 39, w = 2878, h = 3728
+ Box[21]: x = 354, y = 43, w = 2374, h = 3606
+ Box[22]: x = 87, y = 39, w = 2441, h = 3720
+ Box[23]: x = 354, y = 47, w = 2535, h = 3602
+ Box[24]: x = 87, y = 39, w = 2453, h = 3713
+ Box[25]: x = 335, y = 39, w = 2394, h = 3606
+ Box[26]: x = 91, y = 39, w = 2453, h = 3701
+ Box[27]: x = 362, y = 39, w = 2366, h = 3606
+ Box[28]: x = 98, y = 39, w = 2441, h = 3713
+ Box[29]: x = 378, y = 39, w = 2335, h = 3602
+ Box[30]: x = 102, y = 39, w = 2429, h = 3724
+ Box[31]: x = 350, y = 39, w = 2374, h = 3602
+ Box[32]: x = 98, y = 39, w = 2445, h = 3717
+ Box[33]: x = 358, y = 43, w = 2366, h = 3606
+ Box[34]: x = 110, y = 39, w = 2445, h = 3720
+ Box[35]: x = 343, y = 39, w = 2386, h = 3606
+ Box[36]: x = 130, y = 39, w = 2421, h = 3717
+ Box[37]: x = 366, y = 43, w = 2362, h = 3606
+ Box[38]: x = 122, y = 39, w = 2429, h = 3713
+ Box[39]: x = 370, y = 47, w = 2366, h = 3606
+ Box[40]: x = 138, y = 39, w = 2417, h = 3713
+ Box[41]: x = 256, y = 47, w = 2476, h = 3606
+ Box[42]: x = 138, y = 39, w = 2417, h = 3720
+ Box[43]: x = 287, y = 43, w = 2449, h = 3610
+ Box[44]: x = 134, y = 39, w = 2421, h = 3717
+ Box[45]: x = 366, y = 43, w = 2350, h = 3587
+ Box[46]: x = 142, y = 39, w = 2421, h = 3709
+ Box[47]: x = 374, y = 43, w = 2350, h = 3594
+ Box[48]: x = 232, y = 39, w = 2327, h = 3709
+ Box[49]: x = 283, y = 47, w = 2441, h = 3594
diff --git a/leptonica/prog/boxap5.ba b/leptonica/prog/boxap5.ba
new file mode 100644
index 00000000..fd600b88
--- /dev/null
+++ b/leptonica/prog/boxap5.ba
@@ -0,0 +1,553 @@
+
+Boxa Version 2
+Number of boxes = 550
+ Box[0]: x = 0, y = 0, w = 0, h = 0
+ Box[1]: x = 0, y = 0, w = 0, h = 0
+ Box[2]: x = 0, y = 0, w = 0, h = 0
+ Box[3]: x = 0, y = 0, w = 0, h = 0
+ Box[4]: x = 0, y = 0, w = 0, h = 0
+ Box[5]: x = 0, y = 0, w = 0, h = 0
+ Box[6]: x = 0, y = 0, w = 0, h = 0
+ Box[7]: x = 0, y = 0, w = 2521, h = 3394
+ Box[8]: x = 1141, y = 0, w = 1444, h = 3627
+ Box[9]: x = 623, y = 111, w = 1846, h = 3284
+ Box[10]: x = 0, y = 99, w = 1985, h = 3458
+ Box[11]: x = 442, y = 128, w = 2026, h = 3266
+ Box[12]: x = 0, y = 99, w = 1950, h = 3458
+ Box[13]: x = 442, y = 128, w = 2020, h = 3266
+ Box[14]: x = 0, y = 128, w = 2585, h = 3429
+ Box[15]: x = 885, y = 122, w = 1578, h = 3272
+ Box[16]: x = 914, y = 122, w = 1386, h = 3429
+ Box[17]: x = 146, y = 116, w = 2317, h = 3278
+ Box[18]: x = 902, y = 128, w = 1397, h = 3429
+ Box[19]: x = 116, y = 116, w = 2341, h = 3278
+ Box[20]: x = 0, y = 128, w = 2585, h = 3429
+ Box[21]: x = 868, y = 111, w = 1589, h = 3284
+ Box[22]: x = 932, y = 134, w = 1485, h = 3423
+ Box[23]: x = 99, y = 111, w = 2358, h = 3284
+ Box[24]: x = 926, y = 111, w = 1409, h = 1607
+ Box[25]: x = 105, y = 105, w = 2341, h = 3290
+ Box[26]: x = 769, y = 140, w = 1700, h = 3412
+ Box[27]: x = 111, y = 99, w = 2323, h = 3295
+ Box[28]: x = 111, y = 58, w = 2341, h = 3435
+ Box[29]: x = 169, y = 111, w = 2335, h = 3284
+ Box[30]: x = 128, y = 58, w = 2341, h = 3429
+ Box[31]: x = 227, y = 111, w = 2271, h = 3290
+ Box[32]: x = 1071, y = 87, w = 1386, h = 3406
+ Box[33]: x = 215, y = 111, w = 2288, h = 3290
+ Box[34]: x = 93, y = 140, w = 2538, h = 3412
+ Box[35]: x = 920, y = 140, w = 1566, h = 3260
+ Box[36]: x = 186, y = 140, w = 2282, h = 3412
+ Box[37]: x = 215, y = 128, w = 2282, h = 3272
+ Box[38]: x = 192, y = 146, w = 2276, h = 3406
+ Box[39]: x = 192, y = 105, w = 2306, h = 3295
+ Box[40]: x = 47, y = 151, w = 2579, h = 3400
+ Box[41]: x = 897, y = 122, w = 1589, h = 3278
+ Box[42]: x = 47, y = 151, w = 2573, h = 3400
+ Box[43]: x = 215, y = 128, w = 2276, h = 3272
+ Box[44]: x = 175, y = 58, w = 2410, h = 3423
+ Box[45]: x = 908, y = 0, w = 1624, h = 3389
+ Box[46]: x = 93, y = 58, w = 2341, h = 3458
+ Box[47]: x = 186, y = 12, w = 2323, h = 3406
+ Box[48]: x = 116, y = 58, w = 2341, h = 3470
+ Box[49]: x = 210, y = 6, w = 2323, h = 3412
+ Box[50]: x = 134, y = 58, w = 2352, h = 3464
+ Box[51]: x = 210, y = 0, w = 2323, h = 3423
+ Box[52]: x = 134, y = 58, w = 2352, h = 3464
+ Box[53]: x = 204, y = 0, w = 2317, h = 3412
+ Box[54]: x = 146, y = 58, w = 2335, h = 3464
+ Box[55]: x = 221, y = 0, w = 2282, h = 3418
+ Box[56]: x = 122, y = 58, w = 2358, h = 3464
+ Box[57]: x = 210, y = 0, w = 2300, h = 3371
+ Box[58]: x = 146, y = 58, w = 2335, h = 3458
+ Box[59]: x = 210, y = 0, w = 2294, h = 3371
+ Box[60]: x = 134, y = 58, w = 2341, h = 3464
+ Box[61]: x = 210, y = 0, w = 2306, h = 3389
+ Box[62]: x = 140, y = 58, w = 2341, h = 3464
+ Box[63]: x = 215, y = 0, w = 2300, h = 3383
+ Box[64]: x = 128, y = 58, w = 2300, h = 3464
+ Box[65]: x = 227, y = 0, w = 2288, h = 3400
+ Box[66]: x = 64, y = 58, w = 2405, h = 3464
+ Box[67]: x = 215, y = 12, w = 2323, h = 3412
+ Box[68]: x = 35, y = 58, w = 2434, h = 3458
+ Box[69]: x = 204, y = 6, w = 2346, h = 3412
+ Box[70]: x = 76, y = 58, w = 2393, h = 3458
+ Box[71]: x = 204, y = 0, w = 2329, h = 3412
+ Box[72]: x = 41, y = 58, w = 2416, h = 3453
+ Box[73]: x = 215, y = 12, w = 2346, h = 3406
+ Box[74]: x = 41, y = 58, w = 2422, h = 3453
+ Box[75]: x = 221, y = 0, w = 2358, h = 3418
+ Box[76]: x = 151, y = 0, w = 2405, h = 3546
+ Box[77]: x = 215, y = 0, w = 2306, h = 3453
+ Box[78]: x = 0, y = 0, w = 2480, h = 3546
+ Box[79]: x = 0, y = 0, w = 2521, h = 3406
+ Box[80]: x = 1153, y = 58, w = 1432, h = 3488
+ Box[81]: x = 146, y = 279, w = 1718, h = 3144
+ Box[82]: x = 565, y = 58, w = 1817, h = 3488
+ Box[83]: x = 157, y = 0, w = 1752, h = 3324
+ Box[84]: x = 635, y = 58, w = 1799, h = 3488
+ Box[85]: x = 198, y = 12, w = 2370, h = 3418
+ Box[86]: x = 0, y = 58, w = 2428, h = 3400
+ Box[87]: x = 163, y = 6, w = 2410, h = 3429
+ Box[88]: x = 0, y = 58, w = 2422, h = 3412
+ Box[89]: x = 169, y = 6, w = 2405, h = 3429
+ Box[90]: x = 0, y = 58, w = 2410, h = 3406
+ Box[91]: x = 169, y = 0, w = 2405, h = 3365
+ Box[92]: x = 0, y = 58, w = 2410, h = 3383
+ Box[93]: x = 169, y = 12, w = 2405, h = 3423
+ Box[94]: x = 0, y = 58, w = 2410, h = 3389
+ Box[95]: x = 175, y = 0, w = 1589, h = 3441
+ Box[96]: x = 0, y = 58, w = 2405, h = 3383
+ Box[97]: x = 175, y = 12, w = 2393, h = 3423
+ Box[98]: x = 41, y = 58, w = 2364, h = 3389
+ Box[99]: x = 180, y = 0, w = 2387, h = 3354
+ Box[100]: x = 0, y = 58, w = 2405, h = 3423
+ Box[101]: x = 180, y = 0, w = 2387, h = 3354
+ Box[102]: x = 47, y = 58, w = 2358, h = 3423
+ Box[103]: x = 186, y = 0, w = 2381, h = 3348
+ Box[104]: x = 163, y = 58, w = 2311, h = 3313
+ Box[105]: x = 221, y = 0, w = 2346, h = 3348
+ Box[106]: x = 35, y = 58, w = 2387, h = 3400
+ Box[107]: x = 180, y = 0, w = 2352, h = 3330
+ Box[108]: x = 70, y = 58, w = 2341, h = 3359
+ Box[109]: x = 204, y = 0, w = 2364, h = 3324
+ Box[110]: x = 93, y = 58, w = 2381, h = 3418
+ Box[111]: x = 221, y = 0, w = 1531, h = 3371
+ Box[112]: x = 99, y = 58, w = 2381, h = 3336
+ Box[113]: x = 192, y = 0, w = 2341, h = 3324
+ Box[114]: x = 204, y = 58, w = 2224, h = 3418
+ Box[115]: x = 180, y = 0, w = 2195, h = 3330
+ Box[116]: x = 41, y = 58, w = 2381, h = 3342
+ Box[117]: x = 169, y = 0, w = 1747, h = 3319
+ Box[118]: x = 716, y = 58, w = 1752, h = 3482
+ Box[119]: x = 239, y = 0, w = 2317, h = 3330
+ Box[120]: x = 169, y = 58, w = 2317, h = 3348
+ Box[121]: x = 239, y = 0, w = 2323, h = 3342
+ Box[122]: x = 175, y = 58, w = 2306, h = 3319
+ Box[123]: x = 215, y = 0, w = 2335, h = 3348
+ Box[124]: x = 186, y = 58, w = 2300, h = 3307
+ Box[125]: x = 221, y = 0, w = 2335, h = 3336
+ Box[126]: x = 163, y = 58, w = 2323, h = 3324
+ Box[127]: x = 204, y = 0, w = 2352, h = 3336
+ Box[128]: x = 47, y = 58, w = 2364, h = 3307
+ Box[129]: x = 175, y = 0, w = 2341, h = 3307
+ Box[130]: x = 326, y = 58, w = 2148, h = 3354
+ Box[131]: x = 198, y = 0, w = 2201, h = 3348
+ Box[132]: x = 87, y = 58, w = 2393, h = 3342
+ Box[133]: x = 204, y = 0, w = 1718, h = 3330
+ Box[134]: x = 734, y = 58, w = 1735, h = 3476
+ Box[135]: x = 233, y = 0, w = 2294, h = 3354
+ Box[136]: x = 58, y = 58, w = 2364, h = 3313
+ Box[137]: x = 180, y = 0, w = 2381, h = 3342
+ Box[138]: x = 52, y = 58, w = 2364, h = 3330
+ Box[139]: x = 198, y = 0, w = 2358, h = 3336
+ Box[140]: x = 64, y = 58, w = 2346, h = 3319
+ Box[141]: x = 186, y = 0, w = 2370, h = 3324
+ Box[142]: x = 58, y = 58, w = 2370, h = 3336
+ Box[143]: x = 180, y = 0, w = 2381, h = 3336
+ Box[144]: x = 58, y = 58, w = 2364, h = 3319
+ Box[145]: x = 180, y = 0, w = 2375, h = 3348
+ Box[146]: x = 70, y = 58, w = 2346, h = 3336
+ Box[147]: x = 192, y = 0, w = 2364, h = 3324
+ Box[148]: x = 76, y = 58, w = 2341, h = 3313
+ Box[149]: x = 180, y = 0, w = 2370, h = 3307
+ Box[150]: x = 52, y = 58, w = 2375, h = 3330
+ Box[151]: x = 186, y = 0, w = 2370, h = 3336
+ Box[152]: x = 58, y = 58, w = 2364, h = 3301
+ Box[153]: x = 198, y = 0, w = 2352, h = 3330
+ Box[154]: x = 52, y = 58, w = 2381, h = 3313
+ Box[155]: x = 180, y = 0, w = 2375, h = 3330
+ Box[156]: x = 52, y = 58, w = 2387, h = 3319
+ Box[157]: x = 192, y = 0, w = 2364, h = 3336
+ Box[158]: x = 35, y = 58, w = 2410, h = 3330
+ Box[159]: x = 192, y = 0, w = 2375, h = 3319
+ Box[160]: x = 35, y = 58, w = 2410, h = 3336
+ Box[161]: x = 198, y = 0, w = 2358, h = 3324
+ Box[162]: x = 35, y = 58, w = 2364, h = 3324
+ Box[163]: x = 175, y = 0, w = 2341, h = 3423
+ Box[164]: x = 0, y = 0, w = 2474, h = 3534
+ Box[165]: x = 821, y = 116, w = 1001, h = 3324
+ Box[166]: x = 0, y = 58, w = 2591, h = 3476
+ Box[167]: x = 0, y = 0, w = 2515, h = 3423
+ Box[168]: x = 710, y = 58, w = 1642, h = 3429
+ Box[169]: x = 134, y = 0, w = 2405, h = 3394
+ Box[170]: x = 0, y = 58, w = 2375, h = 3330
+ Box[171]: x = 111, y = 0, w = 2428, h = 3371
+ Box[172]: x = 0, y = 58, w = 2364, h = 3330
+ Box[173]: x = 111, y = 0, w = 2428, h = 3330
+ Box[174]: x = 0, y = 58, w = 2038, h = 3336
+ Box[175]: x = 221, y = 0, w = 2317, h = 3365
+ Box[176]: x = 0, y = 58, w = 2370, h = 3330
+ Box[177]: x = 111, y = 0, w = 2422, h = 3336
+ Box[178]: x = 0, y = 58, w = 2375, h = 3330
+ Box[179]: x = 105, y = 0, w = 2405, h = 3359
+ Box[180]: x = 0, y = 58, w = 1950, h = 3324
+ Box[181]: x = 617, y = 0, w = 1916, h = 3324
+ Box[182]: x = 0, y = 58, w = 2393, h = 3348
+ Box[183]: x = 105, y = 0, w = 2399, h = 3354
+ Box[184]: x = 0, y = 58, w = 2364, h = 3336
+ Box[185]: x = 0, y = 0, w = 2498, h = 3354
+ Box[186]: x = 0, y = 58, w = 2370, h = 3470
+ Box[187]: x = 111, y = 0, w = 2381, h = 3348
+ Box[188]: x = 0, y = 58, w = 2009, h = 3307
+ Box[189]: x = 530, y = 0, w = 1962, h = 3429
+ Box[190]: x = 0, y = 58, w = 2370, h = 3348
+ Box[191]: x = 128, y = 0, w = 2358, h = 3371
+ Box[192]: x = 0, y = 58, w = 2358, h = 3470
+ Box[193]: x = 146, y = 0, w = 2346, h = 3330
+ Box[194]: x = 47, y = 58, w = 2323, h = 3324
+ Box[195]: x = 122, y = 0, w = 2370, h = 3324
+ Box[196]: x = 52, y = 58, w = 2335, h = 3342
+ Box[197]: x = 146, y = 0, w = 2329, h = 3336
+ Box[198]: x = 52, y = 58, w = 2352, h = 3313
+ Box[199]: x = 233, y = 0, w = 2247, h = 3365
+ Box[200]: x = 52, y = 58, w = 2358, h = 3470
+ Box[201]: x = 157, y = 0, w = 2370, h = 3324
+ Box[202]: x = 41, y = 58, w = 2381, h = 3307
+ Box[203]: x = 111, y = 0, w = 2410, h = 3319
+ Box[204]: x = 41, y = 58, w = 2387, h = 3301
+ Box[205]: x = 111, y = 0, w = 2416, h = 3365
+ Box[206]: x = 35, y = 58, w = 2375, h = 3319
+ Box[207]: x = 116, y = 0, w = 1246, h = 3365
+ Box[208]: x = 35, y = 58, w = 2387, h = 3319
+ Box[209]: x = 640, y = 0, w = 1834, h = 3359
+ Box[210]: x = 507, y = 58, w = 1846, h = 3307
+ Box[211]: x = 29, y = 0, w = 2399, h = 3336
+ Box[212]: x = 0, y = 58, w = 2370, h = 3290
+ Box[213]: x = 64, y = 0, w = 2416, h = 3342
+ Box[214]: x = 35, y = 58, w = 2405, h = 3295
+ Box[215]: x = 76, y = 0, w = 1205, h = 3371
+ Box[216]: x = 0, y = 58, w = 2346, h = 3464
+ Box[217]: x = 70, y = 0, w = 2375, h = 3342
+ Box[218]: x = 0, y = 58, w = 2352, h = 3464
+ Box[219]: x = 76, y = 0, w = 2405, h = 3348
+ Box[220]: x = 0, y = 58, w = 2341, h = 3464
+ Box[221]: x = 76, y = 0, w = 2393, h = 3354
+ Box[222]: x = 536, y = 58, w = 1805, h = 3319
+ Box[223]: x = 76, y = 0, w = 1490, h = 3365
+ Box[224]: x = 0, y = 58, w = 2352, h = 3464
+ Box[225]: x = 70, y = 0, w = 2405, h = 3359
+ Box[226]: x = 0, y = 58, w = 2451, h = 3295
+ Box[227]: x = 128, y = 0, w = 1263, h = 3365
+ Box[228]: x = 35, y = 58, w = 2428, h = 3307
+ Box[229]: x = 128, y = 0, w = 2405, h = 3324
+ Box[230]: x = 0, y = 58, w = 2457, h = 3295
+ Box[231]: x = 134, y = 0, w = 2399, h = 3348
+ Box[232]: x = 35, y = 58, w = 2410, h = 3290
+ Box[233]: x = 146, y = 0, w = 2387, h = 3324
+ Box[234]: x = 0, y = 58, w = 2428, h = 3464
+ Box[235]: x = 116, y = 0, w = 2422, h = 3342
+ Box[236]: x = 0, y = 0, w = 2364, h = 3522
+ Box[237]: x = 0, y = 41, w = 1263, h = 3394
+ Box[238]: x = 0, y = 82, w = 2428, h = 3441
+ Box[239]: x = 99, y = 151, w = 2276, h = 3284
+ Box[240]: x = 0, y = 58, w = 2393, h = 3377
+ Box[241]: x = 128, y = 0, w = 2300, h = 3389
+ Box[242]: x = 146, y = 41, w = 2440, h = 2084
+ Box[243]: x = 0, y = 0, w = 2573, h = 3383
+ Box[244]: x = 0, y = 0, w = 1968, h = 3517
+ Box[245]: x = 0, y = 0, w = 1124, h = 3458
+ Box[246]: x = 1392, y = 41, w = 1141, h = 3476
+ Box[247]: x = 116, y = 0, w = 2236, h = 3389
+ Box[248]: x = 0, y = 58, w = 2346, h = 3389
+ Box[249]: x = 93, y = 0, w = 2253, h = 3389
+ Box[250]: x = 0, y = 58, w = 2375, h = 3295
+ Box[251]: x = 221, y = 0, w = 2131, h = 3394
+ Box[252]: x = 0, y = 0, w = 2591, h = 3517
+ Box[253]: x = 76, y = 0, w = 1048, h = 3458
+ Box[254]: x = 1467, y = 41, w = 926, h = 2940
+ Box[255]: x = 140, y = 17, w = 2183, h = 3412
+ Box[256]: x = 0, y = 58, w = 2585, h = 3458
+ Box[257]: x = 0, y = 0, w = 1118, h = 3458
+ Box[258]: x = 1409, y = 41, w = 978, h = 3476
+ Box[259]: x = 105, y = 0, w = 2381, h = 3354
+ Box[260]: x = 396, y = 76, w = 2259, h = 3441
+ Box[261]: x = 0, y = 0, w = 1130, h = 3458
+ Box[262]: x = 0, y = 58, w = 2393, h = 3470
+ Box[263]: x = 122, y = 0, w = 2381, h = 3383
+ Box[264]: x = 0, y = 0, w = 1857, h = 3517
+ Box[265]: x = 0, y = 0, w = 0, h = 0
+ Box[266]: x = 0, y = 58, w = 2393, h = 3482
+ Box[267]: x = 134, y = 82, w = 2183, h = 3330
+ Box[268]: x = 76, y = 146, w = 2311, h = 3371
+ Box[269]: x = 105, y = 111, w = 2195, h = 3330
+ Box[270]: x = 390, y = 58, w = 2265, h = 3458
+ Box[271]: x = 0, y = 0, w = 2480, h = 3447
+ Box[272]: x = 1048, y = 76, w = 1549, h = 3441
+ Box[273]: x = 0, y = 12, w = 2527, h = 3377
+ Box[274]: x = 221, y = 58, w = 2131, h = 3458
+ Box[275]: x = 116, y = 105, w = 1188, h = 3342
+ Box[276]: x = 76, y = 140, w = 2358, h = 3377
+ Box[277]: x = 163, y = 146, w = 2166, h = 3301
+ Box[278]: x = 64, y = 128, w = 2335, h = 3389
+ Box[279]: x = 116, y = 111, w = 2195, h = 3336
+ Box[280]: x = 64, y = 128, w = 2323, h = 3389
+ Box[281]: x = 146, y = 111, w = 2160, h = 3336
+ Box[282]: x = 41, y = 134, w = 2352, h = 3383
+ Box[283]: x = 140, y = 116, w = 2189, h = 3330
+ Box[284]: x = 70, y = 122, w = 2329, h = 3394
+ Box[285]: x = 140, y = 134, w = 2352, h = 3313
+ Box[286]: x = 58, y = 116, w = 2329, h = 3394
+ Box[287]: x = 215, y = 116, w = 2102, h = 3330
+ Box[288]: x = 210, y = 116, w = 1601, h = 3394
+ Box[289]: x = 111, y = 134, w = 2224, h = 3313
+ Box[290]: x = 70, y = 116, w = 2346, h = 3394
+ Box[291]: x = 111, y = 116, w = 2207, h = 3330
+ Box[292]: x = 70, y = 111, w = 2329, h = 3400
+ Box[293]: x = 116, y = 29, w = 2207, h = 3423
+ Box[294]: x = 70, y = 111, w = 2311, h = 3400
+ Box[295]: x = 122, y = 93, w = 1688, h = 3354
+ Box[296]: x = 82, y = 111, w = 2294, h = 3400
+ Box[297]: x = 128, y = 87, w = 2341, h = 3359
+ Box[298]: x = 70, y = 111, w = 2335, h = 3400
+ Box[299]: x = 116, y = 93, w = 2352, h = 3354
+ Box[300]: x = 76, y = 105, w = 2323, h = 3406
+ Box[301]: x = 146, y = 134, w = 2183, h = 3319
+ Box[302]: x = 58, y = 105, w = 2346, h = 3406
+ Box[303]: x = 111, y = 87, w = 2236, h = 3435
+ Box[304]: x = 70, y = 99, w = 2242, h = 3412
+ Box[305]: x = 128, y = 122, w = 2201, h = 3330
+ Box[306]: x = 82, y = 105, w = 2323, h = 3406
+ Box[307]: x = 128, y = 17, w = 2346, h = 3365
+ Box[308]: x = 82, y = 105, w = 2335, h = 3406
+ Box[309]: x = 140, y = 0, w = 2346, h = 3464
+ Box[310]: x = 58, y = 99, w = 2364, h = 3412
+ Box[311]: x = 134, y = 140, w = 2393, h = 3313
+ Box[312]: x = 221, y = 122, w = 2207, h = 3389
+ Box[313]: x = 111, y = 0, w = 2393, h = 3453
+ Box[314]: x = 0, y = 58, w = 2591, h = 3453
+ Box[315]: x = 0, y = 0, w = 2504, h = 3453
+ Box[316]: x = 0, y = 58, w = 2201, h = 3453
+ Box[317]: x = 17, y = 0, w = 2515, h = 3453
+ Box[318]: x = 0, y = 128, w = 2434, h = 3383
+ Box[319]: x = 0, y = 134, w = 2527, h = 3319
+ Box[320]: x = 52, y = 151, w = 2352, h = 3354
+ Box[321]: x = 111, y = 146, w = 2311, h = 3307
+ Box[322]: x = 344, y = 146, w = 2026, h = 3359
+ Box[323]: x = 70, y = 134, w = 2358, h = 3319
+ Box[324]: x = 99, y = 151, w = 2323, h = 3354
+ Box[325]: x = 76, y = 134, w = 2358, h = 3319
+ Box[326]: x = 128, y = 151, w = 2306, h = 3354
+ Box[327]: x = 291, y = 116, w = 2038, h = 3336
+ Box[328]: x = 93, y = 146, w = 2323, h = 3359
+ Box[329]: x = 99, y = 151, w = 2323, h = 3301
+ Box[330]: x = 1287, y = 169, w = 1287, h = 3336
+ Box[331]: x = 291, y = 151, w = 2049, h = 3307
+ Box[332]: x = 157, y = 105, w = 1624, h = 3400
+ Box[333]: x = 0, y = 134, w = 2428, h = 3324
+ Box[334]: x = 239, y = 122, w = 2375, h = 3383
+ Box[335]: x = 0, y = 140, w = 2451, h = 3319
+ Box[336]: x = 1293, y = 82, w = 1112, h = 3423
+ Box[337]: x = 169, y = 116, w = 2288, h = 3342
+ Box[338]: x = 151, y = 140, w = 2463, h = 3365
+ Box[339]: x = 0, y = 116, w = 1589, h = 3342
+ Box[340]: x = 128, y = 0, w = 2486, h = 3505
+ Box[341]: x = 600, y = 0, w = 1846, h = 3458
+ Box[342]: x = 151, y = 0, w = 2224, h = 3505
+ Box[343]: x = 99, y = 0, w = 2399, h = 3458
+ Box[344]: x = 99, y = 70, w = 2329, h = 3435
+ Box[345]: x = 204, y = 122, w = 2020, h = 3336
+ Box[346]: x = 349, y = 134, w = 2003, h = 3371
+ Box[347]: x = 198, y = 122, w = 2014, h = 3336
+ Box[348]: x = 367, y = 134, w = 1980, h = 3371
+ Box[349]: x = 175, y = 116, w = 2009, h = 3342
+ Box[350]: x = 361, y = 134, w = 1991, h = 3371
+ Box[351]: x = 175, y = 116, w = 2038, h = 3342
+ Box[352]: x = 122, y = 134, w = 2311, h = 3371
+ Box[353]: x = 175, y = 111, w = 2224, h = 3348
+ Box[354]: x = 105, y = 122, w = 2230, h = 3383
+ Box[355]: x = 175, y = 93, w = 2212, h = 3365
+ Box[356]: x = 332, y = 116, w = 1607, h = 3389
+ Box[357]: x = 169, y = 116, w = 2014, h = 3342
+ Box[358]: x = 128, y = 128, w = 2358, h = 3377
+ Box[359]: x = 163, y = 111, w = 2218, h = 3348
+ Box[360]: x = 122, y = 128, w = 2218, h = 3377
+ Box[361]: x = 180, y = 116, w = 1368, h = 3348
+ Box[362]: x = 355, y = 122, w = 1572, h = 3377
+ Box[363]: x = 227, y = 111, w = 2009, h = 3354
+ Box[364]: x = 344, y = 122, w = 1991, h = 3377
+ Box[365]: x = 233, y = 105, w = 2038, h = 3359
+ Box[366]: x = 326, y = 122, w = 2003, h = 3377
+ Box[367]: x = 192, y = 111, w = 2038, h = 3354
+ Box[368]: x = 99, y = 116, w = 2288, h = 3383
+ Box[369]: x = 180, y = 99, w = 2224, h = 3365
+ Box[370]: x = 309, y = 122, w = 2148, h = 3377
+ Box[371]: x = 0, y = 111, w = 2457, h = 3354
+ Box[372]: x = 291, y = 122, w = 2038, h = 3377
+ Box[373]: x = 221, y = 116, w = 2049, h = 3348
+ Box[374]: x = 291, y = 122, w = 2044, h = 3377
+ Box[375]: x = 122, y = 140, w = 2125, h = 3324
+ Box[376]: x = 361, y = 128, w = 1956, h = 3371
+ Box[377]: x = 99, y = 0, w = 2399, h = 3464
+ Box[378]: x = 0, y = 175, w = 2597, h = 3324
+ Box[379]: x = 0, y = 58, w = 2498, h = 3406
+ Box[380]: x = 0, y = 111, w = 2224, h = 3389
+ Box[381]: x = 0, y = 0, w = 2492, h = 3464
+ Box[382]: x = 0, y = 58, w = 2597, h = 3441
+ Box[383]: x = 0, y = 0, w = 2492, h = 3464
+ Box[384]: x = 0, y = 58, w = 2288, h = 3383
+ Box[385]: x = 82, y = 0, w = 2451, h = 3464
+ Box[386]: x = 0, y = 58, w = 2276, h = 3383
+ Box[387]: x = 12, y = 0, w = 2457, h = 3464
+ Box[388]: x = 93, y = 58, w = 2504, h = 3441
+ Box[389]: x = 0, y = 0, w = 2533, h = 3464
+ Box[390]: x = 0, y = 58, w = 2306, h = 3383
+ Box[391]: x = 76, y = 0, w = 2416, h = 3470
+ Box[392]: x = 70, y = 58, w = 2207, h = 3406
+ Box[393]: x = 116, y = 23, w = 2399, h = 3435
+ Box[394]: x = 0, y = 58, w = 2346, h = 3383
+ Box[395]: x = 87, y = 29, w = 2410, h = 3435
+ Box[396]: x = 17, y = 0, w = 2335, h = 3406
+ Box[397]: x = 87, y = 23, w = 2405, h = 3441
+ Box[398]: x = 0, y = 58, w = 2375, h = 3435
+ Box[399]: x = 76, y = 23, w = 2434, h = 3441
+ Box[400]: x = 29, y = 58, w = 2335, h = 3394
+ Box[401]: x = 87, y = 17, w = 2399, h = 3441
+ Box[402]: x = 35, y = 58, w = 2341, h = 3394
+ Box[403]: x = 87, y = 29, w = 2381, h = 3441
+ Box[404]: x = 0, y = 0, w = 0, h = 0
+ Box[405]: x = 0, y = 35, w = 2486, h = 3447
+ Box[406]: x = 169, y = 2865, w = 681, h = 378
+ Box[407]: x = 0, y = 0, w = 2492, h = 3488
+ Box[408]: x = 29, y = 58, w = 2346, h = 3406
+ Box[409]: x = 93, y = 23, w = 2375, h = 3418
+ Box[410]: x = 23, y = 116, w = 1980, h = 3383
+ Box[411]: x = 122, y = 23, w = 2341, h = 3453
+ Box[412]: x = 29, y = 58, w = 2317, h = 3406
+ Box[413]: x = 76, y = 0, w = 2364, h = 3441
+ Box[414]: x = 58, y = 99, w = 2300, h = 3400
+ Box[415]: x = 6, y = 0, w = 2445, h = 3470
+ Box[416]: x = 35, y = 0, w = 2341, h = 3493
+ Box[417]: x = 76, y = 29, w = 2352, h = 3464
+ Box[418]: x = 29, y = 58, w = 2352, h = 3412
+ Box[419]: x = 70, y = 0, w = 2381, h = 3394
+ Box[420]: x = 58, y = 58, w = 2329, h = 3383
+ Box[421]: x = 76, y = 23, w = 2381, h = 3464
+ Box[422]: x = 70, y = 58, w = 2317, h = 3389
+ Box[423]: x = 99, y = 17, w = 2370, h = 3464
+ Box[424]: x = 29, y = 58, w = 2341, h = 3389
+ Box[425]: x = 70, y = 17, w = 2393, h = 3464
+ Box[426]: x = 58, y = 58, w = 2317, h = 3412
+ Box[427]: x = 76, y = 23, w = 2370, h = 3441
+ Box[428]: x = 35, y = 58, w = 2335, h = 3394
+ Box[429]: x = 70, y = 0, w = 2375, h = 3418
+ Box[430]: x = 47, y = 58, w = 2335, h = 3394
+ Box[431]: x = 70, y = 17, w = 2422, h = 3453
+ Box[432]: x = 47, y = 58, w = 2329, h = 3406
+ Box[433]: x = 70, y = 41, w = 2381, h = 3441
+ Box[434]: x = 47, y = 58, w = 2341, h = 3406
+ Box[435]: x = 70, y = 29, w = 2393, h = 3447
+ Box[436]: x = 41, y = 58, w = 2358, h = 3400
+ Box[437]: x = 93, y = 23, w = 2410, h = 3453
+ Box[438]: x = 41, y = 58, w = 2358, h = 3389
+ Box[439]: x = 76, y = 23, w = 2399, h = 3453
+ Box[440]: x = 41, y = 58, w = 2387, h = 3389
+ Box[441]: x = 82, y = 0, w = 2399, h = 3435
+ Box[442]: x = 29, y = 58, w = 2399, h = 3394
+ Box[443]: x = 58, y = 29, w = 2451, h = 3435
+ Box[444]: x = 29, y = 58, w = 2393, h = 3342
+ Box[445]: x = 87, y = 29, w = 2422, h = 3435
+ Box[446]: x = 35, y = 58, w = 2393, h = 3400
+ Box[447]: x = 64, y = 17, w = 2451, h = 3447
+ Box[448]: x = 23, y = 58, w = 2422, h = 3389
+ Box[449]: x = 93, y = 23, w = 2445, h = 3464
+ Box[450]: x = 6, y = 58, w = 2422, h = 3330
+ Box[451]: x = 111, y = 0, w = 2375, h = 3482
+ Box[452]: x = 1205, y = 58, w = 1164, h = 3429
+ Box[453]: x = 250, y = 35, w = 2201, h = 3418
+ Box[454]: x = 186, y = 58, w = 2440, h = 3348
+ Box[455]: x = 0, y = 23, w = 2364, h = 3423
+ Box[456]: x = 0, y = 58, w = 2393, h = 3423
+ Box[457]: x = 99, y = 6, w = 2410, h = 3435
+ Box[458]: x = 0, y = 58, w = 2626, h = 3418
+ Box[459]: x = 0, y = 12, w = 2352, h = 3441
+ Box[460]: x = 0, y = 58, w = 2632, h = 3348
+ Box[461]: x = 489, y = 6, w = 2020, h = 3441
+ Box[462]: x = 122, y = 58, w = 2218, h = 3377
+ Box[463]: x = 111, y = 582, w = 2311, h = 2381
+ Box[464]: x = 198, y = 547, w = 2416, h = 2346
+ Box[465]: x = 0, y = 559, w = 2399, h = 2399
+ Box[466]: x = 233, y = 541, w = 1357, h = 2341
+ Box[467]: x = 332, y = 571, w = 1176, h = 2405
+ Box[468]: x = 221, y = 559, w = 2178, h = 2335
+ Box[469]: x = 157, y = 0, w = 2230, h = 3493
+ Box[470]: x = 239, y = 0, w = 2160, h = 3499
+ Box[471]: x = 163, y = 0, w = 2224, h = 3493
+ Box[472]: x = 239, y = 1531, w = 1403, h = 1333
+ Box[473]: x = 0, y = 140, w = 2381, h = 3348
+ Box[474]: x = 291, y = 565, w = 2014, h = 2288
+ Box[475]: x = 0, y = 0, w = 0, h = 0
+ Box[476]: x = 285, y = 623, w = 2102, h = 2253
+ Box[477]: x = 146, y = 87, w = 2323, h = 3458
+ Box[478]: x = 140, y = 58, w = 2276, h = 3348
+ Box[479]: x = 116, y = 29, w = 2381, h = 3482
+ Box[480]: x = 338, y = 58, w = 2073, h = 3377
+ Box[481]: x = 146, y = 23, w = 2306, h = 3458
+ Box[482]: x = 128, y = 58, w = 2201, h = 3377
+ Box[483]: x = 82, y = 29, w = 2201, h = 3464
+ Box[484]: x = 326, y = 58, w = 2079, h = 3377
+ Box[485]: x = 146, y = 17, w = 2300, h = 3470
+ Box[486]: x = 105, y = 58, w = 2247, h = 3371
+ Box[487]: x = 70, y = 12, w = 2195, h = 3476
+ Box[488]: x = 111, y = 58, w = 2242, h = 3371
+ Box[489]: x = 76, y = 12, w = 2189, h = 3476
+ Box[490]: x = 111, y = 58, w = 2300, h = 3348
+ Box[491]: x = 105, y = 6, w = 2352, h = 3453
+ Box[492]: x = 99, y = 58, w = 2306, h = 3354
+ Box[493]: x = 105, y = 12, w = 2335, h = 3476
+ Box[494]: x = 87, y = 58, w = 2317, h = 3354
+ Box[495]: x = 93, y = 0, w = 2341, h = 3488
+ Box[496]: x = 105, y = 58, w = 2288, h = 3307
+ Box[497]: x = 116, y = 0, w = 2346, h = 3447
+ Box[498]: x = 87, y = 58, w = 2253, h = 3348
+ Box[499]: x = 70, y = 0, w = 2393, h = 3488
+ Box[500]: x = 140, y = 58, w = 2276, h = 3435
+ Box[501]: x = 99, y = 0, w = 2154, h = 3482
+ Box[502]: x = 58, y = 58, w = 2341, h = 3319
+ Box[503]: x = 64, y = 0, w = 1950, h = 3499
+ Box[504]: x = 87, y = 58, w = 2311, h = 3423
+ Box[505]: x = 111, y = 23, w = 2387, h = 3476
+ Box[506]: x = 99, y = 58, w = 2306, h = 3423
+ Box[507]: x = 116, y = 17, w = 2387, h = 3476
+ Box[508]: x = 111, y = 58, w = 2294, h = 3435
+ Box[509]: x = 122, y = 17, w = 2381, h = 3476
+ Box[510]: x = 87, y = 58, w = 2311, h = 3423
+ Box[511]: x = 111, y = 35, w = 2387, h = 3458
+ Box[512]: x = 128, y = 58, w = 2276, h = 3441
+ Box[513]: x = 122, y = 17, w = 2370, h = 3488
+ Box[514]: x = 76, y = 58, w = 2306, h = 3429
+ Box[515]: x = 111, y = 0, w = 1281, h = 3511
+ Box[516]: x = 58, y = 58, w = 2317, h = 3365
+ Box[517]: x = 105, y = 17, w = 2352, h = 3488
+ Box[518]: x = 70, y = 58, w = 2311, h = 3429
+ Box[519]: x = 111, y = 0, w = 1921, h = 3511
+ Box[520]: x = 58, y = 58, w = 2317, h = 3348
+ Box[521]: x = 105, y = 0, w = 2346, h = 3511
+ Box[522]: x = 52, y = 58, w = 2311, h = 3423
+ Box[523]: x = 105, y = 0, w = 1467, h = 3522
+ Box[524]: x = 52, y = 58, w = 1723, h = 3371
+ Box[525]: x = 111, y = 0, w = 1461, h = 3511
+ Box[526]: x = 47, y = 58, w = 2335, h = 3418
+ Box[527]: x = 105, y = 17, w = 2381, h = 3482
+ Box[528]: x = 47, y = 58, w = 2323, h = 3359
+ Box[529]: x = 146, y = 12, w = 2335, h = 3464
+ Box[530]: x = 35, y = 58, w = 2335, h = 3359
+ Box[531]: x = 151, y = 52, w = 2329, h = 3458
+ Box[532]: x = 52, y = 58, w = 2335, h = 3359
+ Box[533]: x = 157, y = 12, w = 2335, h = 3493
+ Box[534]: x = 35, y = 58, w = 2346, h = 3324
+ Box[535]: x = 157, y = 35, w = 2335, h = 3470
+ Box[536]: x = 35, y = 58, w = 2358, h = 3359
+ Box[537]: x = 163, y = 35, w = 2352, h = 3464
+ Box[538]: x = 35, y = 58, w = 2358, h = 3365
+ Box[539]: x = 157, y = 29, w = 2364, h = 3464
+ Box[540]: x = 35, y = 58, w = 2364, h = 3354
+ Box[541]: x = 163, y = 23, w = 2381, h = 3441
+ Box[542]: x = 35, y = 58, w = 2358, h = 3324
+ Box[543]: x = 163, y = 35, w = 2381, h = 3435
+ Box[544]: x = 35, y = 58, w = 2358, h = 3359
+ Box[545]: x = 157, y = 35, w = 2387, h = 3453
+ Box[546]: x = 0, y = 58, w = 2370, h = 3365
+ Box[547]: x = 134, y = 0, w = 2346, h = 3499
+ Box[548]: x = 151, y = 58, w = 2160, h = 3359
+ Box[549]: x = 0, y = 0, w = 0, h = 0
diff --git a/leptonica/prog/boxedpage.jpg b/leptonica/prog/boxedpage.jpg
new file mode 100644
index 00000000..1b7030f1
--- /dev/null
+++ b/leptonica/prog/boxedpage.jpg
Binary files differ
diff --git a/leptonica/prog/brev.06.75.jpg b/leptonica/prog/brev.06.75.jpg
new file mode 100644
index 00000000..03ec00bf
--- /dev/null
+++ b/leptonica/prog/brev.06.75.jpg
Binary files differ
diff --git a/leptonica/prog/brev.10.75.jpg b/leptonica/prog/brev.10.75.jpg
new file mode 100644
index 00000000..af809928
--- /dev/null
+++ b/leptonica/prog/brev.10.75.jpg
Binary files differ
diff --git a/leptonica/prog/brev.14.75.jpg b/leptonica/prog/brev.14.75.jpg
new file mode 100644
index 00000000..955e5045
--- /dev/null
+++ b/leptonica/prog/brev.14.75.jpg
Binary files differ
diff --git a/leptonica/prog/brev.20.75.jpg b/leptonica/prog/brev.20.75.jpg
new file mode 100644
index 00000000..b2c22f5b
--- /dev/null
+++ b/leptonica/prog/brev.20.75.jpg
Binary files differ
diff --git a/leptonica/prog/brev.36.75.jpg b/leptonica/prog/brev.36.75.jpg
new file mode 100644
index 00000000..8f7e3cc2
--- /dev/null
+++ b/leptonica/prog/brev.36.75.jpg
Binary files differ
diff --git a/leptonica/prog/brev.53.75.jpg b/leptonica/prog/brev.53.75.jpg
new file mode 100644
index 00000000..134bccb9
--- /dev/null
+++ b/leptonica/prog/brev.53.75.jpg
Binary files differ
diff --git a/leptonica/prog/brev.56.75.jpg b/leptonica/prog/brev.56.75.jpg
new file mode 100644
index 00000000..53ad703a
--- /dev/null
+++ b/leptonica/prog/brev.56.75.jpg
Binary files differ
diff --git a/leptonica/prog/breviar.38.150.jpg b/leptonica/prog/breviar.38.150.jpg
new file mode 100644
index 00000000..0a564e59
--- /dev/null
+++ b/leptonica/prog/breviar.38.150.jpg
Binary files differ
diff --git a/leptonica/prog/brothers.150.jpg b/leptonica/prog/brothers.150.jpg
new file mode 100644
index 00000000..74fb9bf0
--- /dev/null
+++ b/leptonica/prog/brothers.150.jpg
Binary files differ
diff --git a/leptonica/prog/buffertest.c b/leptonica/prog/buffertest.c
new file mode 100644
index 00000000..5e5d569d
--- /dev/null
+++ b/leptonica/prog/buffertest.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * buffertest.c
+ *
+ * Tests the bbuffer operations
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define NBLOCKS 11
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_uint8 *array1, *array2, *dataout, *dataout2;
+l_int32 i, blocksize, same;
+size_t nbytes, nout, nout2;
+L_BBUFFER *bb, *bb2;
+FILE *fp;
+static char mainName[] = "buffertest";
+
+ if (argc != 3)
+ return ERROR_INT(" Syntax: buffertest filein fileout", mainName, 1);
+ filein = argv[1];
+ fileout = argv[2];
+ setLeptDebugOK(1);
+
+ if ((array1 = l_binaryRead(filein, &nbytes)) == NULL)
+ return ERROR_INT("array not made", mainName, 1);
+ lept_stderr("Bytes read from file: %lu\n", (unsigned long)nbytes);
+
+ /* Application of byte buffer ops: compress/decompress in memory */
+#if 1
+ dataout = zlibCompress(array1, nbytes, &nout);
+ l_binaryWrite(fileout, "w", dataout, nout);
+
+ dataout2 = zlibUncompress(dataout, nout, &nout2);
+ l_binaryWrite("/tmp/dataout2", "w", dataout2, nout2);
+
+ filesAreIdentical(filein, "/tmp/dataout2", &same);
+ if (same)
+ lept_stderr("Correct: data is the same\n");
+ else
+ lept_stderr("Error: data is different\n");
+
+ lept_stderr("nbytes in = %lu, nbytes comp = %lu, nbytes uncomp = %lu\n",
+ (unsigned long)nbytes, (unsigned long)nout, (unsigned long)nout2);
+ lept_free(dataout);
+ lept_free(dataout2);
+#endif
+
+ /* Low-level byte buffer read/write test */
+#if 1
+ bb = bbufferCreate(array1, nbytes);
+ bbufferRead(bb, array1, nbytes);
+
+ array2 = (l_uint8 *)lept_calloc(2 * nbytes, sizeof(l_uint8));
+
+ lept_stderr(" Bytes initially in buffer: %d\n", bb->n);
+
+ blocksize = (2 * nbytes) / NBLOCKS;
+ for (i = 0; i <= NBLOCKS; i++) {
+ bbufferWrite(bb, array2, blocksize, &nout);
+ lept_stderr(" block %d: wrote %lu bytes\n", i + 1,
+ (unsigned long)nout);
+ }
+
+ lept_stderr(" Bytes left in buffer: %d\n", bb->n);
+
+ bb2 = bbufferCreate(NULL, 0);
+ bbufferRead(bb2, array1, nbytes);
+ fp = lept_fopen(fileout, "wb");
+ bbufferWriteStream(bb2, fp, nbytes, &nout);
+ lept_stderr(" bytes written out to fileout: %lu\n", (unsigned long)nout);
+ lept_fclose(fp);
+
+ bbufferDestroy(&bb);
+ bbufferDestroy(&bb2);
+ lept_free(array2);
+#endif
+
+ lept_free(array1);
+ return 0;
+}
diff --git a/leptonica/prog/bytea_reg.c b/leptonica/prog/bytea_reg.c
new file mode 100644
index 00000000..d62614ea
--- /dev/null
+++ b/leptonica/prog/bytea_reg.c
@@ -0,0 +1,180 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * bytea_reg.c
+ *
+ * This tests the byte array utility.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *str1, *str2;
+l_uint8 *data1, *data2;
+l_int32 i, n;
+size_t size1, size2, slice, total, start;
+FILE *fp;
+L_DNA *da;
+SARRAY *sa;
+L_BYTEA *lba1, *lba2, *lba3, *lba4;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBJPEG)
+ L_ERROR("This test requires libjpeg to run.\n", "bytea_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/bytea");
+
+ /* Test basic init and join */
+ lba1 = l_byteaInitFromFile("feyn.tif");
+ lba2 = l_byteaInitFromFile("test24.jpg");
+ lba3 = l_byteaCopy(lba2, L_COPY);
+ size1 = l_byteaGetSize(lba1);
+ size2 = l_byteaGetSize(lba2);
+ l_byteaJoin(lba1, &lba3); /* destroys lba3 */
+ l_byteaWrite("/tmp/lept/bytea/lba2.bya", lba2, 0, 0);
+ regTestCheckFile(rp, "/tmp/lept/bytea/lba2.bya"); /* 0 */
+
+ /* Test split, using init from memory */
+ lba3 = l_byteaInitFromMem(lba1->data, size1);
+ lba4 = l_byteaInitFromMem(lba1->data + size1, size2);
+ regTestCompareStrings(rp, lba1->data, size1, lba3->data, lba3->size);
+ regTestCompareStrings(rp, lba2->data, size2, lba4->data, lba4->size);
+ /* 1, 2 */
+ l_byteaDestroy(&lba4);
+
+ /* Test split using the split function */
+ l_byteaSplit(lba1, size1, &lba4); /* zeroes lba1 beyond size1 */
+ regTestCompareStrings(rp, lba2->data, size2, lba4->data, lba4->size);
+ /* 3 */
+ l_byteaDestroy(&lba1);
+ l_byteaDestroy(&lba2);
+ l_byteaDestroy(&lba3);
+ l_byteaDestroy(&lba4);
+
+ /* Test appending with strings
+ * In order to make sure it succeeds on windows, where text
+ * lines are typically terminated with with "\r\n" instead
+ * of '\n', we remove the '\r' characters. Also, note that
+ * for files opened for read with "rb", as we do throughout
+ * leptonica with fopenReadStream(), windows will not
+ * append the '\r'. We have 3 ways to remove the '\r'
+ * characters from the byte array data1 with size1 bytes:
+ * str1 = (char *)arrayReplaceEachSequence(data1, size1,
+ * (l_uint8 *)"\r", 1, NULL, 0, &size2, NULL);
+ * str1 = stringReplaceEachSubstr((char *)data1, "\r",
+ * "", NULL);
+ * str1 = stringRemoveChars((char *)data1, "\r");
+ * which can then be used to initialize a L_Bytea using
+ * lba1 = l_byteaInitFromMem((l_uint8 *)str1, strlen(str1));
+ */
+
+ data1 = l_binaryRead("kernel_reg.c", &size1);
+#if 0
+ str1 = (char *)arrayReplaceEachSequence(data1, size1, (l_uint8 *)"\r", 1,
+ NULL, 0, &size2, NULL);
+ lba1 = l_byteaInitFromMem((l_uint8 *)str1, size2);
+#elif 0
+ str1 = stringReplaceEachSubstr((char *)data1, "\r", "", NULL);
+ lba1 = l_byteaInitFromMem((l_uint8 *)str1, strlen(str1));
+#else
+ str1 = stringRemoveChars((char *)data1, "\r");
+ lba1 = l_byteaInitFromMem((l_uint8 *)str1, strlen(str1));
+#endif
+ sa = sarrayCreateLinesFromString(str1, 1);
+ lba2 = l_byteaCreate(0);
+ n = sarrayGetCount(sa);
+ for (i = 0; i < n; i++) {
+ str2 = sarrayGetString(sa, i, L_NOCOPY);
+ l_byteaAppendString(lba2, str2);
+ l_byteaAppendString(lba2, "\n");
+ }
+ data2 = l_byteaGetData(lba1, &size2);
+ regTestCompareStrings(rp, lba1->data, lba1->size, lba2->data, lba2->size);
+ /* 4 */
+ lept_free(data1);
+ lept_free(str1);
+ sarrayDestroy(&sa);
+ l_byteaDestroy(&lba1);
+ l_byteaDestroy(&lba2);
+
+ /* Test appending with binary data */
+ slice = 1000;
+ total = nbytesInFile("breviar.38.150.jpg");
+ lba1 = l_byteaCreate(100);
+ n = 2 + total / slice; /* using 1 is correct; using 2 gives two errors */
+ lept_stderr("******************************************************\n");
+ lept_stderr("* Testing error checking: ignore two reported errors *\n");
+ for (i = 0, start = 0; i < n; i++, start += slice) {
+ data2 = l_binaryReadSelect("breviar.38.150.jpg", start, slice, &size2);
+ l_byteaAppendData(lba1, data2, size2);
+ lept_free(data2);
+ }
+ lept_stderr("******************************************************\n");
+ data1 = l_byteaGetData(lba1, &size1);
+ data2 = l_binaryRead("breviar.38.150.jpg", &size2);
+ regTestCompareStrings(rp, data1, size1, data2, size2); /* 5 */
+ l_byteaDestroy(&lba1);
+ lept_free(data2);
+
+ /* Test search */
+ convertToPdf("test24.jpg", L_JPEG_ENCODE, 0, "/tmp/lept/bytea/test24.pdf",
+ 0, 0, 100, NULL, NULL, 0);
+ lba1 = l_byteaInitFromFile("/tmp/lept/bytea/test24.pdf");
+ l_byteaFindEachSequence(lba1, (l_uint8 *)" 0 obj\n", 7, &da);
+ n = l_dnaGetCount(da);
+ regTestCompareValues(rp, 6, n, 0.0); /* 6 */
+ l_byteaDestroy(&lba1);
+ l_dnaDestroy(&da);
+
+ /* Test write to file */
+ lba1 = l_byteaInitFromFile("feyn.tif");
+ size1 = l_byteaGetSize(lba1);
+ fp = lept_fopen("/tmp/lept/bytea/feyn.dat", "wb");
+ for (start = 0; start < size1; start += 1000) {
+ l_byteaWriteStream(fp, lba1, start, 1000);
+ }
+ lept_fclose(fp);
+ lba2 = l_byteaInitFromFile("/tmp/lept/bytea/feyn.dat");
+ regTestCompareStrings(rp, lba1->data, size1, lba2->data,
+ lba2->size); /* 7 */
+ l_byteaDestroy(&lba1);
+ l_byteaDestroy(&lba2);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/candelabrum.011.jpg b/leptonica/prog/candelabrum.011.jpg
new file mode 100644
index 00000000..a4b26bf5
--- /dev/null
+++ b/leptonica/prog/candelabrum.011.jpg
Binary files differ
diff --git a/leptonica/prog/cat-and-mouse.png b/leptonica/prog/cat-and-mouse.png
new file mode 100644
index 00000000..6c292b06
--- /dev/null
+++ b/leptonica/prog/cat-and-mouse.png
Binary files differ
diff --git a/leptonica/prog/cat.007.jpg b/leptonica/prog/cat.007.jpg
new file mode 100644
index 00000000..00466c36
--- /dev/null
+++ b/leptonica/prog/cat.007.jpg
Binary files differ
diff --git a/leptonica/prog/cat.035.jpg b/leptonica/prog/cat.035.jpg
new file mode 100644
index 00000000..82428395
--- /dev/null
+++ b/leptonica/prog/cat.035.jpg
Binary files differ
diff --git a/leptonica/prog/cavalerie.11.jpg b/leptonica/prog/cavalerie.11.jpg
new file mode 100644
index 00000000..68375b0b
--- /dev/null
+++ b/leptonica/prog/cavalerie.11.jpg
Binary files differ
diff --git a/leptonica/prog/cavalerie.29.jpg b/leptonica/prog/cavalerie.29.jpg
new file mode 100644
index 00000000..00169630
--- /dev/null
+++ b/leptonica/prog/cavalerie.29.jpg
Binary files differ
diff --git a/leptonica/prog/ccbord_reg.c b/leptonica/prog/ccbord_reg.c
new file mode 100644
index 00000000..2e01b8f6
--- /dev/null
+++ b/leptonica/prog/ccbord_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * ccbord_reg.c
+ *
+ * Regression test for border-following representations of binary images.
+ * This uses the steps in ccbordtest.c to test specified images.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+void RunCCBordTest(const char *fname, L_REGPARAMS *rp);
+
+int main(int argc,
+ char **argv)
+{
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+ lept_mkdir("lept/ccbord");
+
+ RunCCBordTest("feyn-fract.tif", rp);
+ RunCCBordTest("dreyfus1.png", rp);
+ return regTestCleanup(rp);
+}
+
+/* ----------------------------------------------- */
+void RunCCBordTest(const char *fname,
+ L_REGPARAMS *rp)
+{
+char *svgstr;
+l_int32 count, disp;
+CCBORDA *ccba, *ccba2;
+PIX *pixs, *pixd, *pixd2, *pixd3;
+PIX *pixt, *pixc, *pixc2;
+
+ pixs = pixRead(fname);
+ disp = rp->display;
+
+ /*------------------------------------------------------------------*
+ * Get border representation and verify border pixels *
+ *------------------------------------------------------------------*/
+ if(disp) lept_stderr("Get border representation...");
+ ccba = pixGetAllCCBorders(pixs);
+
+ /* Get global locs directly and display borders */
+ if (disp) lept_stderr("Convert from local to global locs...");
+ ccbaGenerateGlobalLocs(ccba);
+ if (disp) lept_stderr("display border representation...");
+ pixd = ccbaDisplayBorder(ccba);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 0,7 */
+ pixDisplayWithTitle(pixd, 0, 0, NULL, rp->display);
+ pixDestroy(&pixd);
+
+ /* Get step chain code, then global coords, and display borders */
+ if (disp) lept_stderr("get step chain code...");
+ ccbaGenerateStepChains(ccba);
+ if (disp) lept_stderr("convert from step chain to global locs...");
+ ccbaStepChainsToPixCoords(ccba, CCB_GLOBAL_COORDS);
+ if (disp) lept_stderr("display border representation\n");
+ pixd = ccbaDisplayBorder(ccba);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 1,8 */
+ pixDisplayWithTitle(pixd, 200, 0, NULL, rp->display);
+
+ /* Check if border pixels are in original set */
+ lept_stderr("Check if border pixels are in original set\n");
+ pixt = pixSubtract(NULL, pixd, pixs);
+ pixCountPixels(pixt, &count, NULL);
+ if (count == 0)
+ lept_stderr(" ==> all border pixels are in original set\n");
+ else
+ lept_stderr(" ==> %d border pixels are not in original set\n", count);
+ pixDestroy(&pixt);
+
+ /* Display image */
+ lept_stderr("Reconstruct image\n");
+ pixc = ccbaDisplayImage2(ccba);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 2,9 */
+ pixDisplayWithTitle(pixc, 400, 0, NULL, rp->display);
+
+ /* Check with original to see if correct */
+ pixXor(pixc, pixc, pixs);
+ pixCountPixels(pixc, &count, NULL);
+ if (count == 0)
+ lept_stderr(" ==> perfect direct reconstruction\n");
+ else {
+ l_int32 w, h, i, j;
+ l_uint32 val;
+ lept_stderr(" ==> %d pixels in error in reconstruction\n", count);
+#if 1
+ w = pixGetWidth(pixc);
+ h = pixGetHeight(pixc);
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ pixGetPixel(pixc, j, i, &val);
+ if (val == 1)
+ lept_stderr("bad pixel at (%d, %d)\n", j, i);
+ }
+ }
+ pixWrite("/tmp/lept/ccbord/badpixels1.png", pixc, IFF_PNG);
+#endif
+ }
+
+ /*----------------------------------------------------------*
+ * Write to file (compressed) and read back *
+ *----------------------------------------------------------*/
+ if (disp) lept_stderr("Write serialized step data...");
+ ccbaWrite("/tmp/lept/ccbord/stepdata.ccb", ccba);
+ if (disp) lept_stderr("read serialized step data...");
+ ccba2 = ccbaRead("/tmp/lept/ccbord/stepdata.ccb");
+
+ /* Display the border pixels again */
+ if (disp) lept_stderr("convert from step chain to global locs...");
+ ccbaStepChainsToPixCoords(ccba2, CCB_GLOBAL_COORDS);
+ if (disp) lept_stderr("display border representation\n");
+ pixd2 = ccbaDisplayBorder(ccba2);
+ regTestWritePixAndCheck(rp, pixd2, IFF_PNG); /* 3,10 */
+ pixDisplayWithTitle(pixd2, 600, 0, NULL, rp->display);
+
+ /* Check if border pixels are same as first time */
+ lept_stderr("Check border after write/read\n");
+ pixXor(pixd2, pixd2, pixd);
+ pixCountPixels(pixd2, &count, NULL);
+ if (count == 0)
+ lept_stderr(" ==> perfect w/r border recon\n");
+ else
+ lept_stderr(" ==> %d pixels in error in w/r recon\n", count);
+ pixDestroy(&pixd2);
+
+ /* Display image again */
+ if (disp) lept_stderr("Convert from step chain to local coords...\n");
+ ccbaStepChainsToPixCoords(ccba2, CCB_LOCAL_COORDS);
+ lept_stderr("Reconstruct image from file\n");
+ pixc2 = ccbaDisplayImage2(ccba2);
+ regTestWritePixAndCheck(rp, pixc2, IFF_PNG); /* 4,11 */
+ pixDisplayWithTitle(pixc2, 800, 0, NULL, rp->display);
+
+ /* Check with original to see if correct */
+ pixXor(pixc2, pixc2, pixs);
+ pixCountPixels(pixc2, &count, NULL);
+ if (count == 0)
+ lept_stderr(" ==> perfect image recon\n");
+ else {
+ l_int32 w, h, i, j;
+ l_uint32 val;
+ lept_stderr(" ==> %d pixels in error in image recon\n", count);
+#if 1
+ w = pixGetWidth(pixc2);
+ h = pixGetHeight(pixc2);
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ pixGetPixel(pixc2, j, i, &val);
+ if (val == 1)
+ lept_stderr("bad pixel at (%d, %d)\n", j, i);
+ }
+ }
+ pixWrite("/tmp/lept/ccbord/badpixels2.png", pixc, IFF_PNG);
+#endif
+ }
+
+ /*----------------------------------------------------------*
+ * Make, display and check single path border for svg *
+ *----------------------------------------------------------*/
+ /* Make local single path border for svg */
+ if (disp) lept_stderr("Make local single path borders for svg ...");
+ ccbaGenerateSinglePath(ccba);
+
+ /* Generate global single path border */
+ if (disp) lept_stderr("generate global single path borders ...");
+ ccbaGenerateSPGlobalLocs(ccba, CCB_SAVE_TURNING_PTS);
+
+ /* Display border pixels from single path */
+ if (disp) lept_stderr("display border from single path\n");
+ pixd3 = ccbaDisplaySPBorder(ccba);
+ regTestWritePixAndCheck(rp, pixd3, IFF_PNG); /* 5,12 */
+ pixDisplayWithTitle(pixd3, 1000, 0, NULL, rp->display);
+
+ /* Check if border pixels are in original set */
+ lept_stderr("Check if border pixels are in original set\n");
+ pixt = pixSubtract(NULL, pixd3, pixs);
+ pixCountPixels(pixt, &count, NULL);
+ if (count == 0)
+ lept_stderr(" ==> all border pixels are in original set\n");
+ else
+ lept_stderr(" ==> %d border pixels are not in original set\n", count);
+ lept_stderr("============================================\n");
+ pixDestroy(&pixt);
+ pixDestroy(&pixd3);
+
+ /* Output in svg file format */
+ svgstr = ccbaWriteSVGString(ccba);
+ regTestWriteDataAndCheck(rp, svgstr, strlen(svgstr), "ccb"); /* 6,13 */
+
+ ccbaDestroy(&ccba2);
+ ccbaDestroy(&ccba);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ pixDestroy(&pixc);
+ pixDestroy(&pixc2);
+ lept_free(svgstr);
+}
diff --git a/leptonica/prog/ccbordtest.c b/leptonica/prog/ccbordtest.c
new file mode 100644
index 00000000..828a1ed1
--- /dev/null
+++ b/leptonica/prog/ccbordtest.c
@@ -0,0 +1,246 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * ccbordtest.c
+ *
+ * Comprehensive test for border-following representations
+ * of binary images.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein;
+l_int32 count;
+CCBORDA *ccba, *ccba2;
+PIX *pixs, *pixd, *pixd2, *pixd3;
+PIX *pixt, *pixc, *pixc2;
+static char mainName[] = "ccbordtest";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: ccbordtest filein", mainName, 1);
+ filein = argv[1];
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/ccbord");
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ lept_stderr("Get border representation...");
+ startTimer();
+ ccba = pixGetAllCCBorders(pixs);
+ lept_stderr("%6.3f sec\n", stopTimer());
+
+ /* Get global locs directly and display borders */
+ lept_stderr("Convert from local to global locs...");
+ startTimer();
+ ccbaGenerateGlobalLocs(ccba);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ lept_stderr("Display border representation...");
+ startTimer();
+ pixd = ccbaDisplayBorder(ccba);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ pixWrite("/tmp/lept/ccbord/junkborder1.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+
+ /* Get step chain code, then global coords, and display borders */
+ lept_stderr("Get step chain code...");
+ startTimer();
+ ccbaGenerateStepChains(ccba);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ lept_stderr("Convert from step chain to global locs...");
+ startTimer();
+ ccbaStepChainsToPixCoords(ccba, CCB_GLOBAL_COORDS);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ lept_stderr("Display border representation...");
+ startTimer();
+ pixd = ccbaDisplayBorder(ccba);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ pixWrite("/tmp/lept/ccbord/junkborder1.png", pixd, IFF_PNG);
+
+ /* Check if border pixels are in original set */
+ lept_stderr("Check if border pixels are in original set ...\n");
+ pixt = pixSubtract(NULL, pixd, pixs);
+ pixCountPixels(pixt, &count, NULL);
+ if (count == 0)
+ lept_stderr(" all border pixels are in original set\n");
+ else
+ lept_stderr(" %d border pixels are not in original set\n", count);
+ pixDestroy(&pixt);
+
+ /* Display image */
+ lept_stderr("Reconstruct image ...");
+ startTimer();
+/* pixc = ccbaDisplayImage1(ccba); */
+ pixc = ccbaDisplayImage2(ccba);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ pixWrite("/tmp/lept/ccbord/junkrecon1.png", pixc, IFF_PNG);
+
+ /* check with original to see if correct */
+ lept_stderr("Check with original to see if correct ...\n");
+ pixXor(pixc, pixc, pixs);
+ pixCountPixels(pixc, &count, NULL);
+ if (count == 0)
+ lept_stderr(" perfect direct recon\n");
+ else {
+ l_int32 w, h, i, j;
+ l_uint32 val;
+ lept_stderr(" %d pixels in error in recon\n", count);
+#if 1
+ w = pixGetWidth(pixc);
+ h = pixGetHeight(pixc);
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ pixGetPixel(pixc, j, i, &val);
+ if (val == 1)
+ lept_stderr("bad pixel at (%d, %d)\n", j, i);
+ }
+ }
+ pixWrite("/tmp/lept/ccbord/junkbadpixels.png", pixc, IFF_PNG);
+#endif
+ }
+
+
+ /*----------------------------------------------------------*
+ * write to file (compressed) and read back *
+ *----------------------------------------------------------*/
+ lept_stderr("Write serialized step data...");
+ startTimer();
+ ccbaWrite("/tmp/junkstepout", ccba);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ lept_stderr("Read serialized step data...");
+ startTimer();
+ ccba2 = ccbaRead("/tmp/junkstepout");
+ lept_stderr("%6.3f sec\n", stopTimer());
+
+ /* display the border pixels again */
+ lept_stderr("Convert from step chain to global locs...");
+ startTimer();
+ ccbaStepChainsToPixCoords(ccba2, CCB_GLOBAL_COORDS);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ lept_stderr("Display border representation...");
+ startTimer();
+ pixd2 = ccbaDisplayBorder(ccba2);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ pixWrite("/tmp/lept/ccbord/junkborder2.png", pixd2, IFF_PNG);
+
+ /* check if border pixels are same as first time */
+ pixXor(pixd2, pixd2, pixd);
+ pixCountPixels(pixd2, &count, NULL);
+ if (count == 0)
+ lept_stderr(" perfect w/r border recon\n");
+ else
+ lept_stderr(" %d pixels in error in w/r recon\n", count);
+ pixDestroy(&pixd2);
+
+ /* display image again */
+ lept_stderr("Convert from step chain to local coords...");
+ startTimer();
+ ccbaStepChainsToPixCoords(ccba2, CCB_LOCAL_COORDS);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ lept_stderr("Reconstruct image from file ...");
+ startTimer();
+/* pixc2 = ccbaDisplayImage1(ccba2); */
+ pixc2 = ccbaDisplayImage2(ccba2);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ pixWrite("/tmp/lept/ccbord/junkrecon2.png", pixc2, IFF_PNG);
+
+ /* check with original to see if correct */
+ lept_stderr("Check with original to see if correct ...\n");
+ pixXor(pixc2, pixc2, pixs);
+ pixCountPixels(pixc2, &count, NULL);
+ if (count == 0)
+ lept_stderr(" perfect image recon\n");
+ else {
+ l_int32 w, h, i, j;
+ l_uint32 val;
+ lept_stderr(" %d pixels in error in image recon\n", count);
+#if 1
+ w = pixGetWidth(pixc2);
+ h = pixGetHeight(pixc2);
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ pixGetPixel(pixc2, j, i, &val);
+ if (val == 1)
+ lept_stderr("bad pixel at (%d, %d)\n", j, i);
+ }
+ }
+ pixWrite("/tmp/lept/ccbord/junkbadpixels2.png", pixc2, IFF_PNG);
+#endif
+ }
+
+ /*----------------------------------------------------------*
+ * make, display and check single path border for svg *
+ *----------------------------------------------------------*/
+ /* make local single path border for svg */
+ lept_stderr("Make local single path borders for svg ...");
+ startTimer();
+ ccbaGenerateSinglePath(ccba);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ /* generate global single path border */
+ lept_stderr("Generate global single path borders ...");
+ startTimer();
+ ccbaGenerateSPGlobalLocs(ccba, CCB_SAVE_TURNING_PTS);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ /* display border pixels from single path */
+ lept_stderr("Display border from single path...");
+ startTimer();
+ pixd3 = ccbaDisplaySPBorder(ccba);
+ lept_stderr("%6.3f sec\n", stopTimer());
+ pixWrite("/tmp/lept/ccbord/junkborder3.png", pixd3, IFF_PNG);
+ /* check if border pixels are in original set */
+ lept_stderr("Check if border pixels are in original set ...\n");
+ pixt = pixSubtract(NULL, pixd3, pixs);
+ pixCountPixels(pixt, &count, NULL);
+ if (count == 0)
+ lept_stderr(" all border pixels are in original set\n");
+ else
+ lept_stderr(" %d border pixels are not in original set\n", count);
+ pixDestroy(&pixt);
+ pixDestroy(&pixd3);
+
+ /* output in svg file format */
+ lept_stderr("Write output in svg file format ...\n");
+ startTimer();
+ ccbaWriteSVG("/tmp/junksvg", ccba);
+ lept_stderr("%6.3f sec\n", stopTimer());
+
+ ccbaDestroy(&ccba2);
+ ccbaDestroy(&ccba);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ pixDestroy(&pixc);
+ pixDestroy(&pixc2);
+ return 0;
+}
diff --git a/leptonica/prog/cctest1.c b/leptonica/prog/cctest1.c
new file mode 100644
index 00000000..4bc8efce
--- /dev/null
+++ b/leptonica/prog/cctest1.c
@@ -0,0 +1,125 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * cctest1.c
+ *
+ * This is a test of the following function:
+ *
+ * BOXA *pixConnComp(PIX *pixs, PIXA **ppixa, l_int32 connectivity)
+ *
+ * pixs: input pix
+ * ppixa: &pixa (<optional> pixa of each c.c.)
+ * connectivity (4 or 8)
+ * boxa: returned array of boxes of c.c.
+ *
+ * Use NULL for &pixa if you don't want the pixa array.
+ *
+ * It also demonstrates a few display modes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define NTIMES 2
+
+l_int32 main(l_int32 argc,
+ char **argv)
+{
+char *filein;
+l_int32 i, n, count;
+BOX *box;
+BOXA *boxa;
+PIX *pixs, *pixd;
+PIXA *pixa;
+PIXCMAP *cmap;
+static char mainName[] = "cctest1";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: cctest1 filein", mainName, 1);
+ filein = argv[1];
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+ if (pixGetDepth(pixs) != 1)
+ return ERROR_INT("pixs not 1 bpp", mainName, 1);
+
+ /* Test speed of pixCountConnComp() */
+ startTimer();
+ for (i = 0; i < NTIMES; i++)
+ pixCountConnComp(pixs, 4, &count);
+ lept_stderr("Time to compute 4-cc: %6.3f sec\n", stopTimer()/NTIMES);
+ lept_stderr("Number of 4-cc: %d\n", count);
+ startTimer();
+ for (i = 0; i < NTIMES; i++)
+ pixCountConnComp(pixs, 8, &count);
+ lept_stderr("Time to compute 8-cc: %6.3f sec\n", stopTimer()/NTIMES);
+ lept_stderr("Number of 8-cc: %d\n", count);
+
+ /* Test speed of pixConnComp(), with only boxa output */
+ startTimer();
+ for (i = 0; i < NTIMES; i++) {
+ boxa = pixConnComp(pixs, NULL, 4);
+ boxaDestroy(&boxa);
+ }
+ lept_stderr("Time to compute 4-cc: %6.3f sec\n", stopTimer()/NTIMES);
+ startTimer();
+ for (i = 0; i < NTIMES; i++) {
+ boxa = pixConnComp(pixs, NULL, 8);
+ boxaDestroy(&boxa);
+ }
+ lept_stderr("Time to compute 8-cc: %6.3f sec\n", stopTimer()/NTIMES);
+
+ /* Draw outline of each c.c. box */
+ boxa = pixConnComp(pixs, NULL, 4);
+ n = boxaGetCount(boxa);
+ lept_stderr("Num 4-cc boxes: %d\n", n);
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxa, i, L_CLONE);
+ pixRenderBox(pixs, box, 3, L_FLIP_PIXELS);
+ boxDestroy(&box); /* remember, clones need to be destroyed */
+ }
+ boxaDestroy(&boxa);
+
+ /* Display each component as a random color in cmapped 8 bpp.
+ * Background is color 0; it is set to white. */
+ boxa = pixConnComp(pixs, &pixa, 4);
+ pixd = pixaDisplayRandomCmap(pixa, pixGetWidth(pixs), pixGetHeight(pixs));
+ cmap = pixGetColormap(pixd);
+ pixcmapResetColor(cmap, 0, 255, 255, 255); /* reset background to white */
+ pixDisplay(pixd, 100, 100);
+ boxaDestroy(&boxa);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ pixDestroy(&pixs);
+ return 0;
+}
+
+
diff --git a/leptonica/prog/ccthin1_reg.c b/leptonica/prog/ccthin1_reg.c
new file mode 100644
index 00000000..ea25172f
--- /dev/null
+++ b/leptonica/prog/ccthin1_reg.c
@@ -0,0 +1,202 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * ccthin1_reg.c
+ *
+ * Tests the "best" cc-preserving thinning functions.
+ * Displays all the strong cc-preserving 3x3 Sels.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+BOX *box;
+PIX *pix1, *pix2;
+PIXA *pixa;
+SEL *sel, *sel1, *sel2, *sel3;
+SELA *sela, *sela4, *sela8, *sela48;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixa = pixaCreate(0);
+
+ /* Generate and display all of the 4-cc sels */
+ sela4 = sela4ccThin(NULL);
+ pix1 = selaDisplayInPix(sela4, 35, 3, 15, 3);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix1, 400, 0, NULL, rp->display);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ selaDestroy(&sela4);
+
+ /* Generate and display all of the 8-cc sels */
+ sela8 = sela8ccThin(NULL);
+ pix1 = selaDisplayInPix(sela8, 35, 3, 15, 3);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix1, 850, 0, NULL, rp->display);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ selaDestroy(&sela8);
+
+ /* Generate and display all of the 4 and 8-cc preserving sels */
+ sela48 = sela4and8ccThin(NULL);
+ pix1 = selaDisplayInPix(sela48, 35, 3, 15, 4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix1, 1300, 0, NULL, rp->display);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ selaDestroy(&sela48);
+
+ /* Generate and display three of the 4-cc sels and their rotations */
+ sela = sela4ccThin(NULL);
+ sela4 = selaCreate(0);
+ selaFindSelByName(sela, "sel_4_1", NULL, &sel);
+ sel1 = selRotateOrth(sel, 1);
+ sel2 = selRotateOrth(sel, 2);
+ sel3 = selRotateOrth(sel, 3);
+ selaAddSel(sela4, sel, NULL, L_COPY);
+ selaAddSel(sela4, sel1, "sel_4_1_90", L_INSERT);
+ selaAddSel(sela4, sel2, "sel_4_1_180", L_INSERT);
+ selaAddSel(sela4, sel3, "sel_4_1_270", L_INSERT);
+ selaFindSelByName(sela, "sel_4_2", NULL, &sel);
+ sel1 = selRotateOrth(sel, 1);
+ sel2 = selRotateOrth(sel, 2);
+ sel3 = selRotateOrth(sel, 3);
+ selaAddSel(sela4, sel, NULL, L_COPY);
+ selaAddSel(sela4, sel1, "sel_4_2_90", L_INSERT);
+ selaAddSel(sela4, sel2, "sel_4_2_180", L_INSERT);
+ selaAddSel(sela4, sel3, "sel_4_2_270", L_INSERT);
+ selaFindSelByName(sela, "sel_4_3", NULL, &sel);
+ sel1 = selRotateOrth(sel, 1);
+ sel2 = selRotateOrth(sel, 2);
+ sel3 = selRotateOrth(sel, 3);
+ selaAddSel(sela4, sel, NULL, L_COPY);
+ selaAddSel(sela4, sel1, "sel_4_3_90", L_INSERT);
+ selaAddSel(sela4, sel2, "sel_4_3_180", L_INSERT);
+ selaAddSel(sela4, sel3, "sel_4_3_270", L_INSERT);
+ pix1 = selaDisplayInPix(sela4, 35, 3, 15, 4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pix1, 400, 500, NULL, rp->display);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ selaDestroy(&sela);
+ selaDestroy(&sela4);
+
+ /* Generate and display four of the 8-cc sels and their rotations */
+ sela = sela8ccThin(NULL);
+ sela8 = selaCreate(0);
+ selaFindSelByName(sela, "sel_8_2", NULL, &sel);
+ sel1 = selRotateOrth(sel, 1);
+ sel2 = selRotateOrth(sel, 2);
+ sel3 = selRotateOrth(sel, 3);
+ selaAddSel(sela8, sel, NULL, L_COPY);
+ selaAddSel(sela8, sel1, "sel_8_2_90", L_INSERT);
+ selaAddSel(sela8, sel2, "sel_8_2_180", L_INSERT);
+ selaAddSel(sela8, sel3, "sel_8_2_270", L_INSERT);
+ selaFindSelByName(sela, "sel_8_3", NULL, &sel);
+ sel1 = selRotateOrth(sel, 1);
+ sel2 = selRotateOrth(sel, 2);
+ sel3 = selRotateOrth(sel, 3);
+ selaAddSel(sela8, sel, NULL, L_COPY);
+ selaAddSel(sela8, sel1, "sel_8_3_90", L_INSERT);
+ selaAddSel(sela8, sel2, "sel_8_3_180", L_INSERT);
+ selaAddSel(sela8, sel3, "sel_8_3_270", L_INSERT);
+ selaFindSelByName(sela, "sel_8_5", NULL, &sel);
+ sel1 = selRotateOrth(sel, 1);
+ sel2 = selRotateOrth(sel, 2);
+ sel3 = selRotateOrth(sel, 3);
+ selaAddSel(sela8, sel, NULL, L_COPY);
+ selaAddSel(sela8, sel1, "sel_8_5_90", L_INSERT);
+ selaAddSel(sela8, sel2, "sel_8_5_180", L_INSERT);
+ selaAddSel(sela8, sel3, "sel_8_5_270", L_INSERT);
+ selaFindSelByName(sela, "sel_8_6", NULL, &sel);
+ sel1 = selRotateOrth(sel, 1);
+ sel2 = selRotateOrth(sel, 2);
+ sel3 = selRotateOrth(sel, 3);
+ selaAddSel(sela8, sel, NULL, L_COPY);
+ selaAddSel(sela8, sel1, "sel_8_6_90", L_INSERT);
+ selaAddSel(sela8, sel2, "sel_8_6_180", L_INSERT);
+ selaAddSel(sela8, sel3, "sel_8_6_270", L_INSERT);
+ pix1 = selaDisplayInPix(sela8, 35, 3, 15, 4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix1, 1000, 500, NULL, rp->display);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ selaDestroy(&sela);
+ selaDestroy(&sela8);
+
+ /* Optional display */
+ if (rp->display) {
+ lept_mkdir("/lept/thin");
+ lept_stderr("Writing to: /tmp/lept/thin/ccthin1-1.pdf");
+ pixaConvertToPdf(pixa, 0, 1.0, 0, 0, "Thin 1 Sels",
+ "/tmp/lept/thin/ccthin1-1.pdf");
+ }
+ pixaDestroy(&pixa);
+
+ pixa = pixaCreate(0);
+
+ /* Test the best 4 and 8 cc thinning */
+ pix2 = pixRead("feyn.tif");
+ box = boxCreate(683, 799, 970, 479);
+ pix1 = pixClipRectangle(pix2, box, NULL);
+ boxDestroy(&box);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 5 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+
+ pix2 = pixThinConnected(pix1, L_THIN_FG, 4, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 6 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix2 = pixThinConnected(pix1, L_THIN_BG, 4, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 7 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ pix2 = pixThinConnected(pix1, L_THIN_FG, 8, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 8 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix2 = pixThinConnected(pix1, L_THIN_BG, 8, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 9 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ /* Display tiled */
+ pix1 = pixaDisplayTiledAndScaled(pixa, 8, 500, 1, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10 */
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ if (rp->display) {
+ lept_stderr("Writing to: /tmp/lept/thin/ccthin1-2.pdf");
+ pixaConvertToPdf(pixa, 0, 1.0, 0, 0, "Thin 1 Results",
+ "/tmp/lept/thin/ccthin1-2.pdf");
+ }
+ pixaDestroy(&pixa);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/ccthin2_reg.c b/leptonica/prog/ccthin2_reg.c
new file mode 100644
index 00000000..a5296578
--- /dev/null
+++ b/leptonica/prog/ccthin2_reg.c
@@ -0,0 +1,192 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * ccthin2_reg.c
+ *
+ * Tests:
+ * - The examples in pixThinConnectedBySet()
+ * - Use of thinning and thickening in stroke width normalization
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i;
+BOX *box;
+PIX *pixs, *pix1, *pix2;
+PIXA *pixa1, *pixa2, *pixa3, *pixa4, *pixa5;
+PIXAA *paa;
+L_REGPARAMS *rp;
+SELA *sela;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "ccthin_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Clip to foreground to see if there are any boundary
+ * artifacts from thinning and thickening. (There are not.) */
+ pix1 = pixRead("feyn.tif");
+ box = boxCreate(683, 799, 970, 479);
+ pix2 = pixClipRectangle(pix1, box, NULL);
+ pixClipToForeground(pix2, &pixs, NULL);
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxDestroy(&box);
+
+ pixa1 = pixaCreate(0);
+
+ sela = selaMakeThinSets(1, 0);
+ pix1 = pixThinConnectedBySet(pixs, L_THIN_FG, sela, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ selaDestroy(&sela);
+
+ sela = selaMakeThinSets(2, 0);
+ pix1 = pixThinConnectedBySet(pixs, L_THIN_FG, sela, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1 */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ selaDestroy(&sela);
+
+ sela = selaMakeThinSets(3, 0);
+ pix1 = pixThinConnectedBySet(pixs, L_THIN_FG, sela, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ selaDestroy(&sela);
+
+ sela = selaMakeThinSets(4, 0);
+ pix1 = pixThinConnectedBySet(pixs, L_THIN_FG, sela, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 3 */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ selaDestroy(&sela);
+
+ sela = selaMakeThinSets(5, 0);
+ pix1 = pixThinConnectedBySet(pixs, L_THIN_FG, sela, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 4 */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ selaDestroy(&sela);
+
+ sela = selaMakeThinSets(6, 0);
+ pix1 = pixThinConnectedBySet(pixs, L_THIN_FG, sela, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 5 */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ selaDestroy(&sela);
+
+ sela = selaMakeThinSets(7, 0);
+ pix1 = pixThinConnectedBySet(pixs, L_THIN_FG, sela, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 6 */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ selaDestroy(&sela);
+
+ sela = selaMakeThinSets(8, 0);
+ pix1 = pixThinConnectedBySet(pixs, L_THIN_FG, sela, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 7 */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ selaDestroy(&sela);
+
+ sela = selaMakeThinSets(9, 0);
+ pix1 = pixThinConnectedBySet(pixs, L_THIN_FG, sela, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 8 */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ selaDestroy(&sela);
+
+ sela = selaMakeThinSets(10, 0);
+ pix1 = pixThinConnectedBySet(pixs, L_THIN_BG, sela, 5);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 9 */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ selaDestroy(&sela);
+
+ sela = selaMakeThinSets(11, 0);
+ pix1 = pixThinConnectedBySet(pixs, L_THIN_BG, sela, 5);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10 */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ selaDestroy(&sela);
+
+ /* Display the thinning results */
+ pix1 = pixaDisplayTiledAndScaled(pixa1, 8, 500, 1, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 11 */
+ if (rp->display) {
+ lept_mkdir("lept/thin");
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ lept_stderr("Writing to: /tmp/lept/thin/ccthin2-1.pdf");
+ pixaConvertToPdf(pixa1, 0, 1.0, 0, 0, "Thin 2 Results",
+ "/tmp/lept/thin/ccthin2-1.pdf");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pixs);
+ pixaDestroy(&pixa1);
+
+ /* Show thinning using width normalization */
+ paa = pixaaCreate(3);
+ pixa1 = l_bootnum_gen3();
+ pixa2 = pixaScaleToSize(pixa1, 0, 36);
+ pixaaAddPixa(paa, pixa2, L_INSERT);
+ pixa3 = pixaScaleToSizeRel(pixa2, -4, 0);
+ pixaaAddPixa(paa, pixa3, L_INSERT);
+ pixa3 = pixaScaleToSizeRel(pixa2, 4, 0);
+ pixaaAddPixa(paa, pixa3, L_INSERT);
+ pixa5 = pixaCreate(6);
+ for (i = 0; i < 3; i++) {
+ pixa3 = pixaaGetPixa(paa, i, L_CLONE);
+ pix1 = pixaDisplayTiledInColumns(pixa3, 15, 1.0, 10, 1);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 12, 14, 16 */
+ pixaAddPix(pixa5, pix1, L_INSERT);
+ pixa4 = pixaSetStrokeWidth(pixa3, 5, 1, 8);
+/* pixa4 = pixaSetStrokeWidth(pixa3, 1, 1, 8); */
+ pix1 = pixaDisplayTiledInColumns(pixa4, 15, 1.0, 10, 1);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 13, 15, 17 */
+ pixaAddPix(pixa5, pix1, L_INSERT);
+ pixaDestroy(&pixa3);
+ pixaDestroy(&pixa4);
+ }
+ pix1 = pixaDisplayTiledInColumns(pixa5, 2, 1.0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 18 */
+ if (rp->display) {
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ lept_stderr("Writing to: /tmp/lept/thin/ccthin2-2.pdf");
+ pixaConvertToPdf(pixa5, 0, 1.0, 0, 0, "Thin strokes",
+ "/tmp/lept/thin/ccthin2-2.pdf");
+ }
+ pixaaDestroy(&paa);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa5);
+ pixDestroy(&pix1);
+
+ return regTestCleanup(rp);
+}
+
+
diff --git a/leptonica/prog/char.tif b/leptonica/prog/char.tif
new file mode 100644
index 00000000..de2511d6
--- /dev/null
+++ b/leptonica/prog/char.tif
Binary files differ
diff --git a/leptonica/prog/chars-10.tif b/leptonica/prog/chars-10.tif
new file mode 100644
index 00000000..76a0855b
--- /dev/null
+++ b/leptonica/prog/chars-10.tif
Binary files differ
diff --git a/leptonica/prog/chars-12.tif b/leptonica/prog/chars-12.tif
new file mode 100644
index 00000000..db791955
--- /dev/null
+++ b/leptonica/prog/chars-12.tif
Binary files differ
diff --git a/leptonica/prog/chars-14.tif b/leptonica/prog/chars-14.tif
new file mode 100644
index 00000000..98508874
--- /dev/null
+++ b/leptonica/prog/chars-14.tif
Binary files differ
diff --git a/leptonica/prog/chars-16.tif b/leptonica/prog/chars-16.tif
new file mode 100644
index 00000000..2b8bbf75
--- /dev/null
+++ b/leptonica/prog/chars-16.tif
Binary files differ
diff --git a/leptonica/prog/chars-18.tif b/leptonica/prog/chars-18.tif
new file mode 100644
index 00000000..3c101bec
--- /dev/null
+++ b/leptonica/prog/chars-18.tif
Binary files differ
diff --git a/leptonica/prog/chars-20.tif b/leptonica/prog/chars-20.tif
new file mode 100644
index 00000000..fba7b5f2
--- /dev/null
+++ b/leptonica/prog/chars-20.tif
Binary files differ
diff --git a/leptonica/prog/chars-4.tif b/leptonica/prog/chars-4.tif
new file mode 100644
index 00000000..b535b368
--- /dev/null
+++ b/leptonica/prog/chars-4.tif
Binary files differ
diff --git a/leptonica/prog/chars-6.tif b/leptonica/prog/chars-6.tif
new file mode 100644
index 00000000..27679b40
--- /dev/null
+++ b/leptonica/prog/chars-6.tif
Binary files differ
diff --git a/leptonica/prog/chars-8.tif b/leptonica/prog/chars-8.tif
new file mode 100644
index 00000000..76e08b90
--- /dev/null
+++ b/leptonica/prog/chars-8.tif
Binary files differ
diff --git a/leptonica/prog/checkerboard1.tif b/leptonica/prog/checkerboard1.tif
new file mode 100644
index 00000000..64e0b545
--- /dev/null
+++ b/leptonica/prog/checkerboard1.tif
Binary files differ
diff --git a/leptonica/prog/checkerboard2.tif b/leptonica/prog/checkerboard2.tif
new file mode 100644
index 00000000..8d0cb9e2
--- /dev/null
+++ b/leptonica/prog/checkerboard2.tif
Binary files differ
diff --git a/leptonica/prog/checkerboard_reg.c b/leptonica/prog/checkerboard_reg.c
new file mode 100644
index 00000000..96e14666
--- /dev/null
+++ b/leptonica/prog/checkerboard_reg.c
@@ -0,0 +1,94 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * checkerboard_reg.c
+ *
+ * This tests the function that locates corners where four checkerboard
+ * squares are joined.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void LocateCheckerboardCorners(L_REGPARAMS *rp,
+ const char *fname,
+ l_int32 nsels);
+
+int main(int argc,
+ char **argv)
+{
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ LocateCheckerboardCorners(rp, "checkerboard1.tif", 2);
+ LocateCheckerboardCorners(rp, "checkerboard2.tif", 4);
+ return regTestCleanup(rp);
+}
+
+
+void
+LocateCheckerboardCorners(L_REGPARAMS *rp,
+ const char *fname,
+ l_int32 nsels)
+{
+l_int32 w, h, n, i;
+PIX *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa1;
+PTA *pta1;
+
+ pix1 = pixRead(fname);
+ pixa1 = pixaCreate(0);
+ pixFindCheckerboardCorners(pix1, 15, 3, nsels, &pix2, &pta1, pixa1);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 0, 3 */
+ pix3 = pixaDisplayTiledInColumns(pixa1, 1, 1.0, 20, 2);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 1, 4 */
+ pixDisplayWithTitle(pix3, 100 * (nsels - 2), 100, NULL, rp->display);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ pix4 = pixGenerateFromPta(pta1, w, h);
+ pixDilateBrick(pix4, pix4, 5, 5);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 2, 5 */
+ pixDestroy(&pix1);
+
+ if (rp->display) {
+ n = pixaGetCount(pixa1);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa1, i, L_CLONE);
+ pixDisplay(pix1, 350 + 200 * i, 300 * (nsels - 2));
+ pixDestroy(&pix1);
+ }
+ }
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixaDestroy(&pixa1);
+ ptaDestroy(&pta1);
+}
diff --git a/leptonica/prog/church.png b/leptonica/prog/church.png
new file mode 100644
index 00000000..ebe64c96
--- /dev/null
+++ b/leptonica/prog/church.png
Binary files differ
diff --git a/leptonica/prog/circle_reg.c b/leptonica/prog/circle_reg.c
new file mode 100644
index 00000000..ab5b7b0f
--- /dev/null
+++ b/leptonica/prog/circle_reg.c
@@ -0,0 +1,140 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * circle_reg.c
+ *
+ * Extract the digits from within a circle. In some cases the circle
+ * touches the digits, so this cannot be done by simply selecting
+ * connected components.
+ *
+ * Method:
+ * (1) Find a solid circle that covers the fg pixels.
+ * (2) Progressively erode the circle, computing the number of
+ * 8-connected components after each successive 3x3 erosion.
+ * (3) Stop when the minimum number of components is first reached,
+ * after passing the maximum number of components. Disregard the
+ * original image in the counting, because it can have noise.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 num_erodes = 8;
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, k, prevcount, count, nfiles, n, maxloc, maxval, minval;
+NUMA *na;
+PIX *pixs, *pixsi, *pixc, *pixoc, *pix1, *pix2, *pix3;
+PIXA *pixas, *pixa1, *pixa2;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "circle_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Read the images */
+ pixas = pixaRead("circles.pa");
+ n = pixaGetCount(pixas);
+
+ pixa2 = pixaCreate(0);
+ for (k = 0; k < n; k++) {
+ pixa1 = pixaCreate(0);
+ na = numaCreate(0);
+ pixs = pixaGetPix(pixas, k, L_COPY);
+ pixaAddPix(pixa1, pixs, L_INSERT);
+ pixsi = pixInvert(NULL, pixs);
+ pixc = pixCreateTemplate(pixs);
+ pixSetOrClearBorder(pixc, 1, 1, 1, 1, PIX_SET);
+ pixSeedfillBinary(pixc, pixc, pixsi, 4);
+ pixInvert(pixc, pixc);
+ pixoc = pixCopy(NULL, pixc); /* original circle */
+ pixaAddPix(pixa1, pixoc, L_INSERT);
+ pix1 = pixAnd(NULL, pixs, pixc);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixCountConnComp(pix1, 8, &count);
+ numaAddNumber(na, count);
+ if (rp->display) lept_stderr("count[0] = %d\n", count);
+ for (i = 1; i < num_erodes; i++) {
+ pixErodeBrick(pixc, pixc, 3, 3);
+ pix1 = pixAnd(NULL, pixs, pixc);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixCountConnComp(pix1, 8, &count);
+ numaAddNumber(na, count);
+ if (rp->display) lept_stderr("count[%d] = %d\n", i, count);
+ }
+
+ /* Find the max value, not including the original image, which
+ * may have noise. Then find the first occurrence of the
+ * min value that follows this max value. */
+ maxval = maxloc = 0;
+ for (i = 1; i < num_erodes; i++) { /* get max value starting at 1 */
+ numaGetIValue(na, i, &count);
+ if (count > maxval) {
+ maxval = count;
+ maxloc = i;
+ }
+ }
+ minval = 1000;
+ for (i = maxloc + 1; i < num_erodes; i++) { /* get the min value */
+ numaGetIValue(na, i, &count);
+ if (count < minval) minval = count;
+ }
+ for (i = maxloc + 1; i < num_erodes; i++) { /* get first occurrence */
+ numaGetIValue(na, i, &count);
+ if (count == minval) break;
+ }
+
+ pix1 = pixErodeBrick(NULL, pixoc, 2 * i + 1, 2 * i + 1);
+ pix2 = pixAnd(NULL, pixs, pix1);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pix3 = pixaDisplayTiledInColumns(pixa1, 11, 1.0, 10, 2);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG);
+ pixaAddPix(pixa2, pix3, L_INSERT);
+ pixaDestroy(&pixa1);
+ pixDestroy(&pix1);
+ pixDestroy(&pixsi);
+ pixDestroy(&pixc);
+ numaDestroy(&na);
+ }
+
+ pix1 = pixaDisplayTiledInColumns(pixa2, 1, 1.0, 10, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG);
+ pixDisplayWithTitle(pix1, 100, 100, NULL, rp->display);
+ pixaDestroy(&pixas);
+ pixaDestroy(&pixa2);
+ pixDestroy(&pix1);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/circles.pa b/leptonica/prog/circles.pa
new file mode 100644
index 00000000..c638ce20
--- /dev/null
+++ b/leptonica/prog/circles.pa
Binary files differ
diff --git a/leptonica/prog/cleanpdf.c b/leptonica/prog/cleanpdf.c
new file mode 100644
index 00000000..ae4ee093
--- /dev/null
+++ b/leptonica/prog/cleanpdf.c
@@ -0,0 +1,278 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * cleanpdf.c
+ *
+ * This program is intended to take as input pdf files that have
+ * been constructed from poorly compressed images -- typically images
+ * that have been scanned in grayscale or color but should be rendered
+ * in black and white (1 bpp). It cleans and compresses them, and
+ * generates a pdf composed of tiff-g4 compressed images.
+ *
+ * It will also take as input clean, orthographically-generated pdfs,
+ * and concatenate them into a single pdf file of images.
+ *
+ * Syntax: cleanpdf basedir threshold resolution outfile [rotation]
+ *
+ * The %basedir is a directory where the input pdf files are located.
+ * The program will operate on every file in this directory with
+ * the ".pdf" extension.
+ *
+ * The input binarization %threshold should be somewhere in the
+ * range [130 - 190]. The result is typically not very sensitive to
+ * the value, because internally we use a pixel mapping that is adapted
+ * to the local background before thresholding to binarize the image.
+ *
+ * The output %resolution parameter can take on two values:
+ * 300 (binarize at the same resolution as the gray or color input,
+ * which is typically 300 ppi)
+ * 600 (binarize at twice the resolution of the gray or color input,
+ * by doing an interpolated 2x expansion on the grayscale
+ * image, followed by thresholding to 1 bpp)
+ * At 300 ppi, an 8.5 x 11 page would have 2550 x 3300 pixels.
+ * You can also input 0 for the default output resolution of 300 ppi.
+ *
+ * The pdf output is written to %outfile. It is advisable (but not
+ * required) to have a '.pdf' extension.
+ *
+ * The optional %rotation is an integer:
+ * 0 no rotation
+ * 1 90 degrees cw
+ * 1 180 degrees cw
+ * 1 270 degrees cw
+ *
+ * Whenever possible, the images will be deskewed.
+ *
+ * Notes on using filenames with internal spaces.
+ * * The file-handling functions in leptonica do not support filenames
+ * that have spaces. To use cleanpdf in linux with such input
+ * filenames, substitute an ascii character for the spaces; e.g., '^'.
+ * char *newstr = stringReplaceEachSubstr(str, " ", "^", NULL);
+ * Then run cleanpdf on the file(s).
+ * * To get an output filename with spaces, use single quotes; e.g.,
+ * cleanpdf dir thresh res 'filename with spaces'
+ *
+ * N.B. This requires the Poppler package of pdf utilities, such as
+ * pdfimages and pdftoppm. For non-unix systems, this requires
+ * installation of the cygwin Poppler package:
+ * https://cygwin.com/cgi-bin2/package-cat.cgi?file=x86/poppler/
+ * poppler-0.26.5-1
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#ifdef _WIN32
+# if defined(_MSC_VER) || defined(__MINGW32__)
+# include <direct.h>
+# else
+# include <io.h>
+# endif /* _MSC_VER || __MINGW32__ */
+#endif /* _WIN32 */
+
+ /* Set to 1 to use pdftoppm; 0 for pdfimages */
+#define USE_PDFTOPPM 1
+
+#include "string.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "allheaders.h"
+
+ /* Special version */
+PIX *pixConvertTo8Special(PIX *pix);
+
+l_int32 main(int argc,
+ char **argv)
+{
+char buf[256];
+char *basedir, *fname, *tail, *basename, *imagedir, *outfile, *firstpath;
+l_int32 thresh, res, rotation, i, n, ret;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5;
+SARRAY *sa;
+static char mainName[] = "cleanpdf";
+
+ if (argc != 5 && argc != 6)
+ return ERROR_INT(
+ "Syntax: cleanpdf basedir threshold resolution outfile [rotation]",
+ mainName, 1);
+ basedir = argv[1];
+ thresh = atoi(argv[2]);
+ res = atoi(argv[3]);
+ outfile = argv[4];
+ if (argc == 6)
+ rotation = atoi(argv[5]);
+ else
+ rotation = 0;
+ if (rotation < 0 || rotation > 3) {
+ L_ERROR("rotation not in valid set {0,1,2,3}; setting to 0", mainName);
+ rotation = 0;
+ }
+ if (res == 0)
+ res = 300;
+ if (res != 300 && res != 600) {
+ L_ERROR("invalid res = %d; res must be in {0, 300, 600}\n",
+ mainName, res);
+ return 1;
+ }
+ setLeptDebugOK(1);
+
+#if 1
+ /* Get the names of the pdf files */
+ if ((sa = getSortedPathnamesInDirectory(basedir, "pdf", 0, 0)) == NULL)
+ return ERROR_INT("files not found", mainName, 1);
+ sarrayWriteStream(stderr, sa);
+ n = sarrayGetCount(sa);
+#endif
+
+ /* Rasterize: use either
+ * pdftoppm -r 300 fname outroot (-r 300 renders output at 300 ppi)
+ * or
+ * pdfimages -j fname outroot (-j outputs jpeg if input is dct)
+ * Use of pdftoppm:
+ * This works on all pdf pages, both wrapped images and pages that
+ * were made orthographically. The default output resolution for
+ * pdftoppm is 150 ppi, but we use 300 ppi. This makes large
+ * uncompressed files (e.g., a standard size RGB page image at 300
+ * ppi is 25 MB), but it is very fast. This is now preferred over
+ * using pdfimages.
+ * Use of pdfimages:
+ * This only works when all pages are pdf wrappers around images.
+ * In some cases, it scrambles the order of the output pages
+ * and inserts extra images. */
+ imagedir = stringJoin(basedir, "/image");
+#if 1
+#ifndef _WIN32
+ mkdir(imagedir, 0777);
+#else
+ _mkdir(imagedir);
+#endif /* _WIN32 */
+ for (i = 0; i < n; i++) {
+ fname = sarrayGetString(sa, i, L_NOCOPY);
+ splitPathAtDirectory(fname, NULL, &tail);
+ splitPathAtExtension(tail, &basename, NULL);
+ #if USE_PDFTOPPM
+ snprintf(buf, sizeof(buf), "pdftoppm -r 300 %s %s/%s",
+ fname, imagedir, basename);
+ #else
+ snprintf(buf, sizeof(buf), "pdfimages -j %s %s/%s",
+ fname, imagedir, basename);
+ #endif /* USE_PDFTOPPM */
+ lept_free(tail);
+ lept_free(basename);
+ fprintf(stderr, "%s\n", buf);
+ ret = system(buf); /* pdfimages or pdftoppm */
+ }
+ sarrayDestroy(&sa);
+#endif
+
+#if 1
+ /* Clean, deskew and compress */
+ sa = getSortedPathnamesInDirectory(imagedir, NULL, 0, 0);
+ sarrayWriteStream(stderr, sa);
+ n = sarrayGetCount(sa);
+ firstpath = NULL;
+ for (i = 0; i < n; i++) {
+ fname = sarrayGetString(sa, i, L_NOCOPY);
+ pixs = pixRead(fname);
+ pix1 = pixConvertTo8Special(pixs);
+ if (rotation > 0)
+ pix2 = pixRotateOrth(pix1, rotation);
+ else
+ pix2 = pixClone(pix1);
+ pix3 = pixFindSkewAndDeskew(pix2, 2, NULL, NULL);
+ pix4 = pixBackgroundNormSimple(pix3, NULL, NULL);
+ pixGammaTRC(pix4, pix4, 2.0, 50, 250);
+ if (res == 300)
+ pix5 = pixThresholdToBinary(pix4, thresh);
+ else /* res == 600 */
+ pix5 = pixScaleGray2xLIThresh(pix4, thresh);
+ splitPathAtDirectory(fname, NULL, &tail);
+ splitPathAtExtension(tail, &basename, NULL);
+ snprintf(buf, sizeof(buf), "%s/%s.tif", imagedir, basename);
+ fprintf(stderr, "%s\n", buf);
+ pixWrite(buf, pix5, IFF_TIFF_G4);
+ if (i == 0) /* save full path to first image */
+ firstpath = stringNew(buf);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ lept_free(tail);
+ lept_free(basename);
+ }
+ sarrayDestroy(&sa);
+#endif
+
+#if 1
+ /* Generate the pdf. Compute the actual input resolution from
+ * the pixel dimensions of the first image. This will cause each
+ * page to be printed to cover an 8.5 x 11 inch sheet of paper.
+ * We use flate encoding to avoid photometric reversal which
+ * happens when encoded with G4 tiff. */
+ fprintf(stderr, "Write output to %s\n", outfile);
+ pix1 = pixRead(firstpath);
+ pixInferResolution(pix1, 11.0, &res);
+ pixDestroy(&pix1);
+ lept_free(firstpath);
+ convertFilesToPdf(imagedir, "tif", res, 1.0, L_G4_ENCODE,
+ 0, NULL, outfile);
+#endif
+
+ return 0;
+}
+
+
+ /* A special version of pixConvertTo8() that returns an image without
+ * a colormap and uses pixConvertRGBToGrayMinMax() to strongly
+ * render color into black. */
+PIX *
+pixConvertTo8Special(PIX *pixs)
+{
+ l_int32 d = pixGetDepth(pixs);
+ if (d == 1) {
+ return pixConvert1To8(NULL, pixs, 255, 0);
+ } else if (d == 2) {
+ return pixConvert2To8(pixs, 0, 85, 170, 255, FALSE);
+ } else if (d == 4) {
+ return pixConvert4To8(pixs, FALSE);
+ } else if (d == 8) {
+ if (pixGetColormap(pixs) != NULL)
+ return pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+ else
+ return pixCopy(NULL, pixs);
+ } else if (d == 16) {
+ return pixConvert16To8(pixs, L_MS_BYTE);
+ } else if (d == 32) {
+ return pixConvertRGBToGrayMinMax(pixs, L_CHOOSE_MIN);
+ }
+
+ L_ERROR("Invalid depth d = %d\n", "pixConvertSpecialTo8", d);
+ return NULL;
+}
diff --git a/leptonica/prog/cmapquant_reg.c b/leptonica/prog/cmapquant_reg.c
new file mode 100644
index 00000000..54995d92
--- /dev/null
+++ b/leptonica/prog/cmapquant_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * cmapquant_reg.c
+ *
+ * Tests quantization of rgb image to a specific colormap.
+ * Does this by starting with a grayscale image, doing a grayscale
+ * quantization with a colormap in the dest, then adding new
+ * colors, scaling (which removes the colormap), and finally
+ * re-quantizing back to the original colormap.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define LEVEL 3
+#define MIN_DEPTH 4
+
+int main(int argc,
+ char **argv)
+{
+BOX *box;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
+PIX *pix8, *pix9;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("lucasta-frag.jpg");
+
+ /* Convert to 4 bpp with 6 levels and a colormap */
+ pix1 = pixThresholdTo4bpp(pixs, 6, 1);
+
+ /* Color some non-white pixels, preserving antialiasing, and
+ * adding these colors to the colormap */
+ box = boxCreate(120, 30, 200, 200);
+ pixColorGray(pix1, box, L_PAINT_DARK, 220, 0, 0, 255);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ boxDestroy(&box);
+
+ /* Scale up by 1.5; losing the colormap */
+ pix2 = pixScale(pix1, 1.5, 1.5);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 1 */
+ pixDisplayWithTitle(pix2, 0, 0, NULL, rp->display);
+
+ /* Octcube quantize using the same colormap */
+ startTimer();
+ cmap = pixGetColormap(pix1);
+ pix3 = pixOctcubeQuantFromCmap(pix2, cmap, MIN_DEPTH,
+ LEVEL, L_EUCLIDEAN_DISTANCE);
+ lept_stderr("Time to re-quantize to cmap = %7.3f sec\n", stopTimer());
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix3, 0, 0, NULL, rp->display);
+
+ /* Convert the quantized image to rgb */
+ pix4 = pixConvertTo32(pix3);
+
+ /* Re-quantize using median cut */
+ pix5 = pixMedianCutQuant(pix4, 0);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pix5, 0, 0, NULL, rp->display);
+
+ /* Re-quantize to few colors using median cut */
+ pix6 = pixFewColorsMedianCutQuantMixed(pix4, 30, 30, 100, 0, 0, 0);
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix6, 0, 0, NULL, rp->display);
+
+ /* Octcube quantize mixed with gray */
+ startTimer();
+ pix7 = pixOctcubeQuantMixedWithGray(pix2, 4, 5, 5);
+ lept_stderr("Time to re-quantize mixed = %7.3f sec\n", stopTimer());
+ regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix7, 0, 0, NULL, rp->display);
+
+ /* Fixed octcube quantization */
+ startTimer();
+ pix8 = pixFixedOctcubeQuant256(pix2, 0);
+ lept_stderr("Time to re-quantize 256 = %7.3f sec\n", stopTimer());
+ regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pix8, 0, 0, NULL, rp->display);
+
+ /* Remove unused colors */
+ startTimer();
+ pix9 = pixCopy(NULL, pix8);
+ pixRemoveUnusedColors(pix9);
+ lept_stderr("Time to remove unused colors = %7.3f sec\n", stopTimer());
+ regTestWritePixAndCheck(rp, pix9, IFF_PNG); /* 7 */
+ pixDisplayWithTitle(pix8, 0, 0, NULL, rp->display);
+
+ /* Compare before and after colors removed */
+ regTestComparePix(rp, pix8, pix9); /* 8 */
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ pixDestroy(&pix9);
+ return regTestCleanup(rp);
+}
+
+
diff --git a/leptonica/prog/coffeebeans.png b/leptonica/prog/coffeebeans.png
new file mode 100644
index 00000000..489b7b01
--- /dev/null
+++ b/leptonica/prog/coffeebeans.png
Binary files differ
diff --git a/leptonica/prog/color-wheel-hue.jpg b/leptonica/prog/color-wheel-hue.jpg
new file mode 100644
index 00000000..e00bbf6a
--- /dev/null
+++ b/leptonica/prog/color-wheel-hue.jpg
Binary files differ
diff --git a/leptonica/prog/colorcontent_reg.c b/leptonica/prog/colorcontent_reg.c
new file mode 100644
index 00000000..d5067d81
--- /dev/null
+++ b/leptonica/prog/colorcontent_reg.c
@@ -0,0 +1,170 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * This tests various color content functions, including a simple
+ * color quantization method.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+l_int32 main(int argc,
+ char **argv)
+{
+//char *fname[64];
+char fname[] = "/tmp/lept/colorcontent/maskgen.pdf";
+l_uint32 *colors;
+l_int32 ncolors, w, h;
+l_float32 fcolor;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8;
+PIXA *pixa1;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "colorcontent_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Find the most populated colors */
+ pix1 = pixRead("fish24.jpg");
+ pixGetMostPopulatedColors(pix1, 2, 3, 10, &colors, NULL);
+ pix2 = pixDisplayColorArray(colors, 10, 200, 5, 6);
+ pixDisplayWithTitle(pix2, 0, 0, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 0 */
+ lept_free(colors);
+ pixDestroy(&pix2);
+
+ /* Do a simple color quantization with sigbits = 2 */
+ pix2 = pixSimpleColorQuantize(pix1, 2, 3, 10);
+ pixDisplayWithTitle(pix2, 0, 400, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+ pix3 = pixRemoveColormap(pix2, REMOVE_CMAP_TO_FULL_COLOR);
+ regTestComparePix(rp, pix2, pix3); /* 2 */
+ pixNumColors(pix3, 1, &ncolors);
+ regTestCompareValues(rp, ncolors, 10, 0.0); /* 3 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Do a simple color quantization with sigbits = 3 */
+ pix1 = pixRead("wyom.jpg");
+ pixNumColors(pix1, 1, &ncolors); /* >255, so should give 0 */
+ regTestCompareValues(rp, ncolors, 132165, 0.0); /* 4 */
+ pix2 = pixSimpleColorQuantize(pix1, 3, 3, 20);
+ pixDisplayWithTitle(pix2, 1000, 0, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 5 */
+ ncolors = pixcmapGetCount(pixGetColormap(pix2));
+ regTestCompareValues(rp, ncolors, 20, 0.0); /* 6 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Find the number of perceptually significant gray intensities */
+ pix1 = pixRead("marge.jpg");
+ pix2 = pixConvertTo8(pix1, 0);
+ pixNumSignificantGrayColors(pix2, 20, 236, 0.0001, 1, &ncolors);
+ regTestCompareValues(rp, ncolors, 219, 0.0); /* 7 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Find background color in image with light color regions */
+ pix1 = pixRead("map.057.jpg");
+ pixa1 = pixaCreate(0);
+ pixFindColorRegions(pix1, NULL, 4, 200, 70, 10, 90, 0.05,
+ &fcolor, &pix2, NULL, pixa1);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 8 */
+ pix3 = pixaDisplayTiledInColumns(pixa1, 5, 0.3, 20, 2);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 9 */
+ pixDisplayWithTitle(pix3, 1000, 500, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa1);
+
+ /* Show binary classification of RGB colors using a plane */
+ pix1 = pixMakeGamutRGB(3);
+ pix2 = pixMakeArbMaskFromRGB(pix1, -0.5, -0.5, 1.0, 20);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ pix3 = pixCreate(w, h, 32);
+ pixSetAll(pix3);
+ pixCombineMasked(pix3, pix1, pix2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 11 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 12 */
+ pixDisplayWithTitle(pix3, 0, 1300, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Show use of more than one plane to further restrict the
+ allowed region of RGB color space */
+ pixa1 = pixaCreate(0);
+ pix1 = pixMakeGamutRGB(3);
+ pix2 = pixMakeArbMaskFromRGB(pix1, -0.5, -0.5, 1.0, 20);
+ pix3 = pixMakeArbMaskFromRGB(pix1, 1.5, -0.5, -1.0, 0);
+ pix4 = pixMakeArbMaskFromRGB(pix1, 0.4, 0.3, 0.3, 60);
+ pixInvert(pix4, pix4);
+ pix5 = pixSubtract(NULL, pix2, pix3);
+ pix6 = pixSubtract(NULL, pix5, pix4);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ pix7 = pixCreate(w, h, 32);
+ pixSetAll(pix7);
+ pixCombineMasked(pix7, pix1, pix6);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 13 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 14 */
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 15 */
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 16 */
+ regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 17 */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ pixaAddPix(pixa1, pix4, L_INSERT);
+ pixaAddPix(pixa1, pix5, L_INSERT);
+ pixaAddPix(pixa1, pix6, L_INSERT);
+ pixaAddPix(pixa1, pix7, L_INSERT);
+ lept_mkdir("lept/colorcontent");
+ l_pdfSetDateAndVersion(FALSE);
+ pixaConvertToPdf(pixa1, 0, 0.5, L_FLATE_ENCODE, 0, NULL, fname);
+ regTestCheckFile(rp, fname); /* 18 */
+ lept_stderr("Wrote %s\n", fname);
+ if (rp->display) {
+ pix8 = pixaDisplayTiledInColumns(pixa1, 2, 0.5, 15, 2);
+ pixDisplay(pix8, 800, 1300);
+ pixDestroy(&pix8);
+ }
+ pixaDestroy(&pixa1);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/colorfill_reg.c b/leptonica/prog/colorfill_reg.c
new file mode 100644
index 00000000..3f70ce69
--- /dev/null
+++ b/leptonica/prog/colorfill_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * colorfill_reg.c
+ *
+ * This tests the utility that does color segmentation by region growing.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static PIX *makeSmallTestPix(l_uint32 c1, l_uint32 c2);
+
+
+int main(int argc,
+ char **argv)
+{
+L_COLORFILL *cf;
+PIX *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa1, *pixa2;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Test on a small image */
+ pix1 = makeSmallTestPix(0x3070A000, 0xA0703000);
+ pix2 = pixExpandReplicate(pix1, 15);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix2, 0, 0, NULL, rp->display);
+ pixDestroy(&pix2);
+ cf = l_colorfillCreate(pix1, 1, 1);
+ pixColorContentByLocation(cf, 0, 0, 0, 70, 15, 3, 1, 1);
+ pix2 = pixaDisplayTiledInColumns(cf->pixadb, cf->nx, 1.0, 10, 1);
+ pix3 = pixExpandReplicate(pix2, 10);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix3, 300, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ l_colorfillDestroy(&cf);
+
+ /* Test on simple random image with many colors (1 tile and 4 tiles */
+ pixa1 = makeColorfillTestData(350, 350, 35, 100);
+ pixa2 = pixaCreate(5);
+ pix1 = pixaGetPix(pixa1, 0, L_COPY);
+ pix2 = pixConvertTo32(pix1);
+ pixDestroy(&pix1);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 2 */
+ pixaAddPix(pixa2, pix2, L_INSERT);
+ cf = l_colorfillCreate(pix2, 1, 1); /* 1 tile */
+ pixColorContentByLocation(cf, 0, 0, 0, 70, 30, 500, 1, 1);
+ pix3 = pixaDisplayTiledInColumns(cf->pixam, cf->nx, 1.0, 10, 1);
+ pix4 = pixaDisplayTiledInColumns(cf->pixadb, cf->nx, 1.0, 10, 1);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 4 */
+ pixaAddPix(pixa2, pix3, L_INSERT);
+ pixaAddPix(pixa2, pix4, L_INSERT);
+ l_colorfillDestroy(&cf);
+
+ cf = l_colorfillCreate(pix2, 2, 2); /* 4 tiles */
+ pixColorContentByLocation(cf, 0, 0, 0, 70, 30, 500, 1, 1);
+ pix3 = pixaDisplayTiledInColumns(cf->pixam, cf->nx, 1.0, 10, 1);
+ pix4 = pixaDisplayTiledInColumns(cf->pixadb, cf->nx, 1.0, 10, 1);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 5 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 6 */
+ pixaAddPix(pixa2, pix3, L_INSERT);
+ pixaAddPix(pixa2, pix4, L_INSERT);
+ if (rp->display) {
+ pix1 = pixaDisplayTiledInColumns(pixa2, 5, 1.0, 15, 2);
+ pixDisplay(pix1, 0, 400);
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ l_colorfillDestroy(&cf);
+
+ /* Test on an image with lots of color (with 1 tile and 9 tiles) */
+ pix1 = pixRead("lyra.005.jpg");
+ pix2 = pixScale(pix1, 0.5, 0.5);
+ pixDestroy(&pix1);
+ cf = l_colorfillCreate(pix2, 1, 1); /* 1 tile */
+ pixColorContentByLocation(cf, 0, 0, 0, 70, 30, 100, 1, 1);
+ pix3 = pixaDisplayTiledInColumns(cf->pixam, cf->nx, 1.0, 10, 1);
+ pix4 = pixaDisplayTiledInColumns(cf->pixadb, cf->nx, 1.0, 10, 1);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 7 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 8 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 9 */
+ pixa2 = pixaCreate(3);
+ pixaAddPix(pixa2, pix2, L_COPY);
+ pixaAddPix(pixa2, pix3, L_INSERT);
+ pixaAddPix(pixa2, pix4, L_INSERT);
+ if (rp->display) {
+ pix1 = pixaDisplayTiledInColumns(pixa2, 3, 0.8, 15, 2);
+ pixDisplay(pix1, 0, 650);
+ pixDestroy(&pix1);
+ }
+ l_colorfillDestroy(&cf);
+ pixaDestroy(&pixa2);
+
+ cf = l_colorfillCreate(pix2, 3, 3); /* 9 tiles */
+ pixColorContentByLocation(cf, 0, 0, 0, 70, 30, 100, 1, 1);
+ pix3 = pixaDisplayTiledInColumns(cf->pixam, cf->nx, 1.0, 10, 1);
+ pix4 = pixaDisplayTiledInColumns(cf->pixadb, cf->nx, 1.0, 10, 1);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 10 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 11 */
+ pixa2 = pixaCreate(2);
+ pixaAddPix(pixa2, pix3, L_INSERT);
+ pixaAddPix(pixa2, pix4, L_INSERT);
+ if (rp->display) {
+ pix1 = pixaDisplayTiledInColumns(pixa2, 2, 0.8, 15, 2);
+ pixDisplay(pix1, 0, 1000);
+ pixDestroy(&pix1);
+ }
+ l_colorfillDestroy(&cf);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa2);
+
+ return regTestCleanup(rp);
+}
+
+static PIX *makeSmallTestPix(l_uint32 c1, l_uint32 c2)
+{
+l_int32 i, j;
+PIX *pix1;
+
+ pix1 = pixCreate(17, 17, 32);
+ pixSetAllArbitrary(pix1, c1);
+ for (i = 0; i < 15; i++) {
+ for (j = 0; j < i; j++)
+ pixSetPixel(pix1, j, i, c2);
+ }
+ for (i = 0; i < 15; i++) {
+ for (j = 17 - i; j < 17; j++)
+ pixSetPixel(pix1, j, i, c2);
+ }
+ for (i = 9; i < 17; i++)
+ pixSetPixel(pix1, 8, i, c1);
+ return pix1;
+}
diff --git a/leptonica/prog/coloring_reg.c b/leptonica/prog/coloring_reg.c
new file mode 100644
index 00000000..f9c722a3
--- /dev/null
+++ b/leptonica/prog/coloring_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * coloring_reg.c
+ *
+ * This tests simple coloring functions.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+static const char *bgcolors[] = {"255 255 235",
+ "255 245 235",
+ "255 235 245",
+ "235 245 255"};
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+char buf[512];
+l_int32 i, n, index;
+l_int32 rval[4], gval[4], bval[4];
+l_uint32 scolor, dcolor;
+L_BMF *bmf;
+PIX *pix0, *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXA *pixa;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "coloring_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Read in the bg colors */
+ for (i = 0; i < 4; i++)
+ sscanf(bgcolors[i], "%d %d %d", &rval[i], &gval[i], &bval[i]);
+ bmf = bmfCreate("fonts", 8);
+
+ /* Get the input image (100 ppi resolution) */
+ pix0 = pixRead("harmoniam100-11.png");
+ cmap = pixGetColormap(pix0);
+ pixa = pixaCreate(0);
+
+ /* Do cmapped coloring on the white pixels only */
+ pixcmapGetIndex(cmap, 255, 255, 255, &index); /* index of white pixels */
+ for (i = 0; i < 4; i++) {
+ pixcmapResetColor(cmap, index, rval[i], gval[i], bval[i]);
+ snprintf(buf, sizeof(buf), "(rval, bval, gval) = (%d, %d, %d)",
+ rval[i], gval[i], bval[i]);
+ pix1 = pixAddSingleTextblock(pix0, bmf, buf, 0xff000000,
+ L_ADD_AT_BOT, NULL);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ }
+
+ /* Do cmapped background coloring on all the pixels */
+ for (i = 0; i < 4; i++) {
+ scolor = 0xffffff00; /* source color */
+ composeRGBPixel(rval[i], gval[i], bval[i], &dcolor); /* dest color */
+ pix1 = pixShiftByComponent(NULL, pix0, scolor, dcolor);
+ snprintf(buf, sizeof(buf), "(rval, bval, gval) = (%d, %d, %d)",
+ rval[i], gval[i], bval[i]);
+ pix2 = pixAddSingleTextblock(pix1, bmf, buf, 0xff000000,
+ L_ADD_AT_BOT, NULL);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+
+ /* Do background coloring on rgb */
+ pix1 = pixConvertTo32(pix0);
+ for (i = 0; i < 4; i++) {
+ scolor = 0xffffff00;
+ composeRGBPixel(rval[i], gval[i], bval[i], &dcolor);
+ pix2 = pixShiftByComponent(NULL, pix1, scolor, dcolor);
+ snprintf(buf, sizeof(buf), "(rval, bval, gval) = (%d, %d, %d)",
+ rval[i], gval[i], bval[i]);
+ pix3 = pixAddSingleTextblock(pix2, bmf, buf, 0xff000000,
+ L_ADD_AT_BOT, NULL);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+
+ /* Compare cmapped & rgb foreground coloring */
+ scolor = 0x0; /* source color */
+ composeRGBPixel(200, 30, 150, &dcolor); /* ugly fg dest color */
+ pix1 = pixShiftByComponent(NULL, pix0, scolor, dcolor); /* cmapped */
+ snprintf(buf, sizeof(buf), "(rval, bval, gval) = (%d, %d, %d)",
+ 200, 100, 50);
+ pix2 = pixAddSingleTextblock(pix1, bmf, buf, 0xff000000,
+ L_ADD_AT_BOT, NULL);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix3 = pixConvertTo32(pix0);
+ pix4 = pixShiftByComponent(NULL, pix3, scolor, dcolor); /* rgb */
+ snprintf(buf, sizeof(buf), "(rval, bval, gval) = (%d, %d, %d)",
+ 200, 100, 50);
+ pix5 = pixAddSingleTextblock(pix4, bmf, buf, 0xff000000,
+ L_ADD_AT_BOT, NULL);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestComparePix(rp, pix1, pix4);
+ regTestComparePix(rp, pix2, pix5);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Log all the results */
+ n = pixaGetCount(pixa);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa, i, L_CLONE);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ }
+
+ /* If in testing mode, make a pdf */
+ if (rp->display) {
+ pixaConvertToPdf(pixa, 100, 1.0, L_FLATE_ENCODE, 0,
+ "Colored background", "/tmp/lept/regout/coloring.pdf");
+ L_INFO("Output pdf: /tmp/lept/regout/coloring.pdf\n", rp->testname);
+ }
+
+ pixaDestroy(&pixa);
+ pixDestroy(&pix0);
+ bmfDestroy(&bmf);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/colorize_reg.c b/leptonica/prog/colorize_reg.c
new file mode 100644
index 00000000..c885eafc
--- /dev/null
+++ b/leptonica/prog/colorize_reg.c
@@ -0,0 +1,290 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * colorize_reg.c
+ *
+ * This regresstion test demonstrates the detection of red highlight
+ * color in an image, and the generation of a colormapped version
+ * with clean background and colorized highlighting.
+ *
+ * The input image is rgb. Other examples are breviar.32 and amoris.2.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+PIX *TestForRedColor(L_REGPARAMS *rp, const char *fname,
+ l_float32 gold_red, L_BMF *bmf);
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 irval, igval, ibval;
+l_float32 rval, gval, bval, fract, fgfract;
+L_BMF *bmf;
+BOX *box;
+BOXA *boxa;
+FPIX *fpix;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
+PIX *pix8, *pix9, *pix10, *pix11, *pix12, *pix13, *pix14, *pix15;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "colorize_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixa = pixaCreate(0);
+ pixs = pixRead("breviar.38.150.jpg");
+ pixaAddPix(pixa, pixs, L_CLONE);
+ regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 0 */
+ pixDisplayWithTitle(pixs, 0, 0, "Input image", rp->display);
+
+ /* Extract the blue component, which is small in all the text
+ * regions, including in the highlight color region */
+ pix1 = pixGetRGBComponent(pixs, COLOR_BLUE);
+ pixaAddPix(pixa, pix1, L_CLONE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 1 */
+ pixDisplayWithTitle(pix1, 200, 0, "Blue component", rp->display);
+
+ /* Do a background normalization, with the background set to
+ * approximately 200 */
+ pix2 = pixBackgroundNormSimple(pix1, NULL, NULL);
+ pixaAddPix(pixa, pix2, L_COPY);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 2 */
+ pixDisplayWithTitle(pix2, 400, 0, "BG normalized to 200", rp->display);
+
+ /* Do a linear transform on the gray pixels, with 50 going to
+ * black and 160 going to white. 50 is sufficiently low to
+ * make both the red and black print quite dark. Quantize
+ * to a few equally spaced gray levels. This is the image
+ * to which highlight color will be applied. */
+ pixGammaTRC(pix2, pix2, 1.0, 50, 160);
+ pix3 = pixThresholdOn8bpp(pix2, 7, 1);
+ pixaAddPix(pixa, pix3, L_CLONE);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 3 */
+ pixDisplayWithTitle(pix3, 600, 0, "Basic quantized with white bg",
+ rp->display);
+
+ /* Identify the regions of red text. First, make a mask
+ * consisting of all pixels such that (R-B)/B is larger
+ * than 2.0. This will have all the red, plus a lot of
+ * the dark pixels. */
+ fpix = pixComponentFunction(pixs, 1.0, 0.0, -1.0, 0.0, 0.0, 1.0);
+ pix4 = fpixThresholdToPix(fpix, 2.0);
+ pixInvert(pix4, pix4); /* red plus some dark text */
+ pixaAddPix(pixa, pix4, L_CLONE);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix4, 800, 0, "Red plus dark pixels", rp->display);
+
+ /* Make a mask consisting of all the red and background pixels */
+ pix5 = pixGetRGBComponent(pixs, COLOR_RED);
+ pix6 = pixThresholdToBinary(pix5, 128);
+ pixInvert(pix6, pix6); /* red plus background (white) */
+
+ /* Intersect the two masks to get a mask consisting of pixels
+ * that are almost certainly red. This is the seed. */
+ pix7 = pixAnd(NULL, pix4, pix6); /* red only (seed) */
+ pixaAddPix(pixa, pix7, L_COPY);
+ regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix7, 0, 600, "Seed for red color", rp->display);
+
+ /* Make the clipping mask by thresholding the image with
+ * the background cleaned to white. */
+ pix8 = pixThresholdToBinary(pix2, 230); /* mask */
+ pixaAddPix(pixa, pix8, L_CLONE);
+ regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pix8, 200, 600, "Clipping mask for red components",
+ rp->display);
+
+ /* Fill into the mask from the seed */
+ pixSeedfillBinary(pix7, pix7, pix8, 8); /* filled: red plus touching */
+ regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 7 */
+ pixDisplayWithTitle(pix7, 400, 600, "Red component mask filled",
+ rp->display);
+
+ /* Small closing on regions to be colored */
+ pix9 = pixMorphSequence(pix7, "c5.1", 0);
+ pixaAddPix(pixa, pix9, L_CLONE);
+ regTestWritePixAndCheck(rp, pix9, IFF_PNG); /* 8 */
+ pixDisplayWithTitle(pix9, 600, 600,
+ "Components defining regions allowing coloring",
+ rp->display);
+
+ /* Sanity check on amount to be colored. Only accept images
+ * with less than 10% of all the pixels with highlight color */
+ pixForegroundFraction(pix9, &fgfract);
+ if (fgfract >= 0.10) {
+ L_INFO("too much highlighting: fract = %6.3f; removing it\n",
+ rp->testname, fgfract);
+ pixClearAll(pix9);
+ pixSetPixel(pix9, 0, 0, 1);
+ }
+
+ /* Get a color to paint that is representative of the
+ * actual highlight color in the image. Scale each
+ * color component up from the average by an amount necessary
+ * to saturate the red. Then divide the green and
+ * blue components by 2.0. */
+ pixGetAverageMaskedRGB(pixs, pix7, 0, 0, 1, L_MEAN_ABSVAL,
+ &rval, &gval, &bval);
+ fract = 255.0 / rval;
+ irval = lept_roundftoi(fract * rval);
+ igval = lept_roundftoi(fract * gval / 2.0);
+ ibval = lept_roundftoi(fract * bval / 2.0);
+ lept_stderr("(r,g,b) = (%d,%d,%d)\n", irval, igval, ibval);
+
+ /* Test mask-based colorization on gray and cmapped gray */
+ pix10 = pixColorGrayMasked(pix2, pix9, L_PAINT_DARK, 225,
+ irval, igval, ibval);
+ pixaAddPix(pixa, pix10, L_CLONE);
+ regTestWritePixAndCheck(rp, pix10, IFF_PNG); /* 9 */
+ pixDisplayWithTitle(pix10, 800, 600, "Colorize mask gray", rp->display);
+ pixaAddPix(pixa, pixs, L_CLONE);
+
+ pix11 = pixColorGrayMasked(pix3, pix9, L_PAINT_DARK, 225,
+ irval, igval, ibval);
+ pixaAddPix(pixa, pix11, L_CLONE);
+ regTestWritePixAndCheck(rp, pix11, IFF_PNG); /* 10 */
+ pixDisplayWithTitle(pix11, 900, 600, "Colorize mask cmapped", rp->display);
+
+ /* Get the bounding boxes of the mask components to be colored */
+ boxa = pixConnCompBB(pix9, 8);
+
+ /* Test region colorization on gray and cmapped gray */
+ pix12 = pixColorGrayRegions(pix2, boxa, L_PAINT_DARK, 220, 0, 255, 0);
+ pixaAddPix(pixa, pix12, L_CLONE);
+ regTestWritePixAndCheck(rp, pix12, IFF_PNG); /* 11 */
+ pixDisplayWithTitle(pix12, 900, 600, "Colorize boxa gray", rp->display);
+
+ box = boxCreate(200, 200, 250, 350);
+ pix13 = pixCopy(NULL, pix2);
+ pixColorGray(pix13, box, L_PAINT_DARK, 220, 0, 0, 255);
+ pixaAddPix(pixa, pix13, L_CLONE);
+ regTestWritePixAndCheck(rp, pix13, IFF_PNG); /* 12 */
+ pixDisplayWithTitle(pix13, 1000, 600, "Colorize box gray", rp->display);
+
+ pix14 = pixThresholdTo4bpp(pix2, 6, 1);
+ pix15 = pixColorGrayRegions(pix14, boxa, L_PAINT_DARK, 220, 0, 0, 255);
+ pixaAddPix(pixa, pix15, L_CLONE);
+ regTestWritePixAndCheck(rp, pix15, IFF_PNG); /* 13 */
+ pixDisplayWithTitle(pix15, 1100, 600, "Colorize boxa cmap", rp->display);
+
+ pixColorGrayCmap(pix14, box, L_PAINT_DARK, 0, 255, 255);
+ pixaAddPix(pixa, pix14, L_CLONE);
+ regTestWritePixAndCheck(rp, pix14, IFF_PNG); /* 14 */
+ pixDisplayWithTitle(pix14, 1200, 600, "Colorize box cmap", rp->display);
+ boxDestroy(&box);
+
+ /* Generate a pdf of the intermediate results */
+ lept_mkdir("lept/color");
+ L_INFO("Writing to /tmp/lept/color/colorize.pdf\n", rp->testname);
+ pixaConvertToPdf(pixa, 90, 1.0, 0, 0, "Colorizing highlighted text",
+ "/tmp/lept/color/colorize.pdf");
+
+
+ pixaDestroy(&pixa);
+ fpixDestroy(&fpix);
+ boxDestroy(&box);
+ boxaDestroy(&boxa);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ pixDestroy(&pix9);
+ pixDestroy(&pix10);
+ pixDestroy(&pix11);
+ pixDestroy(&pix12);
+ pixDestroy(&pix13);
+ pixDestroy(&pix14);
+ pixDestroy(&pix15);
+
+ /* Test the color detector */
+ pixa = pixaCreate(7);
+ bmf = bmfCreate(NULL, 4);
+ pix1 = TestForRedColor(rp, "brev.06.75.jpg", 1, bmf); /* 14 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = TestForRedColor(rp, "brev.10.75.jpg", 0, bmf); /* 15 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = TestForRedColor(rp, "brev.14.75.jpg", 1, bmf); /* 16 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = TestForRedColor(rp, "brev.20.75.jpg", 1, bmf); /* 17 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = TestForRedColor(rp, "brev.36.75.jpg", 0, bmf); /* 18 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = TestForRedColor(rp, "brev.53.75.jpg", 1, bmf); /* 19 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = TestForRedColor(rp, "brev.56.75.jpg", 1, bmf); /* 20 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Generate a pdf of the color detector results */
+ L_INFO("Writing to /tmp/lept/color/colordetect.pdf\n", rp->testname);
+ pixaConvertToPdf(pixa, 45, 1.0, 0, 0, "Color detection",
+ "/tmp/lept/color/colordetect.pdf");
+ pixaDestroy(&pixa);
+ bmfDestroy(&bmf);
+
+ return regTestCleanup(rp);
+}
+
+
+PIX *
+TestForRedColor(L_REGPARAMS *rp,
+ const char *fname,
+ l_float32 gold_red,
+ L_BMF *bmf)
+{
+char text[64];
+PIX *pix1, *pix2;
+l_int32 hasred;
+l_float32 ratio;
+
+ pix1 = pixRead(fname);
+ pixHasHighlightRed(pix1, 1, 0.0001, 2.5, &hasred, &ratio, NULL);
+ regTestCompareValues(rp, gold_red, hasred, 0.0);
+ if (hasred)
+ snprintf(text, sizeof(text), "Has red: ratio = %6.1f", ratio);
+ else
+ snprintf(text, sizeof(text), "Does not have red: ratio = %6.1f", ratio);
+ pix2 = pixAddSingleTextblock(pix1, bmf, text, 0x0000ff00,
+ L_ADD_BELOW, NULL);
+ pixDestroy(&pix1);
+ return pix2;
+}
+
diff --git a/leptonica/prog/colormask_reg.c b/leptonica/prog/colormask_reg.c
new file mode 100644
index 00000000..091ced98
--- /dev/null
+++ b/leptonica/prog/colormask_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * colormask_reg.c
+ *
+ * This tests the ability to identify regions in HSV color space
+ * by analyzing the HS histogram and building masks that cover
+ * peaks in HS.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, x, y, rval, gval, bval;
+l_uint32 pixel1, pixel2;
+l_float32 frval, fgval, fbval;
+NUMA *nahue, *nasat, *napk;
+PIX *pixs, *pixhsv, *pixh, *pixg, *pixf, *pixd, *pixr;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa, *pixapk;
+PTA *ptapk;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "colormask_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Make a graded frame color */
+ pixs = pixCreate(650, 900, 32);
+ for (i = 0; i < 900; i++) {
+ rval = 40 + i / 30;
+ for (j = 0; j < 650; j++) {
+ gval = 255 - j / 30;
+ bval = 70 + j / 30;
+ composeRGBPixel(rval, gval, bval, &pixel1);
+ pixSetPixel(pixs, j, i, pixel1);
+ }
+ }
+
+ /* Place an image inside the frame and convert to HSV */
+ pix1 = pixRead("1555.003.jpg");
+ pix2 = pixScale(pix1, 0.5, 0.5);
+ pixRasterop(pixs, 100, 100, 2000, 2000, PIX_SRC, pix2, 0, 0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDisplayWithTitle(pixs, 400, 0, "Input image", rp->display);
+ pixa = pixaCreate(0);
+ pixhsv = pixConvertRGBToHSV(NULL, pixs);
+
+ /* Work in the HS projection of HSV */
+ pixh = pixMakeHistoHS(pixhsv, 5, &nahue, &nasat);
+ pixg = pixMaxDynamicRange(pixh, L_LOG_SCALE);
+ pixf = pixConvertGrayToFalseColor(pixg, 1.0);
+ regTestWritePixAndCheck(rp, pixf, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pixf, 100, 0, "False color HS histo", rp->display);
+ pixaAddPix(pixa, pixs, L_COPY);
+ pixaAddPix(pixa, pixhsv, L_INSERT);
+ pixaAddPix(pixa, pixg, L_INSERT);
+ pixaAddPix(pixa, pixf, L_INSERT);
+ gplotSimple1(nahue, GPLOT_PNG, "/tmp/lept/regout/junkhue",
+ "Histogram of hue values");
+ pix3 = pixRead("/tmp/lept/regout/junkhue.png");
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix3, 100, 300, "Histo of hue", rp->display);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ gplotSimple1(nasat, GPLOT_PNG, "/tmp/lept/regout/junksat",
+ "Histogram of saturation values");
+ pix3 = pixRead("/tmp/lept/regout/junksat.png");
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix3, 100, 800, "Histo of saturation", rp->display);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixd = pixaDisplayTiledAndScaled(pixa, 32, 270, 7, 0, 30, 3);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pixd, 0, 400, "Hue and Saturation Mosaic", rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ numaDestroy(&nahue);
+ numaDestroy(&nasat);
+
+ /* Find all the peaks */
+ pixFindHistoPeaksHSV(pixh, L_HS_HISTO, 20, 20, 6, 2.0,
+ &ptapk, &napk, &pixapk);
+ numaWriteStream(stderr, napk);
+ ptaWriteStream(stderr, ptapk, 1);
+ pixd = pixaDisplayTiledInRows(pixapk, 32, 1400, 1.0, 0, 30, 2);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pixd, 0, 550, "Peaks in HS", rp->display);
+ pixDestroy(&pixh);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixapk);
+
+ /* Make masks for each of the peaks */
+ pixa = pixaCreate(0);
+ pixr = pixScaleBySampling(pixs, 0.4, 0.4);
+ for (i = 0; i < 6; i++) {
+ ptaGetIPt(ptapk, i, &x, &y);
+ pix1 = pixMakeRangeMaskHS(pixr, y, 20, x, 20, L_INCLUDE_REGION);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixGetAverageMaskedRGB(pixr, pix1, 0, 0, 1, L_MEAN_ABSVAL,
+ &frval, &fgval, &fbval);
+ composeRGBPixel((l_int32)frval, (l_int32)fgval, (l_int32)fbval,
+ &pixel1);
+ pixGetPixelAverage(pixr, pix1, 0, 0, 1, &pixel2);
+ regTestCompareValues(rp, pixel1 >> 8, pixel2 >> 8, 0.0); /* 5 - 10 */
+ pix2 = pixCreateTemplate(pixr);
+ pixSetAll(pix2);
+ pixPaintThroughMask(pix2, pix1, 0, 0, pixel1);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix3 = pixCreateTemplate(pixr);
+ pixSetAllArbitrary(pix3, pixel1);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ }
+ pixd = pixaDisplayTiledAndScaled(pixa, 32, 225, 3, 0, 30, 3);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 8 */
+ pixDisplayWithTitle(pixd, 600, 0, "Masks over peaks", rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pixr);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ ptaDestroy(&ptapk);
+ numaDestroy(&napk);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/colormorph_reg.c b/leptonica/prog/colormorph_reg.c
new file mode 100644
index 00000000..56b80035
--- /dev/null
+++ b/leptonica/prog/colormorph_reg.c
@@ -0,0 +1,101 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Regression test for simple color morphological operations
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 SIZE = 7;
+
+int main(int argc,
+ char **argv)
+{
+char buf[256];
+PIX *pixs, *pix1, *pix2;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("wyom.jpg");
+ pixa = pixaCreate(0);
+
+ pix1 = pixColorMorph(pixs, L_MORPH_DILATE, SIZE, SIZE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 0 */
+ snprintf(buf, sizeof(buf), "d%d.%d", SIZE, SIZE);
+ pix2 = pixColorMorphSequence(pixs, buf, 0, 0);
+ regTestComparePix(rp, pix1, pix2); /* 1 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+
+ pix1 = pixColorMorph(pixs, L_MORPH_ERODE, SIZE, SIZE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 2 */
+ snprintf(buf, sizeof(buf), "e%d.%d", SIZE, SIZE);
+ pix2 = pixColorMorphSequence(pixs, buf, 0, 0);
+ regTestComparePix(rp, pix1, pix2); /* 3 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+
+ pix1 = pixColorMorph(pixs, L_MORPH_OPEN, SIZE, SIZE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 4 */
+ snprintf(buf, sizeof(buf), "o%d.%d", SIZE, SIZE);
+ pix2 = pixColorMorphSequence(pixs, buf, 0, 0);
+ regTestComparePix(rp, pix1, pix2); /* 5 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+
+ pix1 = pixColorMorph(pixs, L_MORPH_CLOSE, SIZE, SIZE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 6 */
+ snprintf(buf, sizeof(buf), "c%d.%d", SIZE, SIZE);
+ pix2 = pixColorMorphSequence(pixs, buf, 0, 0);
+ regTestComparePix(rp, pix1, pix2); /* 7 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+
+ if (rp->display) {
+ lept_mkdir("lept/cmorph");
+ lept_stderr("Writing to: /tmp/lept/cmorph/colormorph.pdf\n");
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, "colormorph-test",
+ "/tmp/lept/cmorph/colormorph.pdf");
+ lept_stderr("Writing to: /tmp/lept/cmorph/colormorph.jpg\n");
+ pix1 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 30, 2);
+ pixWrite("/tmp/lept/cmorph/colormorph.jpg", pix1, IFF_JFIF_JPEG);
+ pixDisplay(pix1, 100, 100);
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/colorpage.030.jpg b/leptonica/prog/colorpage.030.jpg
new file mode 100644
index 00000000..fef4c12d
--- /dev/null
+++ b/leptonica/prog/colorpage.030.jpg
Binary files differ
diff --git a/leptonica/prog/colorquant_reg.c b/leptonica/prog/colorquant_reg.c
new file mode 100644
index 00000000..9b5682c5
--- /dev/null
+++ b/leptonica/prog/colorquant_reg.c
@@ -0,0 +1,241 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * colorquant_reg.c
+ *
+ * Regression test for various color quantizers
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 SPACE = 30;
+static const l_int32 MAX_WIDTH = 350;
+static const char *image[4] = {"marge.jpg",
+ "test24.jpg",
+ "juditharismax.jpg",
+ "hardlight2_2.jpg"};
+
+static l_int32 TestImage(const char *filename, l_int32 i, L_REGPARAMS *rp);
+static void PixSave32(PIXA *pixa, PIX *pixc, L_REGPARAMS *rp);
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ for (i = 0; i < 4; i++) {
+/* if (i != 2) continue; */
+ TestImage(image[i], i, rp);
+ }
+
+ return regTestCleanup(rp);
+}
+
+
+static l_int32
+TestImage(const char *filename,
+ l_int32 i,
+ L_REGPARAMS *rp)
+{
+char buf[256];
+l_int32 w, h;
+l_float32 factor;
+PIX *pix, *pixs, *pixc, *pix32, *pixt, *pixd;
+PIXA *pixa;
+
+ PROCNAME("TestImage");
+
+ if ((pix = pixRead(filename)) == NULL) {
+ rp->success = FALSE;
+ return ERROR_INT("pix not made", procName, 1);
+ }
+ pixGetDimensions(pix, &w, &h, NULL);
+ if (w > MAX_WIDTH) {
+ factor = (l_float32)MAX_WIDTH / (l_float32)w;
+ pixs = pixScale(pix, factor, factor);
+ }
+ else
+ pixs = pixClone(pix);
+ pixDestroy(&pix);
+
+ pixa = pixaCreate(0);
+
+ /* Median cut quantizer (no dither; 5 sigbits) */
+ pixaAddPix(pixa, pixs, L_COPY);
+ pixc = pixMedianCutQuantGeneral(pixs, 0, 0, 16, 5, 1, 1);
+ PixSave32(pixa, pixc, rp);
+ pixc = pixMedianCutQuantGeneral(pixs, 0, 0, 128, 5, 1, 1);
+ PixSave32(pixa, pixc, rp);
+ pixc = pixMedianCutQuantGeneral(pixs, 0, 0, 256, 5, 1, 1);
+ PixSave32(pixa, pixc, rp);
+
+ /* Median cut quantizer (with dither; 5 sigbits) */
+ pixaAddPix(pixa, pixs, L_COPY);
+ pixc = pixMedianCutQuantGeneral(pixs, 1, 0, 16, 5, 1, 1);
+ PixSave32(pixa, pixc, rp);
+ pixc = pixMedianCutQuantGeneral(pixs, 1, 0, 128, 5, 1, 1);
+ PixSave32(pixa, pixc, rp);
+ pixc = pixMedianCutQuantGeneral(pixs, 1, 0, 256, 5, 1, 1);
+ PixSave32(pixa, pixc, rp);
+
+ /* Median cut quantizer (no dither; 6 sigbits) */
+ pixaAddPix(pixa, pixs, L_COPY);
+ pixc = pixMedianCutQuantGeneral(pixs, 0, 0, 16, 6, 1, 1);
+ PixSave32(pixa, pixc, rp);
+ pixc = pixMedianCutQuantGeneral(pixs, 0, 0, 128, 6, 1, 1);
+ PixSave32(pixa, pixc, rp);
+ pixc = pixMedianCutQuantGeneral(pixs, 0, 0, 256, 6, 1, 1);
+ PixSave32(pixa, pixc, rp);
+
+ /* Median cut quantizer (with dither; 6 sigbits) */
+ pixaAddPix(pixa, pixs, L_COPY);
+ pixc = pixMedianCutQuantGeneral(pixs, 1, 0, 16, 6, 1, 1);
+ PixSave32(pixa, pixc, rp);
+ pixc = pixMedianCutQuantGeneral(pixs, 1, 0, 128, 6, 1, 1);
+ PixSave32(pixa, pixc, rp);
+ pixc = pixMedianCutQuantGeneral(pixs, 1, 0, 256, 6, 10, 1);
+ PixSave32(pixa, pixc, rp);
+
+ /* Median cut quantizer (mixed color/gray) */
+ pixaAddPix(pixa, pixs, L_COPY);
+ pixc = pixMedianCutQuantMixed(pixs, 20, 10, 0, 0, 0);
+ PixSave32(pixa, pixc, rp);
+ pixc = pixMedianCutQuantMixed(pixs, 60, 20, 0, 0, 0);
+ PixSave32(pixa, pixc, rp);
+ pixc = pixMedianCutQuantMixed(pixs, 180, 40, 0, 0, 0);
+ PixSave32(pixa, pixc, rp);
+
+ /* Simple 256 cube octcube quantizer */
+ pixaAddPix(pixa, pixs, L_COPY);
+ pixc = pixFixedOctcubeQuant256(pixs, 0); /* no dither */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixFixedOctcubeQuant256(pixs, 1); /* dither */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixFixedOctcubeQuant256(pixs, 1); /* dither */
+ PixSave32(pixa, pixc, rp);
+
+ /* 2-pass octree quantizer */
+ pixc = pixOctreeColorQuant(pixs, 128, 0); /* no dither */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixOctreeColorQuant(pixs, 240, 0); /* no dither */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixOctreeColorQuant(pixs, 128, 1); /* dither */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixOctreeColorQuant(pixs, 240, 1); /* dither */
+ PixSave32(pixa, pixc, rp);
+
+ /* Simple adaptive quantization to 4 or 8 bpp, specifying ncolors */
+ pixc = pixOctreeQuantNumColors(pixs, 8, 0); /* fixed: 8 colors */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixOctreeQuantNumColors(pixs, 16, 0); /* fixed: 16 colors */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixOctreeQuantNumColors(pixs, 64, 0); /* fixed: 64 colors */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixOctreeQuantNumColors(pixs, 256, 0); /* fixed: 256 colors */
+ PixSave32(pixa, pixc, rp);
+
+ /* Quantize to fully populated octree (RGB) at given level */
+ pixc = pixFixedOctcubeQuantGenRGB(pixs, 2); /* level 2 */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixFixedOctcubeQuantGenRGB(pixs, 3); /* level 3 */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixFixedOctcubeQuantGenRGB(pixs, 4); /* level 4 */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixFixedOctcubeQuantGenRGB(pixs, 5); /* level 5 */
+ PixSave32(pixa, pixc, rp);
+
+ /* Generate 32 bpp RGB image with num colors <= 256 */
+ pixt = pixOctreeQuantNumColors(pixs, 256, 0); /* cmapped version */
+ pix32 = pixRemoveColormap(pixt, REMOVE_CMAP_BASED_ON_SRC);
+
+ /* Quantize image with few colors at fixed octree leaf level */
+ pixc = pixFewColorsOctcubeQuant1(pix32, 2); /* level 2 */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixFewColorsOctcubeQuant1(pix32, 3); /* level 3 */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixFewColorsOctcubeQuant1(pix32, 4); /* level 4 */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixFewColorsOctcubeQuant1(pix32, 5); /* level 5 */
+ PixSave32(pixa, pixc, rp);
+
+ /* Quantize image by population */
+ pixc = pixOctreeQuantByPopulation(pixs, 3, 0); /* level 3, no dither */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixOctreeQuantByPopulation(pixs, 3, 1); /* level 3, dither */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixOctreeQuantByPopulation(pixs, 4, 0); /* level 4, no dither */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixOctreeQuantByPopulation(pixs, 4, 1); /* level 4, dither */
+ PixSave32(pixa, pixc, rp);
+
+ /* Mixed color/gray octree quantizer */
+ pixaAddPix(pixa, pixt, L_COPY);
+ pixc = pixOctcubeQuantMixedWithGray(pix32, 8, 64, 10); /* max delta = 10 */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixOctcubeQuantMixedWithGray(pix32, 8, 64, 30); /* max delta = 30 */
+ PixSave32(pixa, pixc, rp);
+ pixc = pixOctcubeQuantMixedWithGray(pix32, 8, 64, 50); /* max delta = 50 */
+ PixSave32(pixa, pixc, rp);
+
+ /* Run the high-level converter */
+ pixaAddPix(pixa, pixt, L_COPY);
+ pixc = pixConvertRGBToColormap(pix32, 1);
+ PixSave32(pixa, pixc, rp);
+
+ pixDestroy(&pix32);
+ pixDestroy(&pixt);
+
+ pixd = pixaDisplayTiledInColumns(pixa, 4, 1.0, 25, 2);
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/disp.%d.jpg", i);
+ pixWrite(buf, pixd, IFF_JFIF_JPEG);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ return 0;
+}
+
+static void
+PixSave32(PIXA *pixa, PIX *pixc, L_REGPARAMS *rp)
+{
+PIX *pix32;
+
+ pix32 = pixConvertTo32(pixc);
+ pixaAddPix(pixa, pix32, L_INSERT);
+ regTestWritePixAndCheck(rp, pix32, IFF_JFIF_JPEG);
+ pixDestroy(&pixc);
+}
diff --git a/leptonica/prog/colorseg.jpg b/leptonica/prog/colorseg.jpg
new file mode 100644
index 00000000..ef502eff
--- /dev/null
+++ b/leptonica/prog/colorseg.jpg
Binary files differ
diff --git a/leptonica/prog/colorseg_reg.c b/leptonica/prog/colorseg_reg.c
new file mode 100644
index 00000000..4a212e84
--- /dev/null
+++ b/leptonica/prog/colorseg_reg.c
@@ -0,0 +1,113 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * This explores the space of the four parameters input for color
+ * segmentation. Of the four, only two strongly determine the
+ * output result:
+ * maxdist (the maximum distance between pixels that get
+ * clustered: 20 is very small, 180 is very large)
+ * selsize (responsible for smoothing the result: 0 is no
+ * smoothing (fine texture), 8 is large smoothing)
+ *
+ * For large selsize (>~ 6), large regions get the same color,
+ * and there are few colors in the final result.
+ *
+ * The other two parameters, maxcolors and finalcolors, can be
+ * set small (~4) or large (~20). When set large, @maxdist will
+ * be most influential in determining the actual number of colors.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 MaxColors[] = {4, 8, 16};
+static const l_int32 FinalColors[] = {4, 8, 16};
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, k, maxdist, maxcolors, selsize, finalcolors;
+l_int32 nc, rval, gval, bval;
+PIX *pixs, *pix1, *pix2;
+PIXA *pixa;
+PIXCMAP *cmap, *cmapr;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("tetons.jpg");
+ for (k = 0; k < 3; k++) {
+ maxcolors = MaxColors[k];
+ finalcolors = FinalColors[k];
+ pixa = pixaCreate(0);
+ for (i = 1; i <= 9; i++) {
+ maxdist = 20 * i;
+ for (j = 0; j <= 6; j++) {
+ selsize = j;
+ pix1 = pixColorSegment(pixs, maxdist, maxcolors, selsize,
+ finalcolors, 0);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ }
+ }
+
+ pix2 = pixaDisplayTiledInColumns(pixa, 7, 1.0, 15, 2);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 0, 1, 2 */
+ pixDisplayWithTitle(pix2, 100, k * 300, "colorseg", rp->display);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa);
+ }
+ pixDestroy(&pixs);
+
+ pixs = pixRead("wyom.jpg");
+ pix1 = pixColorSegment(pixs, 50, 6, 6, 6, 0);
+ cmap = pixGetColormap(pix1);
+ nc = pixcmapGetCount(cmap);
+ cmapr = pixcmapCreateRandom(8, 0, 0);
+ for (i = 0; i < nc; i++) {
+ pix2 = pixMakeMaskFromVal(pix1, i);
+ pixcmapGetColor(cmapr, i, &rval, &gval, &bval);
+ pixRenderHashMaskArb(pixs, pix2, 0, 0, 8, 3, i % 4, 0,
+ rval, gval, bval);
+ pixDestroy(&pix2);
+ }
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 3 */
+ regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 4 */
+ pixDisplayWithTitle(pix1, 800, 0, NULL, rp->display);
+ pixDisplayWithTitle(pixs, 800, 640, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pixs);
+ pixcmapDestroy(&cmapr);
+
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/colorsegtest.c b/leptonica/prog/colorsegtest.c
new file mode 100644
index 00000000..121c0e2a
--- /dev/null
+++ b/leptonica/prog/colorsegtest.c
@@ -0,0 +1,109 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * colorsegtest.c
+ *
+ * See colorseg.c for details.
+ *
+ * Just for fun, try these combinations of the 4 parameters below on
+ * the image tetons.jpg:
+ * 30 20 5 10 (20 colors)
+ * 40 20 7 15 (19 colors)
+ * 50 12 5 12 (12 colors)
+ * 50 12 3 12 (12 colors)
+ * 30 13 3 13 (12 colors)
+ * 30 20 3 20 (20 colors)
+ * 15 20 5 15 (19 colors)
+ * 80 20 3 20 (12 colors)
+ * 100 15 5 15 (7 colors)
+ * 100 15 2 15 (7 colors)
+ * 100 15 0 15 (7 colors)
+ * 30 15 0 15 (12 colors)
+ * 150 15 0 15 (4 colors)
+ * 150 15 2 15 (4 colors)
+ * 180 6 2 6 (3 colors)
+ * 180 6 0 6 (3 colors)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 MAX_DIST = 120;
+static const l_int32 MAX_COLORS = 15;
+static const l_int32 SEL_SIZE = 4;
+static const l_int32 FINAL_COLORS = 15;
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 max_dist, max_colors, sel_size, final_colors;
+PIX *pixs, *pixd, *pixt;
+char *filein, *fileout;
+static char mainName[] = "colorsegtest";
+
+ if (argc != 3 && argc != 7)
+ return ERROR_INT(
+ "Syntax: colorsegtest filein fileout"
+ " [max_dist max_colors sel_size final_colors]\n"
+ " Default values are: max_dist = 120\n"
+ " max_colors = 15\n"
+ " sel_size = 4\n"
+ " final_colors = 15\n", mainName, 1);
+ filein = argv[1];
+ fileout = argv[2];
+ if (argc == 3) { /* use default values */
+ max_dist = MAX_DIST;
+ max_colors = MAX_COLORS;
+ sel_size = SEL_SIZE;
+ final_colors = FINAL_COLORS;
+ }
+ else { /* 6 input args */
+ max_dist = atoi(argv[3]);
+ max_colors = atoi(argv[4]);
+ sel_size = atoi(argv[5]);
+ final_colors = atoi(argv[6]);
+ }
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+ startTimer();
+ pixt = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+ pixd = pixColorSegment(pixt, max_dist, max_colors, sel_size,
+ final_colors, 1);
+ lept_stderr("Time to segment: %7.3f sec\n", stopTimer());
+ pixWrite(fileout, pixd, IFF_PNG);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
+
diff --git a/leptonica/prog/colorspace_reg.c b/leptonica/prog/colorspace_reg.c
new file mode 100644
index 00000000..07284c6a
--- /dev/null
+++ b/leptonica/prog/colorspace_reg.c
@@ -0,0 +1,208 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Tests:
+ * - conversions between HSV and both RGB and colormapped images.
+ * - global linear color mapping and extraction of color magnitude
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char label[512];
+l_int32 rval, gval, bval, w, h, i, j, rwhite, gwhite, bwhite, count;
+l_uint32 pixel;
+GPLOT *gplot1, *gplot2;
+NUMA *naseq, *na;
+NUMAA *naa1, *naa2;
+PIX *pixs, *pix0, *pix1, *pix2, *pix3;
+PIX *pixr, *pixg, *pixb; /* for color content extraction */
+PIXA *pixa, *pixat;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "colorspace_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Generate a pdf of results when called with display */
+ pixa = pixaCreate(0);
+
+ /* Generate colors by sampling hue with max sat and value.
+ * This image has been saved as 19-colors.png. */
+ pixat = pixaCreate(19);
+ for (i = 0; i < 19; i++) {
+ convertHSVToRGB((240 * i / 18), 255, 255, &rval, &gval, &bval);
+ composeRGBPixel(rval, gval, bval, &pixel);
+ pix1 = pixCreate(50, 100, 32);
+ pixSetAllArbitrary(pix1, pixel);
+ pixaAddPix(pixat, pix1, L_INSERT);
+ }
+ pix2 = pixaDisplayTiledInRows(pixat, 32, 1100, 1.0, 0, 0, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 0 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaDestroy(&pixat);
+
+ /* Colorspace conversion in rgb */
+ pixs = pixRead("wyom.jpg");
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pix3 = pixConvertRGBToHSV(NULL, pixs);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 1 */
+ pixaAddPix(pixa, pix3, L_COPY);
+ pixConvertHSVToRGB(pix3, pix3);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 2 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+
+ /* Colorspace conversion on a colormap */
+ pix3 = pixOctreeQuantNumColors(pixs, 25, 0);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 3 */
+ pixaAddPix(pixa, pix3, L_COPY);
+ cmap = pixGetColormap(pix3);
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ pixcmapConvertRGBToHSV(cmap);
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 4 */
+ pixaAddPix(pixa, pix3, L_COPY);
+ pixcmapConvertHSVToRGB(cmap);
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 5 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+
+ /* Color content extraction */
+ pixColorContent(pixs, 0, 0, 0, 0, &pixr, &pixg, &pixb);
+ regTestWritePixAndCheck(rp, pixr, IFF_JFIF_JPEG); /* 6 */
+ pixaAddPix(pixa, pixr, L_INSERT);
+ regTestWritePixAndCheck(rp, pixg, IFF_JFIF_JPEG); /* 7 */
+ pixaAddPix(pixa, pixg, L_INSERT);
+ regTestWritePixAndCheck(rp, pixb, IFF_JFIF_JPEG); /* 8 */
+ pixaAddPix(pixa, pixb, L_INSERT);
+
+ /* Color content measurement. This tests the global
+ * mapping of (r,g,b) --> (white), for 20 different
+ * values of (r,g,b). For each mappings, we compute
+ * the color magnitude and threshold it at six values.
+ * For each of those six thresholds, we plot the
+ * fraction of pixels that exceeds the threshold
+ * color magnitude, where the red value (mapped to
+ * white) goes between 100 and 195. */
+ pixat = pixaCreate(20);
+ naseq = numaMakeSequence(100, 5, 20);
+ naa1 = numaaCreate(6);
+ naa2 = numaaCreate(6);
+ for (i = 0; i < 6; i++) {
+ na = numaCreate(20);
+ numaaAddNuma(naa1, na, L_COPY);
+ numaaAddNuma(naa2, na, L_INSERT);
+ }
+ pixGetDimensions(pixs, &w, &h, NULL);
+ for (i = 0; i < 20; i++) {
+ rwhite = 100 + 5 * i;
+ gwhite = 200 - 5 * i;
+ bwhite = 150;
+ pix0 = pixGlobalNormRGB(NULL, pixs, rwhite, gwhite, bwhite, 255);
+ pixaAddPix(pixat, pix0, L_INSERT);
+ pix1 = pixColorMagnitude(pixs, rwhite, gwhite, bwhite,
+ L_AVE_MAX_DIFF_2);
+ for (j = 0; j < 6; j++) {
+ pix2 = pixThresholdToBinary(pix1, 30 + 10 * j);
+ pixInvert(pix2, pix2);
+ pixCountPixels(pix2, &count, NULL);
+ na = numaaGetNuma(naa1, j, L_CLONE);
+ numaAddNumber(na, (l_float32)count / (l_float32)(w * h));
+ numaDestroy(&na);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+ pix1 = pixColorMagnitude(pixs, rwhite, gwhite, bwhite, L_INTERMED_DIFF);
+ for (j = 0; j < 6; j++) {
+ pix2 = pixThresholdToBinary(pix1, 30 + 10 * j);
+ pixInvert(pix2, pix2);
+ pixCountPixels(pix2, &count, NULL);
+ na = numaaGetNuma(naa2, j, L_CLONE);
+ numaAddNumber(na, (l_float32)count / (l_float32)(w * h));
+ numaDestroy(&na);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+ }
+ gplot1 = gplotCreate("/tmp/lept/regout/colorspace.10", GPLOT_PNG,
+ "Fraction with given color (diff from average)",
+ "white point space for red", "amount of color");
+ gplot2 = gplotCreate("/tmp/lept/regout/colorspace.11", GPLOT_PNG,
+ "Fraction with given color (min diff)",
+ "white point space for red", "amount of color");
+ for (j = 0; j < 6; j++) {
+ na = numaaGetNuma(naa1, j, L_CLONE);
+ snprintf(label, sizeof(label), "thresh %d", 30 + 10 * j);
+ gplotAddPlot(gplot1, naseq, na, GPLOT_LINES, label);
+ numaDestroy(&na);
+ na = numaaGetNuma(naa2, j, L_CLONE);
+ gplotAddPlot(gplot2, naseq, na, GPLOT_LINES, label);
+ numaDestroy(&na);
+ }
+ gplotMakeOutput(gplot1);
+ gplotMakeOutput(gplot2);
+ gplotDestroy(&gplot1);
+ gplotDestroy(&gplot2);
+ pix1 = pixaDisplayTiledAndScaled(pixat, 32, 250, 4, 0, 10, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 9 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDisplayWithTitle(pix1, 0, 100, "Color magnitude", rp->display);
+ pixaDestroy(&pixat);
+ numaDestroy(&naseq);
+ numaaDestroy(&naa1);
+ numaaDestroy(&naa2);
+
+ /* Save as golden files, or check against them */
+ regTestCheckFile(rp, "/tmp/lept/regout/colorspace.10.png"); /* 10 */
+ regTestCheckFile(rp, "/tmp/lept/regout/colorspace.11.png"); /* 11 */
+
+ if (rp->display) {
+ pix3 = pixRead("/tmp/lept/regout/colorspace.10.png");
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix3 = pixRead("/tmp/lept/regout/colorspace.11.png");
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixaConvertToPdf(pixa, 0, 1.0, 0, 0, "colorspace tests",
+ "/tmp/lept/regout/colorspace.pdf");
+ L_INFO("Output pdf: /tmp/lept/regout/colorspace.pdf\n", rp->testname);
+ }
+ pixaDestroy(&pixa);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/comap.063.jpg b/leptonica/prog/comap.063.jpg
new file mode 100644
index 00000000..cda10e35
--- /dev/null
+++ b/leptonica/prog/comap.063.jpg
Binary files differ
diff --git a/leptonica/prog/comap.068.jpg b/leptonica/prog/comap.068.jpg
new file mode 100644
index 00000000..c8e64a4c
--- /dev/null
+++ b/leptonica/prog/comap.068.jpg
Binary files differ
diff --git a/leptonica/prog/comap.073.jpg b/leptonica/prog/comap.073.jpg
new file mode 100644
index 00000000..ec28b4fa
--- /dev/null
+++ b/leptonica/prog/comap.073.jpg
Binary files differ
diff --git a/leptonica/prog/comap.100.jpg b/leptonica/prog/comap.100.jpg
new file mode 100644
index 00000000..fdf65952
--- /dev/null
+++ b/leptonica/prog/comap.100.jpg
Binary files differ
diff --git a/leptonica/prog/comap.110.jpg b/leptonica/prog/comap.110.jpg
new file mode 100644
index 00000000..fdda284a
--- /dev/null
+++ b/leptonica/prog/comap.110.jpg
Binary files differ
diff --git a/leptonica/prog/comap.118.jpg b/leptonica/prog/comap.118.jpg
new file mode 100644
index 00000000..96ae6f26
--- /dev/null
+++ b/leptonica/prog/comap.118.jpg
Binary files differ
diff --git a/leptonica/prog/compare_reg.c b/leptonica/prog/compare_reg.c
new file mode 100644
index 00000000..de5fe5a7
--- /dev/null
+++ b/leptonica/prog/compare_reg.c
@@ -0,0 +1,143 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * This tests comparison of images that are:
+ *
+ * (1) translated with respect to each other
+ * (2) only slightly different in content
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 delx, dely, etransx, etransy, w, h, area1, area2;
+l_int32 *stab, *ctab;
+l_float32 cx1, cy1, cx2, cy2, score, fract;
+PIX *pix0, *pix1, *pix2, *pix3, *pix4, *pix5;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "compare_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* ------------ Test of pixBestCorrelation() --------------- */
+ pix0 = pixRead("harmoniam100-11.png");
+ pix1 = pixConvertTo1(pix0, 160);
+ pixGetDimensions(pix1, &w, &h, NULL);
+
+ /* Now make a smaller image, translated by (-32, -12)
+ * Except for the resizing, this is equivalent to
+ * pix2 = pixTranslate(NULL, pix1, -32, -12, L_BRING_IN_WHITE); */
+ pix2 = pixCreate(w - 10, h, 1);
+ pixRasterop(pix2, 0, 0, w, h, PIX_SRC, pix1, 32, 12);
+
+ /* Get the number of FG pixels and the centroid locations */
+ stab = makePixelSumTab8();
+ ctab = makePixelCentroidTab8();
+ pixCountPixels(pix1, &area1, stab);
+ pixCountPixels(pix2, &area2, stab);
+ pixCentroid(pix1, ctab, stab, &cx1, &cy1);
+ pixCentroid(pix2, ctab, stab, &cx2, &cy2);
+ etransx = lept_roundftoi(cx1 - cx2);
+ etransy = lept_roundftoi(cy1 - cy2);
+ lept_stderr("delta cx = %d, delta cy = %d\n", etransx, etransy);
+
+ /* Get the best correlation, searching around the translation
+ * where the centroids coincide */
+ pixBestCorrelation(pix1, pix2, area1, area2, etransx, etransy,
+ 4, stab, &delx, &dely, &score, 5);
+ lept_stderr("delx = %d, dely = %d, score = %7.4f\n", delx, dely, score);
+ regTestCompareValues(rp, 32, delx, 0); /* 0 */
+ regTestCompareValues(rp, 12, dely, 0); /* 1 */
+ lept_mv("/tmp/lept/comp/correl_5.png", "lept/regout", NULL, NULL);
+ regTestCheckFile(rp, "/tmp/lept/regout/correl_5.png"); /* 2 */
+ lept_free(stab);
+ lept_free(ctab);
+ pixDestroy(&pix0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+
+ /* ------------ Test of pixCompareWithTranslation() ------------ */
+ /* Now use the pyramid to get the result. Do a translation
+ * to remove pixels at the bottom from pix2, so that the
+ * centroids are initially far apart. */
+ pix1 = pixRead("harmoniam-11.tif");
+ pix2 = pixTranslate(NULL, pix1, -45, 25, L_BRING_IN_WHITE);
+ l_pdfSetDateAndVersion(0);
+ pixCompareWithTranslation(pix1, pix2, 160, &delx, &dely, &score, 1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ lept_stderr("delx = %d, dely = %d\n", delx, dely);
+ regTestCompareValues(rp, 45, delx, 0); /* 3 */
+ regTestCompareValues(rp, -25, dely, 0); /* 4 */
+ lept_mv("/tmp/lept/comp/correl.pdf", "lept/regout", NULL, NULL);
+ lept_mv("/tmp/lept/comp/compare.pdf", "lept/regout", NULL, NULL);
+ regTestCheckFile(rp, "/tmp/lept/regout/compare.pdf"); /* 5 */
+ regTestCheckFile(rp, "/tmp/lept/regout/correl.pdf"); /* 6 */
+
+ /* ------------ Test of pixGetPerceptualDiff() --------------- */
+ pix0 = pixRead("greencover.jpg");
+ pix1 = pixRead("redcover.jpg"); /* pre-scaled to the same size */
+ /* Apply directly to the color images */
+ pixGetPerceptualDiff(pix0, pix1, 1, 3, 20, &fract, &pix2, &pix3);
+ lept_stderr("Fraction of color pixels = %f\n", fract);
+ regTestCompareValues(rp, 0.061252, fract, 0.01); /* 7 */
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 8 */
+ regTestWritePixAndCheck(rp, pix3, IFF_TIFF_G4); /* 9 */
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ /* Apply to grayscale images */
+ pix2 = pixConvertTo8(pix0, 0);
+ pix3 = pixConvertTo8(pix1, 0);
+ pixGetPerceptualDiff(pix2, pix3, 1, 3, 20, &fract, &pix4, &pix5);
+ lept_stderr("Fraction of grayscale pixels = %f\n", fract);
+ regTestCompareValues(rp, 0.046928, fract, 0.0002); /* 10 */
+ regTestWritePixAndCheck(rp, pix4, IFF_JFIF_JPEG); /* 11 */
+ regTestWritePixAndCheck(rp, pix5, IFF_TIFF_G4); /* 12 */
+ pixDestroy(&pix0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/comparepages.c b/leptonica/prog/comparepages.c
new file mode 100644
index 00000000..45437356
--- /dev/null
+++ b/leptonica/prog/comparepages.c
@@ -0,0 +1,116 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * comparepages.c
+ *
+ * This compares text pages using the location of word bounding boxes.
+ * The goal is to get a fast and robust determination for whether
+ * two pages are the same.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w, h, n, same;
+BOXA *boxa1, *boxa2;
+NUMA *nai1, *nai2;
+NUMAA *naa1, *naa2;
+PIX *pixs, *pixt, *pixb1, *pixb2;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/comp");
+
+ pixs = pixRead("lucasta.047.jpg");
+ pixb1 = pixConvertTo1(pixs, 128);
+ pixGetWordBoxesInTextlines(pixb1, 10, 10, 500, 50, &boxa1, &nai1);
+ pixt = pixDrawBoxaRandom(pixs, boxa1, 2);
+ pixDisplay(pixt, 100, 100);
+ pixWrite("/tmp/lept/comp/pixt.png", pixt, IFF_PNG);
+ naa1 = boxaExtractSortedPattern(boxa1, nai1);
+ numaaWrite("/tmp/lept/comp/naa1.naa", naa1);
+ n = numaaGetCount(naa1);
+ lept_stderr("Number of textlines = %d\n", n);
+ pixDisplay(pixb1, 300, 0);
+
+ /* Translate */
+ pixb2 = pixCreateTemplate(pixb1);
+ pixGetDimensions(pixb1, &w, &h, NULL);
+ pixRasterop(pixb2, 148, 133, w, h, PIX_SRC, pixb1, 0, 0);
+ pixDisplay(pixb2, 600, 0);
+ pixGetWordBoxesInTextlines(pixb2, 10, 10, 500, 50, &boxa2, &nai2);
+ naa2 = boxaExtractSortedPattern(boxa2, nai2);
+ numaaCompareImagesByBoxes(naa1, naa2, 5, 10, 150, 150, 20, 20, &same, 1);
+ lept_stderr("Translation. same?: %d\n\n", same);
+ boxaDestroy(&boxa2);
+ numaDestroy(&nai2);
+ pixDestroy(&pixb2);
+ numaaDestroy(&naa2);
+
+ /* Aligned part is below h/3 */
+ pixb2 = pixCreateTemplate(pixb1);
+ pixGetDimensions(pixb1, &w, &h, NULL);
+ pixRasterop(pixb2, 0, 0, w, h / 3, PIX_SRC, pixb1, 0, 2 * h / 3);
+ pixRasterop(pixb2, 0, h / 3, w, 2 * h / 3, PIX_SRC, pixb1, 0, h / 3);
+ pixDisplay(pixb2, 900, 0);
+ pixGetWordBoxesInTextlines(pixb2, 10, 10, 500, 50, &boxa2, &nai2);
+ naa2 = boxaExtractSortedPattern(boxa2, nai2);
+ numaaCompareImagesByBoxes(naa1, naa2, 5, 10, 150, 150, 20, 20, &same, 1);
+ lept_stderr("Aligned part below h/3. same?: %d\n\n", same);
+ boxaDestroy(&boxa2);
+ numaDestroy(&nai2);
+ pixDestroy(&pixb2);
+ numaaDestroy(&naa2);
+
+ /* Top and bottom switched; no aligned parts */
+ pixb2 = pixCreateTemplate(pixb1);
+ pixGetDimensions(pixb1, &w, &h, NULL);
+ pixRasterop(pixb2, 0, 0, w, h / 3, PIX_SRC, pixb1, 0, 2 * h / 3);
+ pixRasterop(pixb2, 0, h / 3, w, 2 * h / 3, PIX_SRC, pixb1, 0, 0);
+ pixDisplay(pixb2, 1200, 0);
+ pixGetWordBoxesInTextlines(pixb2, 10, 10, 500, 50, &boxa2, &nai2);
+ naa2 = boxaExtractSortedPattern(boxa2, nai2);
+ numaaCompareImagesByBoxes(naa1, naa2, 5, 10, 150, 150, 20, 20, &same, 1);
+ lept_stderr("Top/Bot switched; no alignment. Same?: %d\n", same);
+ boxaDestroy(&boxa2);
+ numaDestroy(&nai2);
+ pixDestroy(&pixb2);
+ numaaDestroy(&naa2);
+
+ boxaDestroy(&boxa1);
+ numaDestroy(&nai1);
+ pixDestroy(&pixs);
+ pixDestroy(&pixb1);
+ pixDestroy(&pixt);
+ numaaDestroy(&naa1);
+ return 0;
+}
diff --git a/leptonica/prog/comparepixa.c b/leptonica/prog/comparepixa.c
new file mode 100644
index 00000000..d8dace51
--- /dev/null
+++ b/leptonica/prog/comparepixa.c
@@ -0,0 +1,101 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * comparepixa.c
+ *
+ * comparepixa file1 file2 nx ny tw spacing border fontsize fileout
+ *
+ * This reads two pixa or pixacomp from files and renders them
+ * interleaved, side-by-side in a pdf. A warning is issued if the
+ * input image arrays have different lengths.
+ *
+ * The integers nx and ny specify how many side-by-side pairs
+ * are displayed on each pdf page. For example, if nx = 1 and ny = 2,
+ * then two pairs are shown, one above the other.
+ *
+ * The input pix are scaled to tw, the target width, then paired
+ * up with %spacing and an optional %border.
+ *
+ * The pairs are then mosaiced, depending on %nx and %ny, into
+ * a set of larger images. The %spacing and %border parameters
+ * are used here as well. To label each pair with the index from
+ * the input arrays, choose fontsize in {4, 6, 8, 10, 12, 14, 16, 18, 20}.
+ * To skip labelling, set %fontsize = 0.
+ *
+ * This set of images is rendered into a pdf and written to %fileont.
+ *
+ * Typical numbers for the input parameters are:
+ * %nx = small integer (1 - 4)
+ * %ny = 2 * %nx
+ * %tw = 200 - 500 pixels
+ * %spacing = 10
+ * %border = 2
+ * %fontsize = 10
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *fileout;
+l_int32 nx, ny, tw, spacing, border, fontsize;
+PIXA *pixa1, *pixa2;
+static char mainName[] = "comparepixa";
+
+ if (argc != 10) {
+ lept_stderr("Syntax error in comparepixa:\n"
+ " comparepixa file1 file2 nx ny tw spacing border"
+ " fontsize fileout\n");
+ return 1;
+ }
+ setLeptDebugOK(1);
+
+ /* Input files can be either pixa or pixacomp */
+ if ((pixa1 = pixaReadBoth(argv[1])) == NULL)
+ return ERROR_INT("pixa1 not read", mainName, 1);
+ if ((pixa2 = pixaReadBoth(argv[2])) == NULL)
+ return ERROR_INT("pixa2 not read", mainName, 1);
+ nx = atoi(argv[3]);
+ ny = atoi(argv[4]);
+ tw = atoi(argv[5]);
+ spacing = atoi(argv[6]);
+ border = atoi(argv[7]);
+ fontsize = atoi(argv[8]);
+ fileout = argv[9];
+
+ pixaCompareInPdf(pixa1, pixa2, nx, ny, tw, spacing, border,
+ fontsize, fileout);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ return 0;
+}
+
diff --git a/leptonica/prog/comparetest.c b/leptonica/prog/comparetest.c
new file mode 100644
index 00000000..a094efd1
--- /dev/null
+++ b/leptonica/prog/comparetest.c
@@ -0,0 +1,159 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * comparetest.c
+ *
+ * comparetest filein1 filein2 type fileout
+ *
+ * where
+ * type = {0, 1} for {abs-diff and subtraction} comparisons
+ *
+ * Compares two images, using either the absolute value of the
+ * pixel differences or the difference clipped to 0. For RGB,
+ * the differences are computed separately on each component.
+ * If one has a colormap and the other doesn't, the colormap
+ * is removed before making the comparison.
+ *
+ * Warning: you usually want to use abs-diff to compare
+ * two grayscale or color images. If you use subtraction,
+ * the result you get will depend on the order of the input images.
+ * For example, if pix2 = pixDilateGray(pix1), then every
+ * pixel in pix1 will be equal to or greater than pix2. So if
+ * you subtract pix2 from pix1, you will get 0 for all pixels,
+ * which looks like they're the same!
+ *
+ * Here's an interesting observation. Take an image that has
+ * been jpeg compressed at a quality = 75. If you re-compress
+ * the image, what quality factor should be used to minimize
+ * the change? Answer: 75 (!)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 type, comptype, d1, d2, same, first, last;
+l_float32 fract, diff, rmsdiff;
+char *filein1, *filein2, *fileout;
+GPLOT *gplot;
+NUMA *na1, *na2;
+PIX *pixs1, *pixs2, *pixd;
+static char mainName[] = "comparetest";
+
+ if (argc != 5)
+ return ERROR_INT(" Syntax: comparetest filein1 filein2 type fileout",
+ mainName, 1);
+ filein1 = argv[1];
+ filein2 = argv[2];
+ type = atoi(argv[3]);
+ pixd = NULL;
+ fileout = argv[4];
+ setLeptDebugOK(1);
+
+ /* If comparing image files with 16 bps and spp > 1,
+ * comment this line out to strip 16 --> 8 spp */
+ l_pngSetReadStrip16To8(0);
+
+ if ((pixs1 = pixRead(filein1)) == NULL)
+ return ERROR_INT("pixs1 not made", mainName, 1);
+ if ((pixs2 = pixRead(filein2)) == NULL)
+ return ERROR_INT("pixs2 not made", mainName, 1);
+ d1 = pixGetDepth(pixs1);
+ d2 = pixGetDepth(pixs2);
+
+ if (d1 == 1 && d2 == 1) {
+ pixEqual(pixs1, pixs2, &same);
+ if (same) {
+ lept_stderr("Images are identical\n");
+ pixd = pixCreateTemplate(pixs1); /* write empty pix for diff */
+ } else {
+ if (type == 0)
+ comptype = L_COMPARE_XOR;
+ else
+ comptype = L_COMPARE_SUBTRACT;
+ pixCompareBinary(pixs1, pixs2, comptype, &fract, &pixd);
+ lept_stderr("Fraction of different pixels: %10.6f\n", fract);
+ }
+ pixWrite(fileout, pixd, IFF_PNG);
+ } else {
+ if (type == 0)
+ comptype = L_COMPARE_ABS_DIFF;
+ else
+ comptype = L_COMPARE_SUBTRACT;
+ pixCompareGrayOrRGB(pixs1, pixs2, comptype, GPLOT_PNG, &same, &diff,
+ &rmsdiff, &pixd);
+ if (type == 0) {
+ if (same) {
+ lept_stderr("Images are identical\n");
+ } else {
+ lept_stderr("Images differ: <diff> = %10.6f\n", diff);
+ lept_stderr(" <rmsdiff> = %10.6f\n", rmsdiff);
+ }
+ }
+ else { /* subtraction */
+ if (same) {
+ lept_stderr("pixs2 strictly greater than pixs1\n");
+ } else {
+ lept_stderr("Images differ: <diff> = %10.6f\n", diff);
+ lept_stderr(" <rmsdiff> = %10.6f\n", rmsdiff);
+ }
+ }
+ if (d1 != 16)
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+ else
+ pixWrite(fileout, pixd, IFF_PNG);
+
+ if (d1 != 16 && !same) {
+ na1 = pixCompareRankDifference(pixs1, pixs2, 1);
+ if (na1) {
+ numaGetNonzeroRange(na1, 0.00005, &first, &last);
+ lept_stderr("Nonzero diff range: first = %d, last = %d\n",
+ first, last);
+ na2 = numaClipToInterval(na1, first, last);
+ gplot = gplotCreate("/tmp/lept/comp/rank", GPLOT_PNG,
+ "Pixel Rank Difference",
+ "pixel val difference", "rank");
+ gplotAddPlot(gplot, NULL, na2, GPLOT_LINES, "rank");
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ l_fileDisplay("/tmp/lept/comp/rank.png", 100, 100, 1.0);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ }
+ }
+ }
+
+ pixDestroy(&pixs1);
+ pixDestroy(&pixs2);
+ pixDestroy(&pixd);
+ return 0;
+}
diff --git a/leptonica/prog/compfilter_reg.c b/leptonica/prog/compfilter_reg.c
new file mode 100644
index 00000000..1ceaee90
--- /dev/null
+++ b/leptonica/prog/compfilter_reg.c
@@ -0,0 +1,346 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * compfilter_reg.c
+ *
+ * Regression test for filters that select connected components
+ * based on size, using logical combinations of indicator arrays.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static void Count_pieces(L_REGPARAMS *rp, PIX *pix, l_int32 nexp);
+static void Count_pieces2(L_REGPARAMS *rp, BOXA *boxa, l_int32 nexp);
+static l_int32 Count_ones(L_REGPARAMS *rp, NUMA *na, l_int32 nexp,
+ l_int32 index, const char *name);
+
+static const l_float32 edges[13] = {0.0f, 0.2f, 0.3f, 0.35f, 0.4f, 0.45f, 0.5f,
+ 0.55f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f};
+
+ /* for feyn.tif */
+static const l_int32 band[12] = {1, 11, 48, 264, 574, 704, 908, 786, 466,
+ 157, 156, 230};
+static const l_int32 total[12] = {1, 12, 60, 324, 898, 1602, 2510, 3296,
+ 3762, 3919, 4075, 4305};
+#if 0
+ /* for rabi.png */
+static const l_int32 band[12] = {24, 295, 490, 817, 1768, 962, 8171,
+ 63, 81, 51, 137, 8619};
+static const l_int32 total[12] = {24, 319, 809, 1626, 3394, 4356, 12527,
+ 12590, 12671, 12722, 12859, 21478};
+#endif
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w, h, n, i, sum, sumi, empty;
+BOX *box1, *box2, *box3, *box4;
+BOXA *boxa1, *boxa2;
+NUMA *na1, *na2, *na3, *na4, *na5;
+NUMA *na2i, *na3i, *na4i, *nat, *naw, *nah;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa1, *pixa2, *pixa3;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Draw 4 filled boxes of different sizes */
+ pixs = pixCreate(200, 200, 1);
+ box1 = boxCreate(10, 10, 20, 30);
+ box2 = boxCreate(50, 10, 40, 20);
+ box3 = boxCreate(110, 10, 35, 5);
+ box4 = boxCreate(160, 10, 5, 15);
+ boxa1 = boxaCreate(4);
+ boxaAddBox(boxa1, box1, L_INSERT);
+ boxaAddBox(boxa1, box2, L_INSERT);
+ boxaAddBox(boxa1, box3, L_INSERT);
+ boxaAddBox(boxa1, box4, L_INSERT);
+ pixRenderBox(pixs, box1, 1, L_SET_PIXELS);
+ pixRenderBox(pixs, box2, 1, L_SET_PIXELS);
+ pixRenderBox(pixs, box3, 1, L_SET_PIXELS);
+ pixRenderBox(pixs, box4, 1, L_SET_PIXELS);
+ pix1 = pixFillClosedBorders(pixs, 4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pix2 = pixCreateTemplate(pixs);
+ pixRenderHashBox(pix2, box1, 6, 4, L_POS_SLOPE_LINE, 1, L_SET_PIXELS);
+ pixRenderHashBox(pix2, box2, 7, 2, L_POS_SLOPE_LINE, 1, L_SET_PIXELS);
+ pixRenderHashBox(pix2, box3, 4, 2, L_VERTICAL_LINE, 1, L_SET_PIXELS);
+ pixRenderHashBox(pix2, box4, 3, 1, L_HORIZONTAL_LINE, 1, L_SET_PIXELS);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+
+ /* Exercise the parameters: Reg indices 2-27 */
+ pix3 = pixSelectBySize(pix1, 0, 22, 8, L_SELECT_HEIGHT,
+ L_SELECT_IF_GT, NULL);
+ Count_pieces(rp, pix3, 1);
+ pix3 = pixSelectBySize(pix1, 0, 30, 8, L_SELECT_HEIGHT,
+ L_SELECT_IF_LT, NULL);
+ Count_pieces(rp, pix3, 3);
+ pix3 = pixSelectBySize(pix1, 0, 5, 8, L_SELECT_HEIGHT,
+ L_SELECT_IF_GT, NULL);
+ Count_pieces(rp, pix3, 3);
+ pix3 = pixSelectBySize(pix1, 0, 6, 8, L_SELECT_HEIGHT,
+ L_SELECT_IF_LT, NULL);
+ Count_pieces(rp, pix3, 1);
+ pix3 = pixSelectBySize(pix1, 20, 0, 8, L_SELECT_WIDTH,
+ L_SELECT_IF_GT, NULL);
+ Count_pieces(rp, pix3, 2);
+ pix3 = pixSelectBySize(pix1, 31, 0, 8, L_SELECT_WIDTH,
+ L_SELECT_IF_LT, NULL);
+ Count_pieces(rp, pix3, 2);
+ pix3 = pixSelectBySize(pix1, 21, 10, 8, L_SELECT_IF_EITHER,
+ L_SELECT_IF_LT, NULL);
+ Count_pieces(rp, pix3, 3);
+ pix3 = pixSelectBySize(pix1, 20, 30, 8, L_SELECT_IF_EITHER,
+ L_SELECT_IF_GT, NULL);
+ Count_pieces(rp, pix3, 2);
+ pix3 = pixSelectBySize(pix1, 22, 32, 8, L_SELECT_IF_BOTH,
+ L_SELECT_IF_LT, NULL);
+ Count_pieces(rp, pix3, 2);
+ pix3 = pixSelectBySize(pix1, 6, 32, 8, L_SELECT_IF_BOTH,
+ L_SELECT_IF_LT, NULL);
+ Count_pieces(rp, pix3, 1);
+ pix3 = pixSelectBySize(pix1, 5, 25, 8, L_SELECT_IF_BOTH,
+ L_SELECT_IF_GT, NULL);
+ Count_pieces(rp, pix3, 1);
+ pix3 = pixSelectBySize(pix1, 25, 5, 8, L_SELECT_IF_BOTH,
+ L_SELECT_IF_GT, NULL);
+ Count_pieces(rp, pix3, 1);
+
+ pix3 = pixSelectByPerimToAreaRatio(pix1, 0.3, 8, L_SELECT_IF_GT, NULL);
+ Count_pieces(rp, pix3, 2);
+ pix3 = pixSelectByPerimToAreaRatio(pix1, 0.15, 8, L_SELECT_IF_GT, NULL);
+ Count_pieces(rp, pix3, 3);
+ pix3 = pixSelectByPerimToAreaRatio(pix1, 0.4, 8, L_SELECT_IF_LTE, NULL);
+ Count_pieces(rp, pix3, 2);
+ pix3 = pixSelectByPerimToAreaRatio(pix1, 0.45, 8, L_SELECT_IF_LT, NULL);
+ Count_pieces(rp, pix3, 3);
+
+ pix3 = pixSelectByPerimSizeRatio(pix2, 2.3, 8, L_SELECT_IF_GT, NULL);
+ Count_pieces(rp, pix3, 2);
+ pix3 = pixSelectByPerimSizeRatio(pix2, 1.2, 8, L_SELECT_IF_GT, NULL);
+ Count_pieces(rp, pix3, 3);
+ pix3 = pixSelectByPerimSizeRatio(pix2, 1.7, 8, L_SELECT_IF_LTE, NULL);
+ Count_pieces(rp, pix3, 1);
+ pix3 = pixSelectByPerimSizeRatio(pix2, 2.9, 8, L_SELECT_IF_LT, NULL);
+ Count_pieces(rp, pix3, 3);
+
+ pix3 = pixSelectByAreaFraction(pix2, 0.3, 8, L_SELECT_IF_LT, NULL);
+ Count_pieces(rp, pix3, 0);
+ pix3 = pixSelectByAreaFraction(pix2, 0.9, 8, L_SELECT_IF_LT, NULL);
+ Count_pieces(rp, pix3, 4);
+ pix3 = pixSelectByAreaFraction(pix2, 0.5, 8, L_SELECT_IF_GTE, NULL);
+ Count_pieces(rp, pix3, 3);
+ pix3 = pixSelectByAreaFraction(pix2, 0.7, 8, L_SELECT_IF_GT, NULL);
+ Count_pieces(rp, pix3, 2);
+
+ boxa2 = boxaSelectBySize(boxa1, 21, 10, L_SELECT_IF_EITHER,
+ L_SELECT_IF_LT, NULL);
+ Count_pieces2(rp, boxa2, 3);
+ boxa2 = boxaSelectBySize(boxa1, 22, 32, L_SELECT_IF_BOTH,
+ L_SELECT_IF_LT, NULL);
+ Count_pieces2(rp, boxa2, 2);
+ boxaDestroy(&boxa1);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Here's the most general method for selecting components. *
+ * We do it for area fraction, but any combination of *
+ * size, area/perimeter ratio and area fraction can be used. *
+ * Reg indices 28-85 */
+ pixs = pixRead("feyn.tif");
+ pix1 = pixCopy(NULL, pixs); /* subtract bands from this */
+ pix2 = pixCreateTemplate(pixs); /* add bands to this */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ boxa1 = pixConnComp(pixs, &pixa1, 8);
+ n = boxaGetCount(boxa1);
+ na1 = pixaFindAreaFraction(pixa1);
+ nat = numaCreate(0);
+ numaSetCount(nat, n); /* initialize to all 0 */
+ sum = sumi = 0;
+ pixa3 = pixaCreate(0);
+ for (i = 0; i < 12; i++) {
+ /* Compute within the intervals using an intersection. */
+ na2 = numaMakeThresholdIndicator(na1, edges[i], L_SELECT_IF_GTE);
+ if (i != 11)
+ na3 = numaMakeThresholdIndicator(na1, edges[i + 1], L_SELECT_IF_LT);
+ else
+ na3 = numaMakeThresholdIndicator(na1, edges[i + 1],
+ L_SELECT_IF_LTE);
+ na4 = numaLogicalOp(NULL, na2, na3, L_INTERSECTION);
+ sum += Count_ones(rp, na4, 0, 0, NULL);
+
+ /* Compute outside the intervals using a union, and invert */
+ na2i = numaMakeThresholdIndicator(na1, edges[i], L_SELECT_IF_LT);
+ if (i != 11)
+ na3i = numaMakeThresholdIndicator(na1, edges[i + 1],
+ L_SELECT_IF_GTE);
+ else
+ na3i = numaMakeThresholdIndicator(na1, edges[i + 1],
+ L_SELECT_IF_GT);
+ na4i = numaLogicalOp(NULL, na3i, na2i, L_UNION);
+ numaInvert(na4i, na4i);
+ sumi += Count_ones(rp, na4i, 0, 0, NULL);
+
+ /* Compare the two methods */
+ if (sum != sumi)
+ lept_stderr("WRONG: sum = %d, sumi = %d\n", sum, sumi);
+
+ /* Reconstruct the image, band by band. */
+ numaLogicalOp(nat, nat, na4, L_UNION);
+ pixa2 = pixaSelectWithIndicator(pixa1, na4, NULL);
+ pix3 = pixaDisplay(pixa2, w, h);
+ pixOr(pix2, pix2, pix3); /* add them in */
+ pix4 = pixCopy(NULL, pix2); /* destroyed by Count_pieces */
+ Count_ones(rp, na4, band[i], i, "band");
+ Count_pieces(rp, pix3, band[i]);
+ Count_ones(rp, nat, total[i], i, "total");
+ Count_pieces(rp, pix4, total[i]);
+ pixaDestroy(&pixa2);
+
+ /* Remove band successively from full image */
+ pixRemoveWithIndicator(pix1, pixa1, na4);
+ pixaAddPix(pixa3, pix1, L_COPY);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+ numaDestroy(&na2i);
+ numaDestroy(&na3i);
+ numaDestroy(&na4i);
+ }
+
+ /* Did we remove all components from pix1? */
+ pixZero(pix1, &empty);
+ regTestCompareValues(rp, 1, empty, 0.0);
+ if (!empty)
+ lept_stderr("\nWRONG: not all pixels removed from pix1\n");
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxaDestroy(&boxa1);
+ pixaDestroy(&pixa1);
+ numaDestroy(&na1);
+ numaDestroy(&nat);
+
+ /* One last extraction. Get all components that have either
+ * a height of at least 50 or a width of between 30 and 35,
+ * and also have a relatively large perimeter/area ratio. */
+ pixs = pixRead("feyn.tif");
+ boxa1 = pixConnComp(pixs, &pixa1, 8);
+ n = boxaGetCount(boxa1);
+ pixaFindDimensions(pixa1, &naw, &nah);
+ na1 = pixaFindPerimToAreaRatio(pixa1);
+ na2 = numaMakeThresholdIndicator(nah, 50, L_SELECT_IF_GTE);
+ na3 = numaMakeThresholdIndicator(naw, 30, L_SELECT_IF_GTE);
+ na4 = numaMakeThresholdIndicator(naw, 35, L_SELECT_IF_LTE);
+ na5 = numaMakeThresholdIndicator(na1, 0.4, L_SELECT_IF_GTE);
+ numaLogicalOp(na3, na3, na4, L_INTERSECTION);
+ numaLogicalOp(na2, na2, na3, L_UNION);
+ numaLogicalOp(na2, na2, na5, L_INTERSECTION);
+ numaInvert(na2, na2); /* get components to be removed */
+ pixRemoveWithIndicator(pixs, pixa1, na2);
+ regTestWritePixAndCheck(rp, pixs, IFF_PNG); /* 86 */
+ pixaAddPix(pixa3, pixs, L_INSERT);
+ boxaDestroy(&boxa1);
+ pixaDestroy(&pixa1);
+ numaDestroy(&naw);
+ numaDestroy(&nah);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+ numaDestroy(&na5);
+
+ if (rp->display) {
+ pix1 = pixaDisplayTiledInColumns(pixa3, 2, 0.25, 25, 2);
+ pixDisplay(pix1, 100, 100);
+ pixWrite("/tmp/lept/filter/result.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa3);
+
+ return regTestCleanup(rp);
+}
+
+
+/* --------------------- Helpers -------------------------- */
+
+static void
+Count_pieces(L_REGPARAMS *rp, PIX *pix, l_int32 nexp)
+{
+l_int32 n;
+BOXA *boxa;
+
+ if (rp->index > 28 && rp->index < 55)
+ regTestWritePixAndCheck(rp, pix, IFF_PNG); /* ... */
+ boxa = pixConnComp(pix, NULL, 8);
+ n = boxaGetCount(boxa);
+ regTestCompareValues(rp, nexp, n, 0.0);
+ if (n != nexp)
+ lept_stderr("WRONG!: Num. comps = %d; expected = %d\n", n, nexp);
+ boxaDestroy(&boxa);
+ pixDestroy(&pix);
+}
+
+static void
+Count_pieces2(L_REGPARAMS *rp, BOXA *boxa, l_int32 nexp)
+{
+l_int32 n;
+
+ n = boxaGetCount(boxa);
+ regTestCompareValues(rp, nexp, n, 0.0);
+ if (n != nexp)
+ lept_stderr("WRONG!: Num. boxes = %d; expected = %d\n", n, nexp);
+ boxaDestroy(&boxa);
+}
+
+static l_int32
+Count_ones(L_REGPARAMS *rp, NUMA *na, l_int32 nexp,
+ l_int32 index, const char *name)
+{
+l_int32 i, n, val, sum;
+
+ n = numaGetCount(na);
+ sum = 0;
+ for (i = 0; i < n; i++) {
+ numaGetIValue(na, i, &val);
+ if (val == 1) sum++;
+ }
+ if (!name) return sum;
+ regTestCompareValues(rp, nexp, sum, 0.0);
+ if (nexp != sum)
+ lept_stderr("WRONG! %s[%d]: num. ones = %d; expected = %d\n",
+ name, index, sum, nexp);
+ return 0;
+}
diff --git a/leptonica/prog/concatpdf.c b/leptonica/prog/concatpdf.c
new file mode 100644
index 00000000..2d3976b0
--- /dev/null
+++ b/leptonica/prog/concatpdf.c
@@ -0,0 +1,177 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * concatpdf.c
+ *
+ * This program concatenates all pdfs in a directory by rendering them
+ * as images, optionally scaling the images, and generating an output pdf.
+ * The pdfs are taken in lexical order.
+ *
+ * It makes no other changes to the images, which are rendered
+ * by Poppler's pdftoppm. Compare with cleanpdf.c, which carries
+ * out several operations to make high resolution, 1 bpp g4-tiff
+ * encoded images in the pdf.
+ *
+ * Syntax: cconcatpdf basedir scalefactor outfile
+ *
+ * The %basedir is a directory where the input pdf files are located.
+ * The program will operate on every file in this directory with
+ * the ".pdf" extension.
+ *
+ * The %scalefactor is typically used to downscale the image to
+ * reduce the size of the generated pdf. It should not affect the
+ * pdf display otherwise. For normal text on images scanned at 300 ppi,
+ * a 2x reduction (%scalefactor = 0.5) may be satisfactory.
+ * We compute an output resolution for that pdf that will cause it
+ * to print 11 inches high, based on the height in pixels of the
+ * first image in the set.
+ *
+ * The pdf encoding for each page is chosen by the default mechanism.
+ * See selectDefaultPdfEncoding() for details.
+ * If DCT encoding (jpeg) is used, the quality factor is set to 50.
+ * This makes smaller files with (usually) decent image quality.
+ *
+ * The pdf output is written to %outfile. It is advisable (but not
+ * required) to have a '.pdf' extension.
+ *
+ * N.B. This requires the Poppler package of pdf utilities, such as
+ * pdfimages and pdftoppm. For non-unix systems, this requires
+ * installation of the cygwin Poppler package:
+ * https://cygwin.com/cgi-bin2/package-cat.cgi?file=x86/poppler/
+ * poppler-0.26.5-1
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#ifdef _WIN32
+# if defined(_MSC_VER) || defined(__MINGW32__)
+# include <direct.h>
+# else
+# include <io.h>
+# endif /* _MSC_VER || __MINGW32__ */
+#endif /* _WIN32 */
+
+#include "string.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "allheaders.h"
+
+l_int32 main(int argc,
+ char **argv)
+{
+char buf[256];
+char *basedir, *fname, *tail, *basename, *imagedir, *outfile;
+l_int32 res, i, n, ret;
+l_float32 scalefactor;
+PIX *pixs, *pix1;
+PIXA *pixa1;
+SARRAY *sa;
+static char mainName[] = "concatpdf";
+
+ if (argc != 4)
+ return ERROR_INT(
+ "Syntax: concatpdf basedir scalefactor outfile",
+ mainName, 1);
+ basedir = argv[1];
+ scalefactor = atof(argv[2]);
+ outfile = argv[3];
+ setLeptDebugOK(1);
+
+#if 1
+ /* Get the names of the pdf files */
+ if ((sa = getSortedPathnamesInDirectory(basedir, "pdf", 0, 0)) == NULL)
+ return ERROR_INT("files not found", mainName, 1);
+ sarrayWriteStream(stderr, sa);
+ n = sarrayGetCount(sa);
+#endif
+
+ /* Rasterize:
+ * pdftoppm -r 150 fname outroot
+ * Use of pdftoppm:
+ * This works on all pdf pages, both wrapped images and pages that
+ * were made orthographically. We use the default output resolution
+ * of 150 ppi for pdftoppm, which makes uncompressed 6 MB files
+ * and is very fast. If you want higher resolution 1 bpp output,
+ * use cleanpdf.c. */
+ imagedir = stringJoin(basedir, "/image");
+#if 1
+#ifndef _WIN32
+ mkdir(imagedir, 0777);
+#else
+ _mkdir(imagedir);
+#endif /* _WIN32 */
+ for (i = 0; i < n; i++) {
+ fname = sarrayGetString(sa, i, L_NOCOPY);
+ splitPathAtDirectory(fname, NULL, &tail);
+ splitPathAtExtension(tail, &basename, NULL);
+ snprintf(buf, sizeof(buf), "pdftoppm -r 150 %s %s/%s",
+ fname, imagedir, basename);
+ lept_free(tail);
+ lept_free(basename);
+ lept_stderr("%s\n", buf);
+ ret = system(buf);
+ }
+ sarrayDestroy(&sa);
+#endif
+
+#if 1
+ /* Scale and collect */
+ sa = getSortedPathnamesInDirectory(imagedir, NULL, 0, 0);
+ sarrayWriteStream(stderr, sa);
+ n = sarrayGetCount(sa);
+ pixa1 = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ fname = sarrayGetString(sa, i, L_NOCOPY);
+ pixs = pixRead(fname);
+ if (scalefactor == 1.0)
+ pix1 = pixClone(pixs);
+ else
+ pix1 = pixScale(pixs, scalefactor, scalefactor);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixDestroy(&pixs);
+ }
+ sarrayDestroy(&sa);
+#endif
+
+#if 1
+ /* Generate the pdf. Compute the actual input resolution from
+ * the pixel dimensions of the first image. This will cause each
+ * page to be printed to cover an 8.5 x 11 inch sheet of paper. */
+ lept_stderr("Write output to %s\n", outfile);
+ pix1 = pixaGetPix(pixa1, 0, L_CLONE);
+ pixInferResolution(pix1, 11.0, &res);
+ pixDestroy(&pix1);
+ pixaConvertToPdf(pixa1, res, 1.0, L_DEFAULT_ENCODE, 50, NULL, outfile);
+ pixaDestroy(&pixa1);
+#endif
+
+ return 0;
+}
+
+
diff --git a/leptonica/prog/conncomp_reg.c b/leptonica/prog/conncomp_reg.c
new file mode 100644
index 00000000..a63569d8
--- /dev/null
+++ b/leptonica/prog/conncomp_reg.c
@@ -0,0 +1,163 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Regression test for connected components (both 4 and 8
+ * connected), including regeneration of the original
+ * image from the components. This is also an implicit
+ * test of rasterop.
+ *
+ * Also tests iterative covering of connected components by
+ * minimum spanning rectangles.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_uint8 *array1, *array2;
+l_int32 i, n1, n2, n3;
+size_t size1, size2;
+FILE *fp;
+BOXA *boxa1, *boxa2;
+PIX *pixs, *pix1, *pix2, *pix3;
+PIXA *pixa1;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn.tif");
+
+ /* --------------------------------------------------------------- *
+ * Test pixConnComp() and pixCountConnComp(), *
+ * with output to both boxa and pixa *
+ * --------------------------------------------------------------- */
+ /* First, test with 4-cc */
+ boxa1= pixConnComp(pixs, &pixa1, 4);
+ n1 = boxaGetCount(boxa1);
+ boxa2= pixConnComp(pixs, NULL, 4);
+ n2 = boxaGetCount(boxa2);
+ pixCountConnComp(pixs, 4, &n3);
+ lept_stderr("Number of 4 c.c.: n1 = %d; n2 = %d, n3 = %d\n", n1, n2, n3);
+ regTestCompareValues(rp, n1, n2, 0); /* 0 */
+ regTestCompareValues(rp, n1, n3, 0); /* 1 */
+ regTestCompareValues(rp, n1, 4452, 0); /* 2 */
+ pix1 = pixaDisplay(pixa1, pixGetWidth(pixs), pixGetHeight(pixs));
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 3 */
+ regTestComparePix(rp, pixs, pix1); /* 4 */
+ pixaDestroy(&pixa1);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ pixDestroy(&pix1);
+
+ /* Test with 8-cc */
+ boxa1= pixConnComp(pixs, &pixa1, 8);
+ n1 = boxaGetCount(boxa1);
+ boxa2= pixConnComp(pixs, NULL, 8);
+ n2 = boxaGetCount(boxa2);
+ pixCountConnComp(pixs, 8, &n3);
+ lept_stderr("Number of 8 c.c.: n1 = %d; n2 = %d, n3 = %d\n", n1, n2, n3);
+ regTestCompareValues(rp, n1, n2, 0); /* 5 */
+ regTestCompareValues(rp, n1, n3, 0); /* 6 */
+ regTestCompareValues(rp, n1, 4305, 0); /* 7 */
+ pix1 = pixaDisplay(pixa1, pixGetWidth(pixs), pixGetHeight(pixs));
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 8 */
+ regTestComparePix(rp, pixs, pix1); /* 9 */
+ pixaDestroy(&pixa1);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ pixDestroy(&pix1);
+
+ /* --------------------------------------------------------------- *
+ * Test boxa I/O *
+ * --------------------------------------------------------------- */
+ lept_mkdir("lept/conn");
+ boxa1 = pixConnComp(pixs, NULL, 4);
+ fp = lept_fopen("/tmp/lept/conn/boxa1.ba", "wb+");
+ boxaWriteStream(fp, boxa1);
+ lept_fclose(fp);
+ fp = lept_fopen("/tmp/lept/conn/boxa1.ba", "rb");
+ boxa2 = boxaReadStream(fp);
+ lept_fclose(fp);
+ fp = lept_fopen("/tmp/lept/conn/boxa2.ba", "wb+");
+ boxaWriteStream(fp, boxa2);
+ lept_fclose(fp);
+ array1 = l_binaryRead("/tmp/lept/conn/boxa1.ba", &size1);
+ array2 = l_binaryRead("/tmp/lept/conn/boxa2.ba", &size2);
+ regTestCompareStrings(rp, array1, size1, array2, size2); /* 10 */
+ lept_free(array1);
+ lept_free(array2);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+
+ /* --------------------------------------------------------------- *
+ * Just for fun, display each component as a random color in *
+ * cmapped 8 bpp. Background is color 0; it is set to white. *
+ * --------------------------------------------------------------- */
+ boxa1 = pixConnComp(pixs, &pixa1, 4);
+ pix1 = pixaDisplayRandomCmap(pixa1, pixGetWidth(pixs), pixGetHeight(pixs));
+ cmap = pixGetColormap(pix1);
+ pixcmapResetColor(cmap, 0, 255, 255, 255); /* reset background to white */
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 11 */
+ if (rp->display) pixDisplay(pix1, 100, 0);
+ boxaDestroy(&boxa1);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+ pixDestroy(&pixs);
+
+ /* --------------------------------------------------------------- *
+ * Test iterative covering of connected components by rectangles *
+ * --------------------------------------------------------------- */
+ pixa1 = pixaCreate(0);
+ pix1 = pixRead("rabi.png");
+ pix2 = pixReduceRankBinaryCascade(pix1, 1, 1, 1, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 12 - */
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ for (i = 1; i < 6; i++) {
+ pix3 = pixMakeCoveringOfRectangles(pix2, i);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 13 - 17 */
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ }
+ pix3 = pixaDisplayTiledInRows(pixa1, 1, 2500, 1.0, 0, 30, 0);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 18 */
+ pixDisplayWithTitle(pix3, 100, 900, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa1);
+
+ return regTestCleanup(rp);
+}
+
+
diff --git a/leptonica/prog/contrast-orig-60.jpg b/leptonica/prog/contrast-orig-60.jpg
new file mode 100644
index 00000000..a9772c50
--- /dev/null
+++ b/leptonica/prog/contrast-orig-60.jpg
Binary files differ
diff --git a/leptonica/prog/contrast1.jpg b/leptonica/prog/contrast1.jpg
new file mode 100644
index 00000000..a185e686
--- /dev/null
+++ b/leptonica/prog/contrast1.jpg
Binary files differ
diff --git a/leptonica/prog/contrasttest.c b/leptonica/prog/contrasttest.c
new file mode 100644
index 00000000..49cfef5d
--- /dev/null
+++ b/leptonica/prog/contrasttest.c
@@ -0,0 +1,94 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * contrasttest.c
+ *
+ * This appplies @factor contrast enhancement to the input image.
+ * It also plots atan curves for different width parameters.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+char buf[512];
+l_int32 iplot;
+l_float32 factor; /* scaled width of atan curve */
+l_float32 fact[] = {0.2f, 0.4f, 0.6f, 0.8f, 1.0f, -1.0f};
+GPLOT *gplot;
+NUMA *na, *nax;
+PIX *pixs;
+static char mainName[] = "contrasttest";
+
+ if (argc != 4)
+ return ERROR_INT(" Syntax: contrasttest filein factor fileout",
+ mainName, 1);
+ filein = argv[1];
+ factor = atof(argv[2]);
+ fileout = argv[3];
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/contrast");
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ na = numaContrastTRC(factor);
+ gplotSimple1(na, GPLOT_PNG, "/tmp/lept/contrast/trc1", "contrast trc");
+ l_fileDisplay("/tmp/lept/contrast/trc1.png", 0, 100, 1.0);
+ numaDestroy(&na);
+
+ /* Plot contrast TRC maps */
+ nax = numaMakeSequence(0.0, 1.0, 256);
+ gplot = gplotCreate("/tmp/lept/contrast/trc2", GPLOT_PNG,
+ "Atan mapping function for contrast enhancement",
+ "value in", "value out");
+ for (iplot = 0; fact[iplot] >= 0.0; iplot++) {
+ na = numaContrastTRC(fact[iplot]);
+ snprintf(buf, sizeof(buf), "factor = %3.1f", fact[iplot]);
+ gplotAddPlot(gplot, nax, na, GPLOT_LINES, buf);
+ numaDestroy(&na);
+ }
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ l_fileDisplay("/tmp/lept/contrast/trc2.png", 600, 100, 1.0);
+ numaDestroy(&nax);
+
+ /* Apply the input contrast enhancement */
+ pixContrastTRC(pixs, pixs, factor);
+ pixWrite(fileout, pixs, IFF_PNG);
+ pixDestroy(&pixs);
+ return 0;
+}
+
diff --git a/leptonica/prog/conversion_reg.c b/leptonica/prog/conversion_reg.c
new file mode 100644
index 00000000..dee21847
--- /dev/null
+++ b/leptonica/prog/conversion_reg.c
@@ -0,0 +1,424 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * conversion_reg.c
+ *
+ * Regression test for depth conversion functions,
+ * including some of the octcube quantization.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *errorstr;
+l_int32 same, error;
+PIX *pixs1, *pixs2, *pixs4, *pixs8, *pixs16, *pixs32;
+PIX *pixc2, *pixc4, *pixc8;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+PIXA *pixa;
+PIXCMAP *cmap;
+SARRAY *sa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs1 = pixRead("test1.png");
+ pixs2 = pixRead("dreyfus2.png");
+ pixc2 = pixRead("weasel2.4c.png");
+ pixs4 = pixRead("weasel4.16g.png");
+ pixc4 = pixRead("weasel4.11c.png");
+ pixs8 = pixRead("karen8.jpg");
+ pixc8 = pixRead("weasel8.240c.png");
+ pixs16 = pixRead("test16.tif");
+ pixs32 = pixRead("marge.jpg");
+ error = FALSE;
+ sa = sarrayCreate(0);
+
+ /* Conversion: 1 bpp --> 8 bpp --> 1 bpp */
+ pix1 = pixConvertTo8(pixs1, FALSE);
+ pix2 = pixThreshold8(pix1, 1, 0, 0);
+ regTestComparePix(rp, pixs1, pix2); /* 0 */
+ pixEqual(pixs1, pix2, &same);
+ if (!same) {
+ pixDisplayWithTitle(pixs1, 100, 100, "1 bpp, no cmap", 1);
+ pixDisplayWithTitle(pix2, 500, 100, "1 bpp, no cmap", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 1 bpp <==> 8 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 1 bpp <==> 8 bpp\n");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Conversion: 2 bpp --> 8 bpp --> 2 bpp */
+ /* Conversion: 2 bpp cmap --> 8 bpp cmap --> 2 bpp cmap */
+ pix1 = pixRemoveColormap(pixs2, REMOVE_CMAP_TO_GRAYSCALE);
+ pix2 = pixThreshold8(pix1, 2, 4, 0);
+ pix3 = pixConvertTo8(pix2, FALSE);
+ pix4 = pixThreshold8(pix3, 2, 4, 0);
+ regTestComparePix(rp, pix2, pix4); /* 1 */
+ pixEqual(pix2, pix4, &same);
+ if (!same) {
+ pixDisplayWithTitle(pix2, 100, 100, "2 bpp, no cmap", 1);
+ pixDisplayWithTitle(pix4, 500, 100, "2 bpp, no cmap", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 2 bpp <==> 8 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 2 bpp <==> 8 bpp\n");
+ }
+ pix5 = pixConvertTo8(pixs2, TRUE);
+ pix6 = pixThreshold8(pix5, 2, 4, 1);
+ regTestComparePix(rp, pixs2, pix6); /* 2 */
+ pixEqual(pixs2, pix6, &same);
+ if (!same) {
+ pixDisplayWithTitle(pixs2, 100, 100, "2 bpp, cmap", 1);
+ pixDisplayWithTitle(pix6, 500, 100, "2 bpp, cmap", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 2 bpp <==> 8 bpp; cmap",
+ L_COPY);
+ } else {
+ lept_stderr("OK: conversion 2 bpp <==> 8 bpp; cmap\n");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+
+ /* Conversion: 4 bpp --> 8 bpp --> 4 bpp */
+ /* Conversion: 4 bpp cmap --> 8 bpp cmap --> 4 bpp cmap */
+ pix1 = pixRemoveColormap(pixs4, REMOVE_CMAP_TO_GRAYSCALE);
+ pix2 = pixThreshold8(pix1, 4, 16, 0);
+ pix3 = pixConvertTo8(pix2, FALSE);
+ pix4 = pixThreshold8(pix3, 4, 16, 0);
+ regTestComparePix(rp, pix2, pix4); /* 3 */
+ pixEqual(pix2, pix4, &same);
+ if (!same) {
+ pixDisplayWithTitle(pix2, 100, 100, "4 bpp, no cmap", 1);
+ pixDisplayWithTitle(pix4, 500, 100, "4 bpp, no cmap", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 4 bpp <==> 8 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 4 bpp <==> 8 bpp\n");
+ }
+ pix5 = pixConvertTo8(pixs4, TRUE);
+ pix6 = pixThreshold8(pix5, 4, 16, 1);
+ regTestComparePix(rp, pixs4, pix6); /* 4 */
+ pixEqual(pixs4, pix6, &same);
+ if (!same) {
+ pixDisplayWithTitle(pixs4, 100, 100, "4 bpp, cmap", 1);
+ pixDisplayWithTitle(pix6, 500, 100, "4 bpp, cmap", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 4 bpp <==> 8 bpp, cmap",
+ L_COPY);
+ } else {
+ lept_stderr("OK: conversion 4 bpp <==> 8 bpp; cmap\n");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+
+ /* Conversion: 2 bpp cmap --> 2 bpp --> 2 bpp cmap --> 2 bpp */
+ pix1 = pixRemoveColormap(pixs2, REMOVE_CMAP_TO_GRAYSCALE);
+ pix2 = pixConvertGrayToColormap(pix1);
+ pix3 = pixRemoveColormap(pix2, REMOVE_CMAP_TO_GRAYSCALE);
+ pix4 = pixThresholdTo2bpp(pix3, 4, 1);
+ regTestComparePix(rp, pix1, pix4); /* 5 */
+ pixEqual(pix1, pix4, &same);
+ if (!same) {
+ pixDisplayWithTitle(pixs2, 100, 100, "2 bpp, cmap", 1);
+ pixDisplayWithTitle(pix4, 500, 100, "2 bpp, cmap", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 2 bpp <==> 2 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 2 bpp <==> 2 bpp\n");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Conversion: 4 bpp cmap --> 4 bpp --> 4 bpp cmap --> 4 bpp */
+ pix1 = pixRemoveColormap(pixs4, REMOVE_CMAP_TO_GRAYSCALE);
+ pix2 = pixConvertGrayToColormap(pix1);
+ pix3 = pixRemoveColormap(pix2, REMOVE_CMAP_TO_GRAYSCALE);
+ pix4 = pixThresholdTo4bpp(pix3, 16, 1);
+ regTestComparePix(rp, pix1, pix4); /* 6 */
+ pixEqual(pix1, pix4, &same);
+ if (!same) {
+ pixDisplayWithTitle(pixs4, 100, 100, "4 bpp, cmap", 1);
+ pixDisplayWithTitle(pix4, 500, 100, "4 bpp, cmap", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 4 bpp <==> 4 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 4 bpp <==> 4 bpp\n");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Conversion: 8 bpp --> 8 bpp cmap --> 8 bpp */
+ pix1 = pixConvertTo8(pixs8, TRUE);
+ pix2 = pixConvertTo8(pix1, FALSE);
+ regTestComparePix(rp, pixs8, pix2); /* 7 */
+ pixEqual(pixs8, pix2, &same);
+ if (!same) {
+ pixDisplayWithTitle(pix1, 100, 100, "8 bpp, cmap", 1);
+ pixDisplayWithTitle(pix2, 500, 100, "8 bpp, no cmap", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 8 bpp <==> 8 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 8 bpp <==> 8 bpp\n");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Conversion: 2 bpp cmap --> 32 bpp --> 2 bpp cmap */
+ pix1 = pixConvertTo8(pixc2, TRUE);
+ pix2 = pixConvertTo32(pix1);
+ pix3 = pixConvertTo32(pixc2);
+ regTestComparePix(rp, pix2, pix3); /* 8 */
+ pixEqual(pix2, pix3, &same);
+ if (!same) {
+ pixDisplayWithTitle(pix2, 100, 100, "32 bpp", 1);
+ pixDisplayWithTitle(pix3, 500, 100, "32 bpp", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 2 bpp ==> 32 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 2 bpp <==> 32 bpp\n");
+ }
+ cmap = pixGetColormap(pixc2);
+ pix4 = pixOctcubeQuantFromCmap(pix3, cmap, 2, 4, L_EUCLIDEAN_DISTANCE);
+ regTestComparePix(rp, pixc2, pix4); /* 9 */
+ pixEqual(pixc2, pix4, &same);
+ if (!same) {
+ pixDisplayWithTitle(pixc2, 100, 100, "4 bpp, cmap", 1);
+ pixDisplayWithTitle(pix4, 500, 100, "4 bpp, cmap", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 2 bpp <==> 32 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 2 bpp <==> 32 bpp\n");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Conversion: 4 bpp cmap --> 32 bpp --> 4 bpp cmap */
+ pix1 = pixConvertTo8(pixc4, TRUE);
+ pix2 = pixConvertTo32(pix1);
+ pix3 = pixConvertTo32(pixc4);
+ regTestComparePix(rp, pix2, pix3); /* 10 */
+ pixEqual(pix2, pix3, &same);
+ if (!same) {
+ pixDisplayWithTitle(pix2, 100, 100, "32 bpp", 1);
+ pixDisplayWithTitle(pix3, 500, 100, "32 bpp", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 4 bpp ==> 32 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 4 bpp <==> 32 bpp\n");
+ }
+ cmap = pixGetColormap(pixc4);
+ pix4 = pixOctcubeQuantFromCmap(pix3, cmap, 2, 4, L_EUCLIDEAN_DISTANCE);
+ regTestComparePix(rp, pixc4, pix4); /* 11 */
+ pixEqual(pixc4, pix4, &same);
+ if (!same) {
+ pixDisplayWithTitle(pixc4, 100, 100, "4 bpp, cmap", 1);
+ pixDisplayWithTitle(pix4, 500, 100, "4 bpp, cmap", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 4 bpp <==> 32 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 4 bpp <==> 32 bpp\n");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Conversion: 8 bpp --> 32 bpp --> 8 bpp */
+ pix1 = pixConvertTo32(pixs8);
+ pix2 = pixConvertTo8(pix1, FALSE);
+ regTestComparePix(rp, pixs8, pix2); /* 12 */
+ pixEqual(pixs8, pix2, &same);
+ if (!same) {
+ pixDisplayWithTitle(pixs8, 100, 100, "8 bpp", 1);
+ pixDisplayWithTitle(pix2, 500, 100, "8 bpp", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 8 bpp <==> 32 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 8 bpp <==> 32 bpp\n");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Conversion: 8 bpp --> 16 bpp --> 8 bpp */
+ pix1 = pixConvert8To16(pixs8, 8);
+ pix2 = pixConvertTo8(pix1, FALSE);
+ regTestComparePix(rp, pixs8, pix2); /* 13 */
+ pixEqual(pixs8, pix2, &same);
+ if (!same) {
+ pixDisplayWithTitle(pixs8, 100, 100, "8 bpp", 1);
+ pixDisplayWithTitle(pix2, 500, 100, "8 bpp", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 8 bpp <==> 16 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 8 bpp <==> 16 bpp\n");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Conversion: 16 bpp --> 8 bpp --> 16 bpp */
+ pix1 = pixConvert16To8(pixs16, 1);
+ pix2 = pixConvertTo16(pix1);
+ regTestComparePix(rp, pixs16, pix2); /* 14 */
+ pixEqual(pixs16, pix2, &same);
+ if (!same) {
+ pixDisplayWithTitle(pixs16, 100, 100, "16 bpp", 1);
+ pixDisplayWithTitle(pix2, 500, 100, "16 bpp", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 16 bpp <==> 8 bpp", L_COPY);
+ } else {
+ lept_stderr("OK: conversion 16 bpp <==> 8 bpp\n");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Conversion: 8 bpp cmap --> 32 bpp --> 8 bpp cmap */
+ /* Required to go to level 6 of octcube to get identical result */
+ pix1 = pixConvertTo32(pixc8);
+ cmap = pixGetColormap(pixc8);
+ pix2 = pixOctcubeQuantFromCmap(pix1, cmap, 2, 6, L_EUCLIDEAN_DISTANCE);
+ regTestComparePix(rp, pixc8, pix2); /* 15 */
+ pixEqual(pixc8, pix2, &same);
+ if (!same) {
+ pixDisplayWithTitle(pixc8, 100, 100, "8 bpp cmap", 1);
+ pixDisplayWithTitle(pix2, 500, 100, "8 bpp cmap", 1);
+ error = TRUE;
+ sarrayAddString(sa, "conversion 8 bpp cmap <==> 32 bpp cmap",
+ L_COPY);
+ } else {
+ lept_stderr("OK: conversion 8 bpp <==> 32 bpp\n");
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Summarize results so far */
+ if (error == FALSE) {
+ lept_stderr("No errors found\n");
+ } else {
+ errorstr = sarrayToString(sa, 1);
+ lept_stderr("Errors in the following:\n %s", errorstr);
+ lept_free(errorstr);
+ }
+
+ /* General onversion to 2 bpp */
+ pixa = pixaCreate(0);
+ pix1 = pixConvertTo2(pixs1);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 16 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo2(pixs2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 17 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo2(pixc2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 18 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo2(pixs4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 19 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo2(pixc4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 20 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo2(pixs8);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 21 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo2(pixc8);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 22 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo2(pixs32);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 23 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixaDisplayTiledAndScaled(pixa, 32, 300, 4, 0, 30, 2);
+ pixDisplayWithTitle(pix2, 500, 0, NULL, rp->display);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa);
+
+ /* General onversion to 4 bpp */
+ pixa = pixaCreate(0);
+ pix1 = pixConvertTo4(pixs1);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 24 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo4(pixs2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 25 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo4(pixc2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 26 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo4(pixs4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 27 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo4(pixc4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 28 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo4(pixs8);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 29 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo4(pixc8);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 30 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixConvertTo4(pixs32);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 31 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixaDisplayTiledAndScaled(pixa, 32, 300, 4, 0, 30, 2);
+ pixDisplayWithTitle(pix2, 500, 750, NULL, rp->display);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa);
+
+ sarrayDestroy(&sa);
+ pixDestroy(&pixs1);
+ pixDestroy(&pixs2);
+ pixDestroy(&pixs4);
+ pixDestroy(&pixc2);
+ pixDestroy(&pixc4);
+ pixDestroy(&pixs8);
+ pixDestroy(&pixc8);
+ pixDestroy(&pixs16);
+ pixDestroy(&pixs32);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/convertfilestopdf.c b/leptonica/prog/convertfilestopdf.c
new file mode 100644
index 00000000..ac6f5fd5
--- /dev/null
+++ b/leptonica/prog/convertfilestopdf.c
@@ -0,0 +1,109 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * convertfilestopdf.c
+ *
+ * Converts all image files in the given directory with matching substring
+ * to a pdf, with the specified scaling factor <= 1.0 applied to all
+ * images.
+ *
+ * See below for syntax and usage.
+ *
+ * The images are displayed at a resolution that depends on the
+ * input resolution (res) and the scaling factor (scalefact) that
+ * is applied to the images before conversion to pdf. Internally
+ * we multiply these, so that the generated pdf will render at the
+ * same resolution as if it hadn't been scaled. By downscaling, you
+ * reduce the size of the images.
+ *
+ * For jpeg and jp2k, downscaling reduces pdf size by the square of
+ * the scale factor.
+ * * The jpeg quality can be specified from 1 (very poor) to 100
+ * (best available, but still lossy); use 0 for the default (75).
+ * * The jp2k quality can be specified from 27 (very poor) to 45 (nearly
+ * lossless; use 0 for the default (34). You can use 100 to
+ * require lossless, but this is very expensive and not recommended.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *dirin, *substr, *title, *fileout;
+l_int32 ret, res, type, quality;
+l_float32 scalefactor;
+static char mainName[] = "convertfilestopdf";
+
+ if (argc != 9) {
+ lept_stderr(
+ " Syntax: convertfilestopdf dirin substr res"
+ " scalefactor encoding_type quality title fileout\n"
+ " dirin: input directory for image files\n"
+ " substr: Use 'allfiles' to convert all files\n"
+ " in the directory.\n"
+ " res: Input resolution of each image;\n"
+ " assumed to all be the same\n"
+ " scalefactor: Use to scale all images\n"
+ " encoding_type:\n"
+ " L_JPEG_ENCODE = 1\n"
+ " L_G4_ENCODE = 2\n"
+ " L_FLATE_ENCODE = 3\n"
+ " L_JP2K_ENCODE = 4, or 0 for per-page default)\n"
+ " quality: used for jpeg; 1-100, 0 for default (75);\n"
+ " used for jp2k: 27-45, 0 for default (34)\n"
+ " title: Use 'none' to omit\n"
+ " fileout: Output pdf file\n");
+ return 1;
+ }
+ dirin = argv[1];
+ substr = argv[2];
+ res = atoi(argv[3]);
+ scalefactor = atof(argv[4]);
+ type = atoi(argv[5]);
+ quality = atoi(argv[6]);
+ title = argv[7];
+ fileout = argv[8];
+ if (!strcmp(substr, "allfiles"))
+ substr = NULL;
+ if (scalefactor <= 0.0 || scalefactor > 2.0) {
+ L_WARNING("invalid scalefactor: setting to 1.0\n", mainName);
+ scalefactor = 1.0;
+ }
+ if (!strcmp(title, "none"))
+ title = NULL;
+
+ setLeptDebugOK(1);
+ ret = convertFilesToPdf(dirin, substr, res, scalefactor, type,
+ quality, title, fileout);
+ return ret;
+}
diff --git a/leptonica/prog/convertfilestops.c b/leptonica/prog/convertfilestops.c
new file mode 100644
index 00000000..d70c27e2
--- /dev/null
+++ b/leptonica/prog/convertfilestops.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * convertfilestops.c
+ *
+ * Converts all files in the given directory with matching substring
+ * to a level 3 compressed PostScript file, at the specified resolution.
+ * To convert all files in the directory, use 'allfiles' for the substring.
+ *
+ * See below for syntax and usage.
+ *
+ * To generate a ps that scales the images to fit a standard 8.5 x 11
+ * page, use res = 0.
+ *
+ * Otherwise, this will convert based on a specified input resolution.
+ * Decreasing the input resolution will cause the image to be rendered
+ * larger, and v.v. For example, if the page was originally scanned
+ * at 400 ppi and you use 300 ppi for the resolution, the page will
+ * be rendered with larger pixels (i.e., be magnified) and you will
+ * lose a quarter of the page on the right side and a quarter
+ * at the bottom.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *dirin, *substr, *fileout;
+l_int32 res;
+
+ if (argc != 5) {
+ lept_stderr(
+ " Syntax: convertfilestops dirin substr res fileout\n"
+ " where\n"
+ " dirin: input directory for image files\n"
+ " substr: Use 'allfiles' to convert all files\n"
+ " in the directory.\n"
+ " res: Input resolution of each image;\n"
+ " assumed to all be the same\n"
+ " fileout: Output ps file.\n");
+ return 1;
+ }
+ dirin = argv[1];
+ substr = argv[2];
+ res = atoi(argv[3]);
+ fileout = argv[4];
+
+ setLeptDebugOK(1);
+ if (!strcmp(substr, "allfiles"))
+ substr = NULL;
+ if (res != 0)
+ return convertFilesToPS(dirin, substr, res, fileout);
+ else
+ return convertFilesFittedToPS(dirin, substr, 0.0, 0.0, fileout);
+}
diff --git a/leptonica/prog/convertformat.c b/leptonica/prog/convertformat.c
new file mode 100644
index 00000000..38ebd8ef
--- /dev/null
+++ b/leptonica/prog/convertformat.c
@@ -0,0 +1,181 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * convertformat.c
+ *
+ * Converts an image file from one format to another.
+ *
+ * Syntax: convertformat filein fileout [format]
+ *
+ * where format is one of these:
+ *
+ * BMP
+ * JPEG (only applicable for 8 bpp or rgb; if not, transcode to png)
+ * PNG
+ * TIFF
+ * TIFF_G4 (only applicable for 1 bpp; if not, transcode to png)
+ * PNM
+ * GIF
+ * WEBP
+ * JP2 (only available for 8 bpp or rgb; if not, transcode to png)
+ *
+ * The output format can be chosen either explicitly with the %format
+ * arg, or implicitly using the extension of @fileout:
+ *
+ * BMP .bmp
+ * JPEG .jpg
+ * PNG .png
+ * TIFF (zip compressed: use explicitly with %format arg)
+ * TIFFG4 .tif
+ * PNM .pnm
+ * GIF .gif
+ * WEBP .webp
+ * JP2 .jp2
+ *
+ * If the requested output format does not support the image type,
+ * the image is written in png format, with filename extension 'png'.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout, *base, *ext;
+const char *formatstr;
+l_int32 format, d, change;
+PIX *pixs;
+static char mainName[] = "convertformat";
+
+ if (argc != 3 && argc != 4) {
+ lept_stderr("Syntax: convertformat filein fileout [format]\n"
+ "Either specify a format from one of these:\n"
+ " BMP, JPEG, PNG, TIFF, TIFFG4, PNM, GIF, WEBP, JP2\n"
+ "Or specify the extensions to the output file:\n"
+ " bmp, jpg, png, tif, pnm, gif, webp, jp2\n");
+ return 1;
+ }
+ filein = argv[1];
+ fileout = argv[2];
+
+ if (argc == 3) {
+ splitPathAtExtension(fileout, NULL, &ext);
+ if (!strcmp(ext, ".bmp"))
+ format = IFF_BMP;
+ else if (!strcmp(ext, ".jpg"))
+ format = IFF_JFIF_JPEG;
+ else if (!strcmp(ext, ".png"))
+ format = IFF_PNG;
+ else if (!strcmp(ext, ".tif")) /* requesting g4-tiff binary comp */
+ format = IFF_TIFF_G4;
+ else if (!strcmp(ext, ".pnm"))
+ format = IFF_PNM;
+ else if (!strcmp(ext, ".gif"))
+ format = IFF_GIF;
+ else if (!strcmp(ext, ".webp"))
+ format = IFF_WEBP;
+ else if (!strcmp(ext, ".jp2"))
+ format = IFF_JP2;
+ else {
+ return ERROR_INT(
+ "Valid extensions: bmp, jpg, png, tif, pnm, gif, webp, jp2",
+ mainName, 1);
+ }
+ lept_free(ext);
+ }
+ else {
+ formatstr = argv[3];
+ if (!strcmp(formatstr, "BMP"))
+ format = IFF_BMP;
+ else if (!strcmp(formatstr, "JPEG"))
+ format = IFF_JFIF_JPEG;
+ else if (!strcmp(formatstr, "PNG"))
+ format = IFF_PNG;
+ else if (!strcmp(formatstr, "TIFF"))
+ format = IFF_TIFF_ZIP; /* zip compressed tif */
+ else if (!strcmp(formatstr, "TIFFG4"))
+ format = IFF_TIFF_G4;
+ else if (!strcmp(formatstr, "PNM"))
+ format = IFF_PNM;
+ else if (!strcmp(formatstr, "GIF"))
+ format = IFF_GIF;
+ else if (!strcmp(formatstr, "WEBP"))
+ format = IFF_WEBP;
+ else if (!strcmp(formatstr, "JP2"))
+ format = IFF_JP2;
+ else {
+ return ERROR_INT(
+ "Valid formats: BMP, JPEG, PNG, TIFF, TIFFG4, PNM, "
+ "GIF, WEBP, JP2",
+ mainName, 1);
+ }
+ }
+
+ setLeptDebugOK(1);
+ if ((pixs = pixRead(filein)) == NULL) {
+ L_ERROR("read fail for %s\n", mainName, filein);
+ return 1;
+ }
+
+ /* Change output format if necessary */
+ change = FALSE;
+ d = pixGetDepth(pixs);
+ if (d != 1 && format == IFF_TIFF_G4) {
+ L_WARNING("can't convert to tiff_g4; converting to png\n", mainName);
+ change = TRUE;
+ }
+ if (d < 8) {
+ switch(format)
+ {
+ case IFF_JFIF_JPEG:
+ L_WARNING("can't convert to jpeg; converting to png\n", mainName);
+ change = TRUE;
+ break;
+ case IFF_WEBP:
+ L_WARNING("can't convert to webp; converting to png\n", mainName);
+ change = TRUE;
+ break;
+ case IFF_JP2:
+ L_WARNING("can't convert to jp2; converting to png\n", mainName);
+ change = TRUE;
+ break;
+ }
+ }
+ if (change) {
+ splitPathAtExtension(fileout, &base, &ext);
+ fileout = stringJoin(base, ".png");
+ format = IFF_PNG;
+ }
+
+ pixWrite(fileout, pixs, format);
+ return 0;
+}
diff --git a/leptonica/prog/convertsegfilestopdf.c b/leptonica/prog/convertsegfilestopdf.c
new file mode 100644
index 00000000..f79ce8ad
--- /dev/null
+++ b/leptonica/prog/convertsegfilestopdf.c
@@ -0,0 +1,162 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * convertsegfilestopdf.c
+ *
+ * Converts all image files in the page directory with matching substring
+ * to a pdf. Image regions are downscaled by the scalefactor and
+ * encoded as jpeg. Non-image regions with depth > 1 are automatically
+ * scaled up by 2x and thresholded if the encoding type is G4;
+ * otherwise, no scaling is performed on them. To convert all
+ * files in the page directory, use 'allfiles' for its substring.
+ * Likewise to use all files in the mask directory, use 'allfiles'
+ * for its substring.
+ *
+ * A typical invocation would be something like:
+ * convertsegfilestopdf /tmp/segpages allfiles /tmp/segmasks allfiles \
+ * 300 2 160 skip 0.5 [title] [output pdf]
+ * This upscales by 2x all non-image regions to 600 ppi, and downscales
+ * by 0.5 all image regions to 150 ppi.
+ *
+ * If used on a set of images without segmentation data, a typical
+ * invocation would be:
+ * convertsegfilestopdf /tmp/pages allfiles skip skip \
+ * 300 2 160 skip 1.0 [title] [output pdf]
+ * If the page images have depth > 1 bpp, this will upscale all pages
+ * by 2x (to 600 ppi), and then convert the images to 1 bpp.
+ * Note that 'skip' is used three times to omit all segmentation data.
+ *
+ * See below for further syntax and usage.
+ *
+ * Again, note that the image regions are displayed at a resolution
+ * that depends on the input resolution (res) and the scaling factor
+ * (scalefact) that is applied to the images before conversion to pdf.
+ * Internally we multiply these, so that the generated pdf will render
+ * at the same resolution as if it hadn't been scaled. When we
+ * downscale the image regions, this:
+ * (1) reduces the size of the images. For jpeg, downscaling
+ * reduces by square of the scale factor the 'image' segmented part.
+ * (2) regenerates the jpeg with quality = 75 after downscaling.
+ *
+ * If you already have a boxaafile of the image regions, use 'skip' for
+ * maskdir. Otherwise, this will generate the boxaa from the mask images.
+ *
+ * A regression test that uses this is pdfseg_reg, which
+ * generates images and the boxaa file in /tmp/segtest/.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *pagedir, *pagesubstr, *maskdir, *masksubstr;
+char *title, *fileout, *boxaafile, *boxaapath;
+l_int32 ret, res, type, thresh;
+l_float32 scalefactor;
+BOXAA *baa;
+static char mainName[] = "convertsegfilestopdf";
+
+ if (argc != 12) {
+ lept_stderr(
+ " Syntax: convertsegfilestopdf dirin substr res type thresh \\ \n"
+ " boxaafile scalefactor title fileout\n"
+ " where\n"
+ " pagedir: input directory for image files\n"
+ " pagesubstr: Use 'allfiles' to convert all files\n"
+ " in the directory\n"
+ " maskdir: input directory for mask files;\n"
+ " use 'skip' to skip \n"
+ " masksubstr: Use 'allfiles' to convert all files\n"
+ " in the directory; 'skip' to skip\n"
+ " res: Input resolution of each image;\n"
+ " assumed to all be the same\n"
+ " type: compression used for non-image regions:\n"
+ " 0: default (G4 encoding)\n"
+ " 1: JPEG encoding\n"
+ " 2: G4 encoding\n"
+ " 3: PNG encoding\n"
+ " thresh: threshold for binarization; use 0 for default\n"
+ " boxaafile: Optional file of 'image' regions within\n"
+ " each page. This contains a boxa for each\n"
+ " page, consisting of a set of regions.\n"
+ " Use 'skip' to skip.\n"
+ " scalefactor: Use to scale down the image regions\n"
+ " title: Use 'none' to omit\n"
+ " fileout: Output pdf file\n");
+ return 1;
+ }
+ pagedir = argv[1];
+ pagesubstr = argv[2];
+ maskdir = argv[3];
+ masksubstr = argv[4];
+ res = atoi(argv[5]);
+ type = atoi(argv[6]);
+ thresh = atoi(argv[7]);
+ boxaafile = argv[8];
+ scalefactor = atof(argv[9]);
+ title = argv[10];
+ fileout = argv[11];
+
+ if (!strcmp(pagesubstr, "allfiles"))
+ pagesubstr = NULL;
+ if (!strcmp(maskdir, "skip"))
+ maskdir = NULL;
+ if (!strcmp(masksubstr, "allfiles"))
+ masksubstr = NULL;
+ if (scalefactor <= 0.0 || scalefactor > 1.0) {
+ L_WARNING("invalid scalefactor: setting to 1.0\n", mainName);
+ scalefactor = 1.0;
+ }
+ if (type != 1 && type != 2 && type != 3)
+ type = L_G4_ENCODE;
+ if (thresh <= 0)
+ thresh = 150;
+ if (!strcmp(title, "none"))
+ title = NULL;
+
+ setLeptDebugOK(1);
+ if (maskdir) /* use this; ignore any input boxaafile */
+ baa = convertNumberedMasksToBoxaa(maskdir, masksubstr, 0, 0);
+ else if (strcmp(boxaafile, "skip") != 0) { /* use the boxaafile */
+ boxaapath = genPathname(boxaafile, NULL);
+ baa = boxaaRead(boxaapath);
+ lept_free(boxaapath);
+ }
+ else /* no maskdir and no input boxaafile */
+ baa = NULL;
+
+ ret = convertSegmentedFilesToPdf(pagedir, pagesubstr, res, type, thresh,
+ baa, 75, scalefactor, title, fileout);
+ boxaaDestroy(&baa);
+ return ret;
+}
diff --git a/leptonica/prog/convertsegfilestops.c b/leptonica/prog/convertsegfilestops.c
new file mode 100644
index 00000000..da74d425
--- /dev/null
+++ b/leptonica/prog/convertsegfilestops.c
@@ -0,0 +1,137 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * convertsegfilestops.c
+ *
+ * Converts all image files in a 'page' directory, using optional
+ * corresponding segmentation mask files in a 'mask' directory,
+ * to a level 2 compressed PostScript file. This is done
+ * automatically at a resolution that fits to a letter-sized
+ * (8.5 x 11) inch page. The 'page' and 'mask' files are paired
+ * by having the same number embedded in their name.
+ * The 'numpre' and 'numpost' args specify the number of
+ * characters at the beginning and end of the filename (not
+ * counting any extension) that are NOT part of the page number.
+ * For example, if the page numbers are 00000.jpg, 00001.jpg, ...
+ * then numpre = numpost = 0.
+ *
+ * The mask directory must exist, but it does not need to have
+ * any image mask files.
+ *
+ * The pages are taken in lexical order of the filenames. Therefore,
+ * the embedded numbers should be 0-padded on the left up to
+ * a fixed number of digits.
+ *
+ * PostScript (and pdf) allow regions of the image to be encoded
+ * differently. Regions can be over-written, with the last writing
+ * determining the final output. Black "ink" can also be written
+ * through a mask that is given by a 1 bpp image.
+ *
+ * The page images are typically grayscale or color. To take advantage
+ * of this depth, one typically upscales the text by 2.0. Likewise,
+ * the images regions, denoted by foreground in the corresponding
+ * segmentation mask, can be rendered at lower resolution, and
+ * it is often useful to downscale the image parts by 0.5.
+ *
+ * If the mask does not exist, the entire page is interpreted as
+ * text; it is converted to 1 bpp and written to file with
+ * ccitt-g4 compression at the requested "textscale" relative
+ * to the page image. If the mask exists and the foreground
+ * covers the entire page, the entire page is saved with jpeg
+ * ("dct") compression at the requested "imagescale".
+ * If the mask exists and partially covers the page image, the
+ * page is saved as a mixture of grayscale or rgb dct and 1 bpp g4.
+ *
+ * This uses a single global threshold for binarizing the text
+ * (i.e., non-image) regions of every page.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *pagedir, *pagestr, *maskdir, *maskstr, *fileout;
+l_int32 threshold, page_numpre, mask_numpre, numpost, maxnum;
+l_float32 textscale, imagescale;
+
+ if (argc != 13) {
+ lept_stderr(
+ " Syntax: convertsegfilestops pagedir pagestr page_numpre \\ \n"
+ " maskdir maskstr mask_numpre \\ \n"
+ " numpost maxnum textscale \\ \n"
+ " imagescale thresh fileout\n"
+ " where\n"
+ " pagedir: Input directory for page image files\n"
+ " pagestr: Substring for matching; use 'allfiles' to\n"
+ " convert all files in the page directory\n"
+ " page_numpre: Number of characters in page name "
+ "before number\n"
+ " maskdir: Input directory for mask image files\n"
+ " maskstr: Substring for matching; use 'allfiles' to\n"
+ " convert all files in the mask directory\n"
+ " mask_numpre: Number of characters in mask name "
+ "before number\n"
+ " numpost: Number of characters in name after number\n"
+ " maxnum: Only consider page numbers up to this value\n"
+ " textscale: Scale of text output relative to pixs\n"
+ " imagescale: Scale of image output relative to pixs\n"
+ " thresh: threshold for binarization; typically about\n"
+ " 180; use 0 for default\n"
+ " fileout: Output p file\n");
+ return 1;
+ }
+ pagedir = argv[1];
+ pagestr = argv[2];
+ page_numpre = atoi(argv[3]);
+ maskdir = argv[4];
+ maskstr = argv[5];
+ mask_numpre = atoi(argv[6]);
+ numpost = atoi(argv[7]);
+ maxnum = atoi(argv[8]);
+ textscale = atof(argv[9]);
+ imagescale = atof(argv[10]);
+ threshold = atoi(argv[11]);
+ fileout = argv[12];
+
+ if (!strcmp(pagestr, "allfiles"))
+ pagestr = NULL;
+ if (!strcmp(maskstr, "allfiles"))
+ maskstr = NULL;
+
+ setLeptDebugOK(1);
+ return convertSegmentedPagesToPS(pagedir, pagestr, page_numpre,
+ maskdir, maskstr, mask_numpre,
+ numpost, maxnum, textscale,
+ imagescale, threshold, fileout);
+}
+
diff --git a/leptonica/prog/converttogray.c b/leptonica/prog/converttogray.c
new file mode 100644
index 00000000..eaa3d837
--- /dev/null
+++ b/leptonica/prog/converttogray.c
@@ -0,0 +1,123 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * converttogray.c
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein;
+char *fileout = NULL;
+l_int32 d, same;
+PIX *pixs, *pixd, *pix1, *pix2, *pix3, *pix4;
+static char mainName[] = "converttogray";
+
+ if (argc != 2 && argc != 3)
+ return ERROR_INT(" Syntax: converttogray filein [fileout]",
+ mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/gray");
+
+ filein = argv[1];
+ if (argc == 3) fileout = argv[2];
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ if (fileout) {
+ pixd = pixConvertRGBToGray(pixs, 0.33, 0.34, 0.33);
+ pixWrite(fileout, pixd, IFF_PNG);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+ }
+
+ d = pixGetDepth(pixs);
+ if (d == 2) {
+ pix1 = pixConvert2To8(pixs, 0x00, 0x55, 0xaa, 0xff, TRUE);
+ pix2 = pixConvert2To8(pixs, 0x00, 0x55, 0xaa, 0xff, FALSE);
+ pixEqual(pix1, pix2, &same);
+ if (same)
+ lept_stderr("images are the same\n");
+ else
+ lept_stderr("images are different!\n");
+ pixWrite("/tmp/lept/gray/pix1.png", pix1, IFF_PNG);
+ pixWrite("/tmp/lept/gray/pix2.png", pix2, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixSetColormap(pixs, NULL);
+ pix3 = pixConvert2To8(pixs, 0x00, 0x55, 0xaa, 0xff, TRUE);
+ pix4 = pixConvert2To8(pixs, 0x00, 0x55, 0xaa, 0xff, FALSE);
+ pixEqual(pix3, pix4, &same);
+ if (same)
+ lept_stderr("images are the same\n");
+ else
+ lept_stderr("images are different!\n");
+ pixWrite("/tmp/lept/gray/pix3.png", pix3, IFF_PNG);
+ pixWrite("/tmp/lept/gray/pix4.png", pix4, IFF_PNG);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ } else if (d == 4) {
+ pix1 = pixConvert4To8(pixs, TRUE);
+ pix2 = pixConvert4To8(pixs, FALSE);
+ pixEqual(pix1, pix2, &same);
+ if (same)
+ lept_stderr("images are the same\n");
+ else
+ lept_stderr("images are different!\n");
+ pixWrite("/tmp/lept/gray/pix1.png", pix1, IFF_PNG);
+ pixWrite("/tmp/lept/gray/pix2.png", pix2, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixSetColormap(pixs, NULL);
+ pix3 = pixConvert4To8(pixs, TRUE);
+ pix4 = pixConvert4To8(pixs, FALSE);
+ pixEqual(pix3, pix4, &same);
+ if (same)
+ lept_stderr("images are the same\n");
+ else
+ lept_stderr("images are different!\n");
+ pixWrite("/tmp/lept/gray/pix3.png", pix3, IFF_PNG);
+ pixWrite("/tmp/lept/gray/pix4.png", pix4, IFF_PNG);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ } else {
+ L_INFO("only converts 2 and 4 bpp; d = %d\n", mainName, d);
+ }
+
+ pixDestroy(&pixs);
+ return 0;
+}
+
diff --git a/leptonica/prog/converttopdf.c b/leptonica/prog/converttopdf.c
new file mode 100644
index 00000000..4e7f62e6
--- /dev/null
+++ b/leptonica/prog/converttopdf.c
@@ -0,0 +1,78 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * converttopdf.c
+ *
+ * Bundles all image files that are in the designated directory, with
+ * optional matching substring, into a pdf.
+ *
+ * The encoding type depends on the input file format:
+ * jpeg ==> DCT (not transcoded)
+ * jp2k ==> JPX (not transcoded)
+ * tiff-g4 ==> G4
+ * png ==> FLATE (some are not transcoded)
+ * The default resolution is set at 300 ppi if not given in the
+ * individual images, and the images are wrapped at full resolution.
+ * No title is attached.
+ *
+ * This is meant for the simplest set of input arguments. It is
+ * very fast for jpeg, jp2k and some png.
+ * The syntax for using all files in the directory is:
+ * convertopdf <directory> <pdf_outfile>
+ * The syntax using some substring to be matched in the file names is:
+ * converttopdf <directory> <substring> <pdf_outfile>
+ * If you want something more general, use convertfilestopdf.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 ret;
+char *dirin, *substr, *fileout;
+
+ if (argc != 3 && argc != 4) {
+ lept_stderr(
+ " Syntax: converttopdf dir [substr] fileout\n"
+ " substr: Leave this out to bundle all files\n"
+ " fileout: Output pdf file\n");
+ return 1;
+ }
+ dirin = argv[1];
+ substr = (argc == 4) ? argv[2] : NULL;
+ fileout = (argc == 4) ? argv[3] : argv[2];
+
+ setLeptDebugOK(1);
+ ret = convertUnscaledFilesToPdf(dirin, substr, "", fileout);
+ return ret;
+}
diff --git a/leptonica/prog/converttops.c b/leptonica/prog/converttops.c
new file mode 100644
index 00000000..855376f4
--- /dev/null
+++ b/leptonica/prog/converttops.c
@@ -0,0 +1,68 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * converttops.c
+ *
+ * Syntax: converttops filein fileout [level]
+ *
+ * where level = {1,2,3} and 2 is the default
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+char error_msg[] = " ps level = {1,2,3}; level 2 is default";
+l_int32 level;
+static char mainName[] = "converttops";
+
+ if (argc != 3 && argc != 4) {
+ lept_stderr("Syntax: converttops filein fileout [level]\n");
+ lept_stderr("%s\n", error_msg);
+ return 1;
+ }
+ filein = argv[1];
+ fileout = argv[2];
+ level = 2;
+ if (argc == 4) {
+ level = atoi(argv[3]);
+ if (level != 1 && level != 2 && level != 3) {
+ L_WARNING("ps level must be 1, 2 or 3; setting to 2\n", mainName);
+ level = 2;
+ }
+ }
+
+ setLeptDebugOK(1);
+ convertToPSEmbed(filein, fileout, level);
+ return 0;
+}
diff --git a/leptonica/prog/convolve_reg.c b/leptonica/prog/convolve_reg.c
new file mode 100644
index 00000000..caf2422b
--- /dev/null
+++ b/leptonica/prog/convolve_reg.c
@@ -0,0 +1,211 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Tests a number of convolution functions.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const char *kel1str = " 20 50 80 50 20 "
+ " 50 100 140 100 50 "
+ " 90 160 200 160 90 "
+ " 50 100 140 100 50 "
+ " 20 50 80 50 20 ";
+
+static const char *kel2str = " -20 -50 -80 -50 -20 "
+ " -50 50 80 50 -50 "
+ " -90 90 200 90 -90 "
+ " -50 50 80 50 -50 "
+ " -20 -50 -80 -50 -20 ";
+
+static const char *kel3xstr = " -70 40 100 40 -70 ";
+static const char *kel3ystr = " 20 -70 40 100 40 -70 20 ";
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, sizex, sizey, bias;
+FPIX *fpixv, *fpixrv;
+L_KERNEL *kel1, *kel2, *kel3x, *kel3y;
+PIX *pixs, *pixacc, *pixg, *pixt, *pixd;
+PIX *pixb, *pixm, *pixms, *pixrv, *pix1, *pix2, *pix3, *pix4;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Test pixBlockconvGray() on 8 bpp */
+ pixs = pixRead("test8.jpg");
+ pixacc = pixBlockconvAccum(pixs);
+ pixd = pixBlockconvGray(pixs, pixacc, 3, 5);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 0 */
+ pixDisplayWithTitle(pixd, 100, 0, NULL, rp->display);
+ pixDestroy(&pixacc);
+ pixDestroy(&pixd);
+
+ /* Test pixBlockconv() on 8 bpp */
+ pixd = pixBlockconv(pixs, 9, 8);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 1 */
+ pixDisplayWithTitle(pixd, 200, 0, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixDestroy(&pixs);
+
+ /* Test pixBlockrank() on 1 bpp */
+ pixs = pixRead("test1.png");
+ pixacc = pixBlockconvAccum(pixs);
+ for (i = 0; i < 3; i++) {
+ pixd = pixBlockrank(pixs, pixacc, 4, 4, 0.25 + 0.25 * i);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 2 - 4 */
+ pixDisplayWithTitle(pixd, 300 + 100 * i, 0, NULL, rp->display);
+ pixDestroy(&pixd);
+ }
+
+ /* Test pixBlocksum() on 1 bpp */
+ pixd = pixBlocksum(pixs, pixacc, 16, 16);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 5 */
+ pixDisplayWithTitle(pixd, 700, 0, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixDestroy(&pixacc);
+ pixDestroy(&pixs);
+
+ /* Test pixCensusTransform() */
+ pixs = pixRead("test24.jpg");
+ pixg = pixScaleRGBToGrayFast(pixs, 2, COLOR_GREEN);
+ pixd = pixCensusTransform(pixg, 10, NULL);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pixd, 800, 0, NULL, rp->display);
+ pixDestroy(&pixd);
+
+ /* Test generic convolution with kel1 */
+ kel1 = kernelCreateFromString(5, 5, 2, 2, kel1str);
+ pixd = pixConvolve(pixg, kel1, 8, 1);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 7 */
+ pixDisplayWithTitle(pixd, 100, 500, NULL, rp->display);
+ pixDestroy(&pixd);
+
+ /* Test convolution with flat rectangular kel */
+ kel2 = kernelCreate(11, 11);
+ kernelSetOrigin(kel2, 5, 5);
+ for (i = 0; i < 11; i++) {
+ for (j = 0; j < 11; j++)
+ kernelSetElement(kel2, i, j, 1);
+ }
+ pixd = pixConvolve(pixg, kel2, 8, 1);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 8 */
+ pixDisplayWithTitle(pixd, 200, 500, NULL, rp->display);
+ pixDestroy(&pixd);
+ kernelDestroy(&kel1);
+ kernelDestroy(&kel2);
+
+ /* Test pixBlockconv() on 32 bpp */
+ pixt = pixScaleBySampling(pixs, 0.5, 0.5);
+ pixd = pixBlockconv(pixt, 4, 6);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 9 */
+ pixDisplayWithTitle(pixd, 300, 500, NULL, rp->display);
+ pixDestroy(&pixt);
+ pixDestroy(&pixs);
+ pixDestroy(&pixg);
+ pixDestroy(&pixd);
+
+ /* Test bias convolution non-separable with kel2 */
+ pixs = pixRead("marge.jpg");
+ pixg = pixScaleRGBToGrayFast(pixs, 2, COLOR_GREEN);
+ kel2 = kernelCreateFromString(5, 5, 2, 2, kel2str);
+ pixd = pixConvolveWithBias(pixg, kel2, NULL, TRUE, &bias);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 10 */
+ pixDisplayWithTitle(pixd, 400, 500, NULL, rp->display);
+ lept_stderr("bias = %d\n", bias);
+ kernelDestroy(&kel2);
+ pixDestroy(&pixd);
+
+ /* Test bias convolution separable with kel3x and kel3y */
+ kel3x = kernelCreateFromString(1, 5, 0, 2, kel3xstr);
+ kel3y = kernelCreateFromString(7, 1, 3, 0, kel3ystr);
+ pixd = pixConvolveWithBias(pixg, kel3x, kel3y, TRUE, &bias);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 11 */
+ pixDisplayWithTitle(pixd, 500, 500, NULL, rp->display);
+ lept_stderr("bias = %d\n", bias);
+ kernelDestroy(&kel3x);
+ kernelDestroy(&kel3y);
+ pixDestroy(&pixd);
+ pixDestroy(&pixs);
+ pixDestroy(&pixg);
+
+ /* Test pixWindowedMean() and pixWindowedMeanSquare() on 8 bpp */
+ pixs = pixRead("feyn-fract2.tif");
+ pixg = pixConvertTo8(pixs, 0);
+ sizex = 5;
+ sizey = 20;
+ pixb = pixAddBorderGeneral(pixg, sizex + 1, sizex + 1,
+ sizey + 1, sizey + 1, 0);
+ pixm = pixWindowedMean(pixb, sizex, sizey, 1, 1);
+ pixms = pixWindowedMeanSquare(pixb, sizex, sizey, 1);
+ regTestWritePixAndCheck(rp, pixm, IFF_JFIF_JPEG); /* 12 */
+ pixDisplayWithTitle(pixm, 100, 0, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pixb);
+
+ /* Test pixWindowedVariance() on 8 bpp */
+ pixWindowedVariance(pixm, pixms, &fpixv, &fpixrv);
+ pixrv = fpixConvertToPix(fpixrv, 8, L_CLIP_TO_ZERO, 1);
+ regTestWritePixAndCheck(rp, pixrv, IFF_JFIF_JPEG); /* 13 */
+ pixDisplayWithTitle(pixrv, 100, 250, NULL, rp->display);
+ pix1 = fpixDisplayMaxDynamicRange(fpixv);
+ pix2 = fpixDisplayMaxDynamicRange(fpixrv);
+ pixDisplayWithTitle(pix1, 100, 500, "Variance", rp->display);
+ pixDisplayWithTitle(pix2, 100, 750, "RMS deviation", rp->display);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 14 */
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 15 */
+ fpixDestroy(&fpixv);
+ fpixDestroy(&fpixrv);
+ pixDestroy(&pixm);
+ pixDestroy(&pixms);
+ pixDestroy(&pixrv);
+
+ /* Test again all windowed functions with simpler interface */
+ pixWindowedStats(pixg, sizex, sizey, 0, NULL, NULL, &fpixv, &fpixrv);
+ pix3 = fpixDisplayMaxDynamicRange(fpixv);
+ pix4 = fpixDisplayMaxDynamicRange(fpixrv);
+ regTestComparePix(rp, pix1, pix3); /* 16 */
+ regTestComparePix(rp, pix2, pix4); /* 17 */
+ pixDestroy(&pixg);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ fpixDestroy(&fpixv);
+ fpixDestroy(&fpixrv);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/cootoots.png b/leptonica/prog/cootoots.png
new file mode 100644
index 00000000..4daabf99
--- /dev/null
+++ b/leptonica/prog/cootoots.png
Binary files differ
diff --git a/leptonica/prog/copernicus.png b/leptonica/prog/copernicus.png
new file mode 100644
index 00000000..1c1cb6f9
--- /dev/null
+++ b/leptonica/prog/copernicus.png
Binary files differ
diff --git a/leptonica/prog/cornertest.c b/leptonica/prog/cornertest.c
new file mode 100644
index 00000000..d069bc21
--- /dev/null
+++ b/leptonica/prog/cornertest.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * cornertest.c
+ *
+ * e.g., use on witten.png
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define LINE_SIZE 29
+
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 x, y, n, i;
+PIX *pixs;
+PTA *pta;
+PTAA *ptaa, *ptaa2, *ptaa3;
+static char mainName[] = "cornertest";
+
+ if (argc != 3)
+ return ERROR_INT(" Syntax: cornertest filein fileout", mainName, 1);
+ filein = argv[1];
+ fileout = argv[2];
+
+ setLeptDebugOK(1);
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ /* Clean noise in LR corner of witten.tif */
+ pixSetPixel(pixs, 2252, 3051, 0);
+ pixSetPixel(pixs, 2252, 3050, 0);
+ pixSetPixel(pixs, 2251, 3050, 0);
+
+ pta = pixFindCornerPixels(pixs);
+ ptaWriteStream(stderr, pta, 1);
+
+ /* Test pta and ptaa I/O */
+#if 1
+ ptaa = ptaaCreate(3);
+ ptaaAddPta(ptaa, pta, L_COPY);
+ ptaaAddPta(ptaa, pta, L_COPY);
+ ptaaAddPta(ptaa, pta, L_COPY);
+ ptaaWriteStream(stderr, ptaa, 1);
+ ptaaWrite("/tmp/junkptaa", ptaa, 1);
+ ptaa2 = ptaaRead("/tmp/junkptaa");
+ ptaaWrite("/tmp/junkptaa2", ptaa2, 1);
+ ptaaWrite("/tmp/junkptaa3", ptaa, 0);
+ ptaa3 = ptaaRead("/tmp/junkptaa3");
+ ptaaWrite("/tmp/junkptaa4", ptaa3, 0);
+ ptaaDestroy(&ptaa);
+ ptaaDestroy(&ptaa2);
+ ptaaDestroy(&ptaa3);
+#endif
+
+ /* mark corner pixels */
+ n = ptaGetCount(pta);
+ for (i = 0; i < n; i++) {
+ ptaGetIPt(pta, i, &x, &y);
+ pixRenderLine(pixs, x - LINE_SIZE, y, x + LINE_SIZE, y, 5,
+ L_FLIP_PIXELS);
+ pixRenderLine(pixs, x, y - LINE_SIZE, x, y + LINE_SIZE, 5,
+ L_FLIP_PIXELS);
+ }
+
+ pixWrite(fileout, pixs, IFF_PNG);
+
+ pixDestroy(&pixs);
+ ptaDestroy(&pta);
+ ptaDestroy(&pta);
+ return 0;
+}
+
diff --git a/leptonica/prog/corrupttest.c b/leptonica/prog/corrupttest.c
new file mode 100644
index 00000000..8ff6aec6
--- /dev/null
+++ b/leptonica/prog/corrupttest.c
@@ -0,0 +1,271 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * corrupttest.c
+ *
+ * Excises or permutes a given fraction of bytes, starting from
+ * a specified location. The parameters @loc and @size are fractions
+ * of the entire file (between 0.0 and 1.0).
+ *
+ * Syntax: corrupttest <file> <deletion> [loc size]
+ *
+ * where <deletion> == 1 means that bytes are deleted
+ * <deletion> == 0 means that random bytes are substituted
+ *
+ * Use: "fuzz testing" jpeg, png, tiff, bmp, webp and pnm reading,
+ * under corruption by either random byte substitution or
+ * deletion of part of the compressed file.
+ *
+ * For example,
+ * corrupttest rabi.png 0 0.0001 0.0001
+ * which tests read functions on rabi.png after 23 bytes (0.01%)
+ * starting at byte 23 have been randomly permuted, emits the following:
+ * > Info in fileCorruptByMutation: Randomizing 23 bytes at location 23
+ * > libpng error: IHDR: CRC error
+ * > Error in pixReadMemPng: internal png error
+ * > Error in pixReadStream: png: no pix returned
+ * > Error in pixRead: pix not read
+ * > libpng error: IHDR: CRC error
+ * > Error in fgetPngResolution: internal png error
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+static const char *corruptfile = "/tmp/lept/corrupt/badfile";
+
+int main(int argc,
+ char **argv)
+{
+size_t filesize;
+l_float32 loc, size;
+l_float32 coeff1[15], coeff2[25];
+l_int32 i, j, w, xres, yres, format, ret, nwarn, hint, deletion, show;
+l_uint8 *comment, *filedata;
+char *filein;
+size_t nbytes;
+FILE *fp;
+PIX *pix;
+static char mainName[] = "corrupttest";
+
+ if (argc != 3 && argc != 5)
+ return ERROR_INT("syntax: corrupttest filein deletion [loc size]",
+ mainName, 1);
+ filein = argv[1];
+ deletion = atoi(argv[2]);
+ findFileFormat(filein, &format);
+ nbytes = nbytesInFile(filein);
+ lept_stderr("file size: %lu bytes\n", (unsigned long)nbytes);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/corrupt");
+
+ hint = 0;
+ if (argc == 5) { /* Single test */
+ loc = atof(argv[3]);
+ size = atof(argv[4]);
+ if (deletion == TRUE) {
+ fileCorruptByDeletion(filein, loc, size, corruptfile);
+ } else { /* mutation */
+ fileCorruptByMutation(filein, loc, size, corruptfile);
+ }
+ if ((fp = fopenReadStream(corruptfile)) == NULL)
+ return ERROR_INT("stream not opened", mainName, 1);
+ if (format == IFF_JFIF_JPEG) {
+ if ((pix = pixReadJpeg(corruptfile, 0, 1, &nwarn, hint)) != NULL) {
+ pixDisplay(pix, 100, 100);
+ pixDestroy(&pix);
+ }
+ freadHeaderJpeg(fp, &w, NULL, NULL, NULL, NULL);
+ fgetJpegResolution(fp, &xres, &yres);
+ ret = fgetJpegComment(fp, &comment);
+ if (!ret && comment) {
+ lept_stderr("comment: %s\n", comment);
+ lept_free(comment);
+ }
+ } else if (format == IFF_PNG) {
+ if ((pix = pixRead(corruptfile)) != NULL) {
+ pixDisplay(pix, 100, 100);
+ pixDestroy(&pix);
+ }
+ freadHeaderPng(fp, &w, NULL, NULL, NULL, NULL);
+ fgetPngResolution(fp, &xres, &yres);
+ } else if (format == IFF_WEBP) {
+ if ((pix = pixRead(corruptfile)) != NULL) {
+ pixDisplay(pix, 100, 100);
+ pixDestroy(&pix);
+ }
+ readHeaderWebP(corruptfile, &w, NULL, NULL);
+ } else if (format == IFF_PNM) {
+ if ((pix = pixRead(corruptfile)) != NULL) {
+ pixDisplay(pix, 100, 100);
+ pixDestroy(&pix);
+ }
+ freadHeaderPnm(fp, &w, NULL, NULL, NULL, NULL, NULL);
+ }
+ fclose(fp);
+ return 0;
+ }
+
+ /* Generate coefficients so that the size of the mangled
+ * or deleted data can range from 0.001% to 1% of the file,
+ * and the location of deleted data ranges from 0.001%
+ * to 90% of the file. */
+ for (i = 0; i < 15; i++) {
+ if (i < 5) coeff1[i] = 0.00001;
+ else if (i < 10) coeff1[i] = 0.0001;
+ else coeff1[i] = 0.001;
+ }
+ for (i = 0; i < 25; i++) {
+ if (i < 5) coeff2[i] = 0.00001;
+ else if (i < 10) coeff2[i] = 0.0001;
+ else if (i < 15) coeff2[i] = 0.001;
+ else if (i < 20) coeff2[i] = 0.01;
+ else coeff2[i] = 0.1;
+ }
+
+ /* Multiple test (argc == 3) */
+ show = TRUE;
+ for (i = 0; i < 25; i++) {
+ loc = coeff2[i] * (2 * (i % 5) + 1);
+ for (j = 0; j < 15; j++) {
+ size = coeff1[j] * (2 * (j % 5) + 1);
+
+ /* Write corrupt file */
+ if (deletion == TRUE) {
+ fileCorruptByDeletion(filein, loc, size, corruptfile);
+ } else {
+ fileCorruptByMutation(filein, loc, size, corruptfile);
+ }
+
+ /* Attempt to read the file */
+ pix = NULL;
+ if (format == IFF_JFIF_JPEG) {
+ /* The pix is usually returned as long as the header
+ * information is not damaged.
+ * We expect nwarn > 0 (typically 1) for nearly every
+ * corrupted image. In the situation where only a few
+ * bytes are removed, a corrupted image will occasionally
+ * have nwarn == 0 even though it's visually defective. */
+ pix = pixReadJpeg(corruptfile, 0, 1, &nwarn, 0);
+ if (pix && nwarn != 1 && deletion == TRUE) {
+ lept_stderr("nwarn[%d,%d] = %d\n", j, i, nwarn);
+ if (show) pixDisplay(pix, 20 * i, 30 * j);
+ show = FALSE;
+ }
+ } else if (format == IFF_PNG) {
+ pix = pixRead(corruptfile);
+ if (pix) {
+ lept_stderr("pix[%d,%d] is read\n", j, i);
+ if (show) pixDisplay(pix, 20 * i, 30 * j);
+ show = FALSE;
+ }
+ pixDestroy(&pix);
+ filedata = l_binaryRead(corruptfile, &filesize);
+ pix = pixReadMemPng(filedata, filesize);
+ lept_free(filedata);
+ } else if (L_FORMAT_IS_TIFF(format)) {
+ /* A corrupted pix is often returned, as long as the
+ * header is not damaged, so we do not display them. */
+ pix = pixRead(corruptfile);
+ if (pix) lept_stderr("pix[%d,%d] is read\n", j, i);
+ pixDestroy(&pix);
+ filedata = l_binaryRead(corruptfile, &filesize);
+ pix = pixReadMemTiff(filedata, filesize, 0);
+ if (!pix) lept_stderr("no pix!\n");
+ lept_free(filedata);
+ } else if (format == IFF_BMP) {
+ /* A corrupted pix is always returned if the header is
+ * not damaged, so we do not display them. */
+ pix = pixRead(corruptfile);
+ if (pix) lept_stderr("pix[%d,%d] is read\n", j, i);
+ pixDestroy(&pix);
+ filedata = l_binaryRead(corruptfile, &filesize);
+ pix = pixReadMemBmp(filedata, filesize);
+ lept_free(filedata);
+ } else if (format == IFF_WEBP) {
+ /* A corrupted pix is always returned if the header is
+ * not damaged, so we do not display them. */
+ pix = pixRead(corruptfile);
+ if (pix) lept_stderr("pix[%d,%d] is read\n", j, i);
+ pixDestroy(&pix);
+ filedata = l_binaryRead(corruptfile, &filesize);
+ pix = pixReadMemWebP(filedata, filesize);
+ lept_free(filedata);
+ } else if (format == IFF_PNM) {
+ /* A corrupted pix is always returned if the header is
+ * not damaged, so we do not display them. */
+ pix = pixRead(corruptfile);
+ if (pix) lept_stderr("pix[%d,%d] is read\n", j, i);
+ pixDestroy(&pix);
+ filedata = l_binaryRead(corruptfile, &filesize);
+ pix = pixReadMemPnm(filedata, filesize);
+ lept_free(filedata);
+ } else {
+ lept_stderr("Format %d unknown\n", format);
+ continue;
+ }
+
+ /* Effect of 1% byte mangling from interior of data stream */
+ if (pix && j == 14 && i == 10 && deletion == FALSE)
+ pixDisplay(pix, 0, 0);
+ pixDestroy(&pix);
+
+ /* Attempt to read the header and the resolution */
+ fp = fopenReadStream(corruptfile);
+ if (format == IFF_JFIF_JPEG) {
+ freadHeaderJpeg(fp, &w, NULL, NULL, NULL, NULL);
+ if (fgetJpegResolution(fp, &xres, &yres) == 0)
+ lept_stderr("w = %d, xres = %d, yres = %d\n",
+ w, xres, yres);
+ } else if (format == IFF_PNG) {
+ freadHeaderPng(fp, &w, NULL, NULL, NULL, NULL);
+ if (fgetPngResolution(fp, &xres, &yres) == 0)
+ lept_stderr("w = %d, xres = %d, yres = %d\n",
+ w, xres, yres);
+ } else if (L_FORMAT_IS_TIFF(format)) {
+ freadHeaderTiff(fp, 0, &w, NULL, NULL, NULL, NULL, NULL, NULL);
+ getTiffResolution(fp, &xres, &yres);
+ lept_stderr("w = %d, xres = %d, yres = %d\n",
+ w, xres, yres);
+ } else if (format == IFF_WEBP) {
+ if (readHeaderWebP(corruptfile, &w, NULL, NULL) == 0)
+ lept_stderr("w = %d\n", w);
+ } else if (format == IFF_PNM) {
+ if (freadHeaderPnm(fp, &w, NULL, NULL, NULL, NULL, NULL) == 0)
+ lept_stderr("w = %d\n", w);
+ }
+ fclose(fp);
+ }
+ }
+ return 0;
+}
+
diff --git a/leptonica/prog/crop_reg.c b/leptonica/prog/crop_reg.c
new file mode 100644
index 00000000..c38f331a
--- /dev/null
+++ b/leptonica/prog/crop_reg.c
@@ -0,0 +1,320 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * crop_reg.c
+ *
+ * Test:
+ * * plotting pixel profiles
+ * * undercropping from a box (i.e., with an added border)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 mindif = 60;
+
+static l_int32 GetLeftCut(NUMA *narl, NUMA *nart, NUMA *nait,
+ l_int32 h, l_int32 *pleft);
+static l_int32 GetRightCut(NUMA *narl, NUMA *nart, NUMA *nait,
+ l_int32 h, l_int32 *pright);
+
+const char *fnames[] = {"lyra.005.jpg", "lyra.036.jpg"};
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, pageno, w, h, left, right;
+BOX *box1, *box2;
+NUMA *na1, *nar, *naro, *narl, *nart, *nai, *naio, *nait;
+PIX *pixs, *pixr, *pixg, *pixgi, *pixd, *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa1, *pixa2;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/crop");
+
+ /* Calculate projection profiles through images/drawings. */
+ pixa1 = pixaCreate(2);
+ for (i = 0; i < 2; i++) {
+ pageno = extractNumberFromFilename(fnames[i], 5, 0);
+ lept_stderr("Page %d\n", pageno);
+ pixs = pixRead(fnames[i]);
+ pixr = pixRotate90(pixs, (pageno % 2) ? 1 : -1);
+ pixg = pixConvertTo8(pixr, 0);
+ pixGetDimensions(pixg, &w, &h, NULL);
+
+ /* Get info on vertical reversal profile */
+ nar = pixReversalProfile(pixg, 0.8, L_VERTICAL_LINE,
+ 0, h - 1, mindif, 1, 1);
+ naro = numaOpen(nar, 11);
+ pix1 = gplotSimplePix1(naro, "Reversals Opened");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0,2 */
+ narl = numaLowPassIntervals(naro, 0.1, 0.0);
+ nart = numaThresholdEdges(naro, 0.1, 0.5, 0.0);
+ numaDestroy(&nar);
+ numaDestroy(&naro);
+
+ /* Get info on vertical intensity profile */
+ pixgi = pixInvert(NULL, pixg);
+ nai = pixAverageIntensityProfile(pixgi, 0.8, L_VERTICAL_LINE,
+ 0, h - 1, 1, 1);
+ naio = numaOpen(nai, 11);
+ pix2 = gplotSimplePix1(naio, "Intensities Opened");
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1,3 */
+ nait = numaThresholdEdges(naio, 0.4, 0.6, 0.0);
+ numaDestroy(&nai);
+ numaDestroy(&naio);
+
+ /* Analyze profiles for left/right edges */
+ GetLeftCut(narl, nart, nait, w, &left);
+ GetRightCut(narl, nart, nait, w, &right);
+ if (rp->display)
+ lept_stderr("left = %d, right = %d\n", left, right);
+
+ /* Output visuals */
+ pixa2 = pixaCreate(3);
+ pixaAddPix(pixa2, pixr, L_INSERT);
+ pixaAddPix(pixa2, pix1, L_INSERT);
+ pixaAddPix(pixa2, pix2, L_INSERT);
+ pixd = pixaDisplayTiledInColumns(pixa2, 2, 1.0, 25, 0);
+ pixaDestroy(&pixa2);
+ pixaAddPix(pixa1, pixd, L_INSERT);
+ pixDisplayWithTitle(pixd, 800 * i, 100, NULL, rp->display);
+ if (rp->display) {
+ lept_stderr("narl:");
+ numaWriteStderr(narl);
+ lept_stderr("nart:");
+ numaWriteStderr(nart);
+ lept_stderr("nait:");
+ numaWriteStderr(nait);
+ }
+ pixDestroy(&pixs);
+ pixDestroy(&pixg);
+ pixDestroy(&pixgi);
+ numaDestroy(&narl);
+ numaDestroy(&nart);
+ numaDestroy(&nait);
+ }
+
+ lept_stderr("Writing profiles to /tmp/lept/crop/croptest.pdf\n");
+ pixaConvertToPdf(pixa1, 75, 1.0, L_JPEG_ENCODE, 0, "Profiles",
+ "/tmp/lept/crop/croptest.pdf");
+ pixaDestroy(&pixa1);
+
+ /* Calculate projection profiles from text lines */
+ pixs = pixRead("1555.007.jpg");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ na1 = pixReversalProfile(pixs, 0.98, L_HORIZONTAL_LINE,
+ 0, h - 1, 40, 3, 3);
+ pix1 = gplotSimplePix1(na1, "Reversals");
+ numaDestroy(&na1);
+
+ na1 = pixAverageIntensityProfile(pixs, 0.98, L_HORIZONTAL_LINE,
+ 0, h - 1, 1, 1);
+ pix2 = gplotSimplePix1(na1, "Intensities");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 4 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 5 */
+ numaDestroy(&na1);
+ pixa1 = pixaCreate(3);
+ pixaAddPix(pixa1, pixScale(pixs, 0.5, 0.5), L_INSERT);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixd = pixaDisplayTiledInRows(pixa1, 32, 1000, 1.0, 0, 30, 2);
+ pixWrite("/tmp/lept/crop/profiles.png", pixd, IFF_PNG);
+ pixDisplayWithTitle(pixd, 0, 700, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa1);
+
+ /* Test rectangle clipping with border */
+ pix1 = pixRead("lyra.005.jpg");
+ pix2 = pixScale(pix1, 0.5, 0.5);
+ box1 = boxCreate(125, 50, 180, 230); /* fully contained */
+ pix3 = pixClipRectangleWithBorder(pix2, box1, 30, &box2);
+ pixRenderBoxArb(pix2, box1, 2, 255, 0, 0);
+ pixRenderBoxArb(pix3, box2, 2, 255, 0, 0);
+ pixa1 = pixaCreate(2);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ pix4 = pixaDisplayTiledInColumns(pixa1, 2, 1.0, 15, 2);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pix4, 325, 700, NULL, rp->display);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ pixDestroy(&pix4);
+ pixaDestroy(&pixa1);
+
+ pix2 = pixScale(pix1, 0.5, 0.5);
+ box1 = boxCreate(125, 10, 180, 270); /* not full border */
+ pix3 = pixClipRectangleWithBorder(pix2, box1, 30, &box2);
+ pixRenderBoxArb(pix2, box1, 2, 255, 0, 0);
+ pixRenderBoxArb(pix3, box2, 2, 255, 0, 0);
+ pixa1 = pixaCreate(2);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ pix4 = pixaDisplayTiledInColumns(pixa1, 2, 1.0, 15, 2);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 7 */
+ pixDisplayWithTitle(pix4, 975, 700, NULL, rp->display);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ pixDestroy(&pix4);
+ pixaDestroy(&pixa1);
+
+ pix2 = pixScale(pix1, 0.5, 0.5);
+ box1 = boxCreate(125, 200, 180, 270); /* not entirely within pix2 */
+ pix3 = pixClipRectangleWithBorder(pix2, box1, 30, &box2);
+ pixRenderBoxArb(pix2, box1, 2, 255, 0, 0);
+ pixRenderBoxArb(pix3, box2, 2, 255, 0, 0);
+ pixa1 = pixaCreate(2);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ pix4 = pixaDisplayTiledInColumns(pixa1, 2, 1.0, 15, 2);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 8 */
+ pixDisplayWithTitle(pix4, 1600, 700, NULL, rp->display);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ pixDestroy(&pix4);
+ pixaDestroy(&pixa1);
+ pixDestroy(&pix1);
+
+ return regTestCleanup(rp);
+}
+
+
+/*
+ * Use these variable abbreviations:
+ *
+ * pap1: distance from left edge to the page
+ * txt1: distance from left edge to the text
+ * Identify pap1 by (a) 1st downward transition in intensity (nait).
+ * (b) start of 1st lowpass interval (nail)
+ * Identify txt1 by (a) end of 1st lowpass interval (nail)
+ * (b) first upward transition in reversals (nart)
+ *
+ * pap2: distance from right edge to beginning of last upward transition,
+ * plus some extra for safety.
+ * txt1: distance from right edge to the text
+ * Identify pap2 by 1st downward transition in intensity.
+ * Identify txt2 by (a) beginning of 1st lowpass interval from bottom
+ * (b) last downward transition in reversals from bottom
+ */
+static l_int32
+GetLeftCut(NUMA *narl,
+ NUMA *nart,
+ NUMA *nait,
+ l_int32 w,
+ l_int32 *pleft) {
+l_int32 nrl, nrt, nit, start, end, sign, pap1, txt1, del;
+
+ nrl = numaGetCount(narl);
+ nrt = numaGetCount(nart);
+ nit = numaGetCount(nait);
+
+ /* Check for small max number of reversals or no edge */
+ numaGetSpanValues(narl, 0, NULL, &end);
+ if (end < 20 || nrl <= 1) {
+ *pleft = 0;
+ return 0;
+ }
+
+ /* Where is text and page, scanning from the left? */
+ pap1 = 0;
+ txt1 = 0;
+ if (nrt >= 4) { /* beginning of first upward transition */
+ numaGetEdgeValues(nart, 0, &start, NULL, NULL);
+ txt1 = start;
+ }
+ if (nit >= 4) { /* end of first downward trans in (inverse) intensity */
+ numaGetEdgeValues(nait, 0, NULL, &end, &sign);
+ if (end < txt1 && sign == -1)
+ pap1 = end;
+ else
+ pap1 = 0.5 * txt1;
+ }
+ del = txt1 - pap1;
+ if (del > 20) {
+ txt1 -= L_MIN(20, 0.5 * del);
+ pap1 += L_MIN(20, 0.5 * del);
+ }
+ lept_stderr("txt1 = %d, pap1 = %d\n", txt1, pap1);
+ *pleft = pap1;
+ return 0;
+}
+
+
+static l_int32
+GetRightCut(NUMA *narl,
+ NUMA *nart,
+ NUMA *nait,
+ l_int32 w,
+ l_int32 *pright) {
+l_int32 nrt, ntrans, start, end, sign, txt2, pap2, found, trans;
+
+ nrt = numaGetCount(nart);
+
+ /* Check for small max number of reversals or no edge */
+ /* Where is text and page, scanning from the right? */
+ ntrans = nrt / 3;
+ if (ntrans > 1) {
+ found = FALSE;
+ for (trans = ntrans - 1; trans > 0; --trans) {
+ numaGetEdgeValues(nart, trans, &start, &end, &sign);
+ if (sign == -1) { /* end of textblock */
+ txt2 = end;
+ found = TRUE;
+ }
+ }
+ if (!found) {
+ txt2 = w - 1; /* take the whole thing! */
+ pap2 = w - 1;
+ } else { /* found textblock; now find right side of page */
+ found = FALSE;
+ for (trans = ntrans - 1; trans > 0; --trans) {
+ numaGetEdgeValues(nart, trans, &start, &end, &sign);
+ if (sign == 1 && start > txt2) {
+ pap2 = start; /* start of textblock on other page */
+ found = TRUE;
+ }
+ }
+ if (!found) { /* no text from other page */
+ pap2 = w - 1; /* refine later */
+ }
+ }
+ } else {
+ txt2 = w - 1;
+ pap2 = w - 1;
+ }
+ lept_stderr("txt2 = %d, pap2 = %d\n", txt2, pap2);
+ *pright = pap2;
+ return 0;
+}
+
diff --git a/leptonica/prog/croptext.c b/leptonica/prog/croptext.c
new file mode 100644
index 00000000..c1e72e5a
--- /dev/null
+++ b/leptonica/prog/croptext.c
@@ -0,0 +1,99 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * croptext.c
+ *
+ * Simple program that crops text pages to a given border
+ *
+ * Syntax:
+ * croptext dirin border dirout
+ * where
+ * border = number of pixels added on each side (e.g., 50)
+ *
+ * The output file name has the same tail as the input file name.
+ * If dirout is the same as dirin, you overwrite the input files.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *dirin, *dirout, *infile, *outfile, *tail;
+l_int32 i, nfiles, border, x, y, w, h, xb, yb, wb, hb;
+BOX *box1, *box2;
+BOXA *boxa1, *boxa2;
+PIX *pixs, *pixt1, *pixd;
+SARRAY *safiles;
+static char mainName[] = "croptext";
+
+ if (argc != 4)
+ return ERROR_INT("Syntax: croptext dirin border dirout", mainName, 1);
+ dirin = argv[1];
+ border = atoi(argv[2]);
+ dirout = argv[3];
+
+ setLeptDebugOK(1);
+ safiles = getSortedPathnamesInDirectory(dirin, NULL, 0, 0);
+ nfiles = sarrayGetCount(safiles);
+
+ for (i = 0; i < nfiles; i++) {
+ infile = sarrayGetString(safiles, i, L_NOCOPY);
+ splitPathAtDirectory(infile, NULL, &tail);
+ outfile = genPathname(dirout, tail);
+ pixs = pixRead(infile);
+ pixt1 = pixMorphSequence(pixs, "r11 + c10.40 + o5.5 + x4", 0);
+ boxa1 = pixConnComp(pixt1, NULL, 8);
+ if (boxaGetCount(boxa1) == 0) {
+ lept_stderr("Warning: no components on page %s\n", tail);
+ continue;
+ }
+ boxa2 = boxaSort(boxa1, L_SORT_BY_AREA, L_SORT_DECREASING, NULL);
+ box1 = boxaGetBox(boxa2, 0, L_CLONE);
+ boxGetGeometry(box1, &x, &y, &w, &h);
+ xb = L_MAX(0, x - border);
+ yb = L_MAX(0, y - border);
+ wb = w + 2 * border;
+ hb = h + 2 * border;
+ box2 = boxCreate(xb, yb, wb, hb);
+ pixd = pixClipRectangle(pixs, box2, NULL);
+ pixWrite(outfile, pixd, IFF_TIFF_G4);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixd);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ }
+
+ return 0;
+}
+
diff --git a/leptonica/prog/dave-orig.png b/leptonica/prog/dave-orig.png
new file mode 100644
index 00000000..7948cb5a
--- /dev/null
+++ b/leptonica/prog/dave-orig.png
Binary files differ
diff --git a/leptonica/prog/deskew_it.c b/leptonica/prog/deskew_it.c
new file mode 100644
index 00000000..db082b8d
--- /dev/null
+++ b/leptonica/prog/deskew_it.c
@@ -0,0 +1,140 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+
+/*
+ * deskew_it.c
+ *
+ * deskew_it filein threshold sweeprange tryboth fileout
+ *
+ * where:
+ * threshold: for binarization, use 0 for default (130)
+ * sweeprange: half the sweep angle search range, in degrees;
+ * use 0 for default (7.0 degrees)
+ * tryboth: 1 to test for skew both as input and with a 90 deg rotation;
+ * 0 to test for skew as input only
+ *
+ * On failure to deskew, write the input image to the output (not rotated).
+ *
+ * For further information on these and other defaulted parameters,
+ * see skew.c.
+
+ * For testing the deskew functions, see skewtest.c and the skew
+ * regression test skew_reg.c.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Default binarization threshold */
+static const l_int32 DefaultThreshold = 130;
+
+ /* Default half angle for searching */
+static const l_float32 DefaultSweepRange = 7.0; /* degrees */
+
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 threshold, tryboth, format;
+l_float32 deg2rad, sweeprange, angle, conf;
+PIX *pixs, *pix1, *pix2, *pixd;
+static char mainName[] = "deskew_it";
+
+ if (argc != 6)
+ return ERROR_INT(
+ "\n Syntax: deskew_it filein threshold sweeprange tryboth fileout",
+ mainName, 1);
+
+ filein = argv[1];
+ threshold = atoi(argv[2]);
+ sweeprange = atof(argv[3]);
+ tryboth = atoi(argv[4]);
+ fileout = argv[5];
+
+ setLeptDebugOK(1);
+ pixd = NULL;
+ deg2rad = 3.1415926535 / 180.;
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ sweeprange = (sweeprange == 0) ? DefaultSweepRange : sweeprange;
+ threshold = (threshold == 0) ? DefaultThreshold : threshold;
+ format = pixGetInputFormat(pixs);
+ if (format == IFF_UNKNOWN) format = IFF_PNG;
+
+ pixd = pixDeskewGeneral(pixs, 0, sweeprange, 0.0, 0, threshold,
+ &angle, &conf);
+ if (!pixd) {
+ L_ERROR("deskew failed; pixd not made\n", mainName);
+ pixWrite(fileout, pixs, format);
+ pixDestroy(&pixs);
+ return 1;
+ }
+ lept_stderr("skew angle = %.3f, conf = %.1f\n", angle, conf);
+
+ /* Two situations were we're finished:
+ * (1) conf >= 3.0 and it's good enough, so write out pixd
+ * (2) conf < 3.0, so pixd is a clone of pixs, and we're
+ * only trying once. */
+ if (conf >= 3.0 || tryboth == 0) {
+ pixWrite(fileout, pixd, format);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+ }
+ pixDestroy(&pixd);
+
+ /* Confidence was less than the min acceptable, but we will
+ * try again (tryboth == 1) after a 90 degree rotation. */
+ pix1 = pixRotateOrth(pixs, 1);
+ pix2 = pixDeskewGeneral(pix1, 0, sweeprange, 0.0, 0, threshold,
+ &angle, &conf);
+ pixDestroy(&pix1);
+ if (!pix2) {
+ L_ERROR("deskew failed at 90 deg; pixd not made\n", mainName);
+ pixWrite(fileout, pixs, format);
+ pixDestroy(&pixs);
+ return 1;
+ }
+ lept_stderr("90 rot: skew angle = %.3f, conf = %.1f\n", angle, conf);
+
+ if (conf < 3.0) {
+ pixWrite(fileout, pixs, format);
+ } else {
+ pixd = pixRotateOrth(pix2, 3);
+ pixWrite(fileout, pixd, format);
+ pixDestroy(&pixd);
+ }
+ pixDestroy(&pixs);
+ pixDestroy(&pix2);
+ return 0;
+}
diff --git a/leptonica/prog/dewarp_reg.c b/leptonica/prog/dewarp_reg.c
new file mode 100644
index 00000000..1e3db289
--- /dev/null
+++ b/leptonica/prog/dewarp_reg.c
@@ -0,0 +1,211 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * dewarp_reg.c
+ *
+ * Regression test for image dewarp based on text lines
+ *
+ * We also test some of the fpix and dpix functions (scaling,
+ * serialization, interconversion)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i, n;
+l_float32 a, b, c;
+L_DEWARP *dew1, *dew2;
+L_DEWARPA *dewa1, *dewa2;
+DPIX *dpix1, *dpix2, *dpix3;
+FPIX *fpix1, *fpix2, *fpix3;
+NUMA *nax, *nafit;
+PIX *pixs, *pixn, *pixg, *pixd, *pixb, *pix1, *pixt1, *pixt2;
+PIX *pixs2, *pixn2, *pixg2, *pixb2;
+PTA *pta, *ptad;
+PTAA *ptaa1, *ptaa2;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Read page 7, normalize for varying background and binarize */
+ pixs = pixRead("1555.007.jpg");
+ pixn = pixBackgroundNormSimple(pixs, NULL, NULL);
+ pixg = pixConvertRGBToGray(pixn, 0.5, 0.3, 0.2);
+ pixb = pixThresholdToBinary(pixg, 130);
+ pixDestroy(&pixn);
+ pixDestroy(&pixg);
+ regTestWritePixAndCheck(rp, pixb, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pixb, 0, 0, "page 7 binarized input", rp->display);
+
+ /* Get the textline centers */
+ ptaa1 = dewarpGetTextlineCenters(pixb, 0);
+ pixt1 = pixCreateTemplate(pixs);
+ pixt2 = pixDisplayPtaa(pixt1, ptaa1);
+ regTestWritePixAndCheck(rp, pixt2, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pixt2, 0, 500, "textline centers", rp->display);
+ pixDestroy(&pixt1);
+
+ /* Remove short lines */
+ ptaa2 = dewarpRemoveShortLines(pixb, ptaa1, 0.8, 0);
+
+ /* Fit to quadratic */
+ n = ptaaGetCount(ptaa2);
+ for (i = 0; i < n; i++) {
+ pta = ptaaGetPta(ptaa2, i, L_CLONE);
+ ptaGetArrays(pta, &nax, NULL);
+ ptaGetQuadraticLSF(pta, &a, &b, &c, &nafit);
+ ptad = ptaCreateFromNuma(nax, nafit);
+ pixDisplayPta(pixt2, pixt2, ptad);
+ ptaDestroy(&pta);
+ ptaDestroy(&ptad);
+ numaDestroy(&nax);
+ numaDestroy(&nafit);
+ }
+ regTestWritePixAndCheck(rp, pixt2, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pixt2, 300, 500, "fitted lines superimposed",
+ rp->display);
+ ptaaDestroy(&ptaa1);
+ ptaaDestroy(&ptaa2);
+ pixDestroy(&pixt2);
+
+ /* Build the model for page 7 and dewarp */
+ dewa1 = dewarpaCreate(2, 30, 1, 15, 30);
+ if ((dew1 = dewarpCreate(pixb, 7)) == NULL)
+ return ERROR_INT("\n\n\n FAILURE !!! \n\n\n", rp->testname, 1);
+ dewarpaUseBothArrays(dewa1, 1);
+ dewarpaInsertDewarp(dewa1, dew1);
+ dewarpBuildPageModel(dew1, NULL);
+ dewarpaApplyDisparity(dewa1, 7, pixb, 200, 0, 0, &pixd, NULL);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pixd, 400, 0, "page 7 dewarped", rp->display);
+ pixDestroy(&pixd);
+
+ /* Read page 3, normalize background and binarize */
+ pixs2 = pixRead("1555.003.jpg");
+ pixn2 = pixBackgroundNormSimple(pixs2, NULL, NULL);
+ pixg2 = pixConvertRGBToGray(pixn2, 0.5, 0.3, 0.2);
+ pixb2 = pixThresholdToBinary(pixg2, 130);
+ pixDestroy(&pixn2);
+ pixDestroy(&pixg2);
+ regTestWritePixAndCheck(rp, pixb, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pixb, 0, 400, "binarized input (2)", rp->display);
+
+ /* Minimize and re-apply page 7 disparity to this image */
+ dewarpaInsertRefModels(dewa1, 0, 0);
+ dewarpaApplyDisparity(dewa1, 3, pixb2, 200, 0, 0, &pixd, NULL);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pixd, 400, 400, "page 3 dewarped", rp->display);
+ pixDestroy(&pixd);
+
+ /* Write and read back minimized dewarp struct */
+ dewarpMinimize(dew1);
+ dewarpWrite("/tmp/lept/regout/dewarp.6.dew", dew1);
+ regTestCheckFile(rp, "/tmp/lept/regout/dewarp.6.dew"); /* 6 */
+ dew2 = dewarpRead("/tmp/lept/regout/dewarp.6.dew");
+ dewarpWrite("/tmp/lept/regout/dewarp.7.dew", dew2);
+ regTestCheckFile(rp, "/tmp/lept/regout/dewarp.7.dew"); /* 7 */
+ regTestCompareFiles(rp, 6, 7); /* 8 */
+
+ /* Apply this minimized dew to page 3 in a new dewa */
+ dewa2 = dewarpaCreate(2, 30, 1, 15, 30);
+ dewarpaUseBothArrays(dewa2, 1);
+ dewarpaInsertDewarp(dewa2, dew2);
+ dewarpaInsertRefModels(dewa2, 0, 0);
+ dewarpaListPages(dewa2); /* just for fun: should be 1, 3, 5, 7 */
+ dewarpaApplyDisparity(dewa2, 3, pixb2, 200, 0, 0, &pixd, NULL);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 9 */
+ pixDisplayWithTitle(pixd, 800, 400, "page 3 dewarped again", rp->display);
+ pixDestroy(&pixd);
+
+ /* Minimize, re-populate disparity arrays, and apply again */
+ dewarpMinimize(dew2);
+ dewarpaApplyDisparity(dewa2, 3, pixb2, 200, 0, 0, &pixd, NULL);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 10 */
+ regTestCompareFiles(rp, 9, 10); /* 11 */
+ pixDisplayWithTitle(pixd, 900, 400, "page 3 dewarped yet again",
+ rp->display);
+ pixDestroy(&pixd);
+
+ /* Test a few of the fpix functions */
+ if (!dew2) {
+ L_ERROR("dew2 doesn't exist !!!!\n", "dewarp_reg");
+ return 1;
+ }
+ fpix1 = fpixClone(dew2->sampvdispar);
+ fpixWrite("/tmp/lept/regout/dewarp.12.fpix", fpix1);
+ regTestCheckFile(rp, "/tmp/lept/regout/dewarp.12.fpix"); /* 12 */
+
+ fpix2 = fpixRead("/tmp/lept/regout/dewarp.12.fpix");
+ fpixWrite("/tmp/lept/regout/dewarp.13.fpix", fpix2);
+ regTestCheckFile(rp, "/tmp/lept/regout/dewarp.13.fpix"); /* 13 */
+ regTestCompareFiles(rp, 12, 13); /* 14 */
+ fpix3 = fpixScaleByInteger(fpix2, 30);
+ pix1 = fpixRenderContours(fpix3, 2.0, 0.2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 15 */
+ pixDisplayWithTitle(pix1, 0, 800, "v. disparity contours", rp->display);
+ fpixDestroy(&fpix1);
+ fpixDestroy(&fpix2);
+ fpixDestroy(&fpix3);
+
+ /* Test a few of the dpix functions. Note that we can't compare
+ * 15 with 19, because of a tiny difference due to float roundoff,
+ * so we do an approximate comparison on the images. */
+ dpix1 = fpixConvertToDPix(dew2->sampvdispar);
+ dpixWrite("/tmp/lept/regout/dewarp.16.dpix", dpix1);
+ regTestCheckFile(rp, "/tmp/lept/regout/dewarp.16.dpix"); /* 16 */
+ dpix2 = dpixRead("/tmp/lept/regout/dewarp.16.dpix");
+ dpixWrite("/tmp/lept/regout/dewarp.17.dpix", dpix2);
+ regTestCheckFile(rp, "/tmp/lept/regout/dewarp.17.dpix"); /* 17 */
+ regTestCompareFiles(rp, 16, 17); /* 18 */
+ dpix3 = dpixScaleByInteger(dpix2, 30);
+ fpix3 = dpixConvertToFPix(dpix3);
+ pixt1 = fpixRenderContours(fpix3, 2.0, 0.2);
+ regTestWritePixAndCheck(rp, pixt1, IFF_PNG); /* 19 */
+ pixDisplayWithTitle(pixt1, 400, 800, "v. disparity contours", rp->display);
+ regTestCompareSimilarPix(rp, pix1, pixt1, 1, 0.00001, 0); /* 20 */
+ dpixDestroy(&dpix1);
+ dpixDestroy(&dpix2);
+ dpixDestroy(&dpix3);
+ fpixDestroy(&fpix3);
+ pixDestroy(&pix1);
+ pixDestroy(&pixt1);
+
+ dewarpaDestroy(&dewa1);
+ dewarpaDestroy(&dewa2);
+ pixDestroy(&pixs);
+ pixDestroy(&pixb);
+ pixDestroy(&pixs2);
+ pixDestroy(&pixb2);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/dewarprules.c b/leptonica/prog/dewarprules.c
new file mode 100644
index 00000000..3eab1e1f
--- /dev/null
+++ b/leptonica/prog/dewarprules.c
@@ -0,0 +1,176 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * dewarprules.c
+ *
+ * Syntax: dewarprules select ndew
+ * where select = 0 (sudoku), 1 (graph paper)
+ * ndew = 1 (simple) or 2 (twice with rotations)
+ *
+ * There are two ways to dewarp the images:
+ * (1) use dewarpBuildLineModel() to correct both vertical and
+ * horizontal disparity with 1 dew
+ * (2) use dewarpBuildPageModel() twice, correcting only for
+ * vertical disparity, with 90 degree rotations in between
+ * and at the end.
+ *
+ * A challenge was presented in:
+ * http://stackoverflow.com/questions/10196198/how-to-remove-convexity-defects-in-sudoku-square/10226971#10226971
+ *
+ * Solutions were given there using mathematica and opencv.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 w, h, select, ndew;
+BOXA *boxa1;
+L_DEWARP *dew;
+L_DEWARPA *dewa;
+PIX *pixs, *pixd, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
+PIX *pix8, *pix9, *pix10;
+PIXA *pixa1, *pixa2;
+
+ if (argc != 3) {
+ lept_stderr(" Syntax: dewarprules select ndew\n");
+ return 1;
+ }
+ select = atoi(argv[1]);
+ ndew = atoi(argv[2]);
+
+ setLeptDebugOK(1);
+ lept_mkdir("dewarp");
+
+ if (select == 0) {
+ /* Extract the basic grid from the sudoku image */
+ pixs = pixRead("warped_sudoku.jpg");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pix1 = pixConvertTo1(pixs, 220);
+ boxa1 = pixConnComp(pix1, &pixa1, 8);
+ pixa2 = pixaSelectBySize(pixa1, 400, 400, L_SELECT_IF_BOTH,
+ L_SELECT_IF_GT, NULL);
+ pix2 = pixaDisplay(pixa2, w, h); /* grid */
+ pixDisplay(pix1, 600, 300);
+ pixDisplay(pix2, 100, 100);
+ } else { /* select == 1 */
+ /* Extract the grid from the graph paper image */
+ pixs = pixRead("warped_paper.jpg");
+ pixDisplay(pixs, 1500, 1000);
+ pix3 = pixConvertTo8(pixs, 0);
+ pix4 = pixBackgroundNormSimple(pix3, NULL, NULL);
+ pix5 = pixGammaTRC(NULL, pix4, 1.0, 50, 200);
+ pix1 = pixConvertTo1(pix5, 220);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ boxa1 = pixConnComp(pix1, &pixa1, 8);
+ pixa2 = pixaSelectBySize(pixa1, 400, 400, L_SELECT_IF_BOTH,
+ L_SELECT_IF_GT, NULL);
+ pix2 = pixaDisplay(pixa2, w, h); /* grid */
+ pixDisplay(pix1, 600, 300);
+ pixDisplay(pix2, 600, 400);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ }
+
+ if (ndew == 1) {
+ /* -------------------------------------------------------------------*
+ * Use dewarpBuildLineModel() to correct using both horizontal
+ * and vertical lines with one dew.
+ * -------------------------------------------------------------------*/
+ dewa = dewarpaCreate(1, 30, 1, 4, 50);
+ dewarpaSetCurvatures(dewa, 500, 0, 500, 100, 100, 200);
+ dewarpaUseBothArrays(dewa, 1);
+ dew = dewarpCreate(pix2, 0);
+ dewarpaInsertDewarp(dewa, dew);
+ dewarpBuildLineModel(dew, 10, "/tmp/dewarp/sud.pdf");
+ dewarpaApplyDisparity(dewa, 0, pix1, 255, 0, 0, &pix3, NULL);
+ dewarpaApplyDisparity(dewa, 0, pix2, 255, 0, 0, &pix4, NULL);
+ pixDisplay(pix3, 500, 100);
+ pixDisplay(pix4, 600, 100);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ dewarpaDestroy(&dewa);
+ } else {
+ /* -------------------------------------------------------------------*
+ * Hack: use dewarpBuildPageModel() twice, first straightening
+ * the horizontal lines, then rotating the result by 90 degrees
+ * and doing it again, and finally rotating back by -90 degrees.
+ * -------------------------------------------------------------------*/
+ /* Extract the horizontal lines */
+ pix3 = pixMorphSequence(pix2, "d1.3 + c6.1 + o8.1", 0);
+ pixDisplay(pix3, 600, 100);
+
+ /* Correct for vertical disparity */
+ dewa = dewarpaCreate(1, 30, 1, 4, 50);
+ dewarpaSetCurvatures(dewa, 500, 0, 500, 100, 100, 200);
+ dewarpaUseBothArrays(dewa, 0);
+ dew = dewarpCreate(pix3, 0);
+ dewarpaInsertDewarp(dewa, dew);
+ dewarpBuildPageModel(dew, "/tmp/dewarp/sud1.pdf");
+ dewarpaApplyDisparity(dewa, 0, pix1, 255, 0, 0, &pix4, NULL);
+ dewarpaApplyDisparity(dewa, 0, pix2, 255, 0, 0, &pix5, NULL);
+ pixDisplay(pix4, 500, 100);
+ pixDisplay(pix5, 600, 100);
+ dewarpaDestroy(&dewa);
+
+ /* Rotate result 90 degrees */
+ pix6 = pixRotateOrth(pix4, 1);
+ pix7 = pixRotateOrth(pix5, 1); /* grid: vertical lines now are horiz */
+
+ /* Extract the vertical lines (which are now horizontal) */
+ pix8 = pixMorphSequence(pix7, "d1.3 + c6.1 + o8.1", 0);
+ pixDisplay(pix8, 600, 500);
+
+ /* Correct for vertical (now horizontal) disparity */
+ dewa = dewarpaCreate(1, 30, 1, 4, 50);
+ dewarpaSetCurvatures(dewa, 500, 0, 500, 100, 100, 200);
+ dewarpaUseBothArrays(dewa, 0);
+ dew = dewarpCreate(pix8, 0);
+ dewarpaInsertDewarp(dewa, dew);
+ dewarpBuildPageModel(dew, "/tmp/dewarp/sud2.pdf");
+ dewarpaApplyDisparity(dewa, 0, pix6, 255, 0, 0, &pix9, NULL);
+ dewarpaApplyDisparity(dewa, 0, pix8, 255, 0, 0, &pix10, NULL);
+ pixd = pixRotateOrth(pix9, 3);
+ pixDisplay(pix10, 600, 300);
+ pixDisplay(pixd, 600, 700);
+ }
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxaDestroy(&boxa1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ return 0;
+}
diff --git a/leptonica/prog/dewarptest1.c b/leptonica/prog/dewarptest1.c
new file mode 100644
index 00000000..333f6b4f
--- /dev/null
+++ b/leptonica/prog/dewarptest1.c
@@ -0,0 +1,182 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * dewarptest1.c
+ *
+ * This exercise functions in dewarp.c for dewarping based on lines
+ * of horizontal text. It also creates a 24-image pdf of steps
+ * in the process.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define DO_QUAD 1
+#define DO_CUBIC 0
+#define DO_QUARTIC 0
+
+ /* Default LSF is quadratic on left and right edges.
+ * Set to 1 for linear LSF. */
+#define LINEAR_FIT_ON_EDGES 0
+
+l_int32 main(int argc,
+ char **argv)
+{
+L_DEWARP *dew1, *dew2;
+L_DEWARPA *dewa;
+PIX *pixs, *pixn, *pixg, *pixb, *pixd, *pixt1, *pixt2;
+PIX *pixs2, *pixn2, *pixg2, *pixb2, *pixd2;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/model");
+ lept_rmdir("lept/dewmod");
+ lept_mkdir("lept/dewmod");
+
+/* pixs = pixRead("1555.007.jpg"); */
+ pixs = pixRead("cat.035.jpg");
+/* pixs = pixRead("cat.010.jpg"); */
+
+ /* Normalize for varying background and binarize */
+ pixn = pixBackgroundNormSimple(pixs, NULL, NULL);
+ pixg = pixConvertRGBToGray(pixn, 0.5, 0.3, 0.2);
+ pixb = pixThresholdToBinary(pixg, 130);
+
+ /* Run the basic functions */
+ dewa = dewarpaCreate(2, 30, 1, 10, 30);
+ if (LINEAR_FIT_ON_EDGES)
+ dewarpaSetCurvatures(dewa, -1, -1, -1, 0, -1, -1);
+ dewarpaUseBothArrays(dewa, 1);
+ dew1 = dewarpCreate(pixb, 35);
+ dewarpaInsertDewarp(dewa, dew1);
+ dewarpBuildPageModel(dew1, "/tmp/lept/model/dewarp_model1.pdf");
+ dewarpaApplyDisparity(dewa, 35, pixg, 200, 0, 0, &pixd,
+ "/tmp/lept/model/dewarp_apply1.pdf");
+
+ /* Write out some of the files to be imaged */
+ lept_rmdir("lept/dewtest");
+ lept_mkdir("lept/dewtest");
+ pixWrite("/tmp/lept/dewtest/001.jpg", pixs, IFF_JFIF_JPEG);
+ pixWrite("/tmp/lept/dewtest/002.jpg", pixn, IFF_JFIF_JPEG);
+ pixWrite("/tmp/lept/dewtest/003.jpg", pixg, IFF_JFIF_JPEG);
+ pixWrite("/tmp/lept/dewtest/004.png", pixb, IFF_TIFF_G4);
+ pixWrite("/tmp/lept/dewtest/005.jpg", pixd, IFF_JFIF_JPEG);
+ pixt1 = pixRead("/tmp/lept/dewmod/0020.png");
+ pixWrite("/tmp/lept/dewtest/006.png", pixt1, IFF_PNG);
+ pixDestroy(&pixt1);
+ pixt1 = pixRead("/tmp/lept/dewmod/0030.png");
+ pixWrite("/tmp/lept/dewtest/007.png", pixt1, IFF_PNG);
+ pixDestroy(&pixt1);
+ pixt1 = pixRead("/tmp/lept/dewmod/0060.png");
+ pixWrite("/tmp/lept/dewtest/008.png", pixt1, IFF_PNG);
+ pixDestroy(&pixt1);
+ pixt1 = pixRead("/tmp/lept/dewmod/0070.png");
+ pixWrite("/tmp/lept/dewtest/009.png", pixt1, IFF_PNG);
+ pixDestroy(&pixt1);
+ pixt1 = pixRead("/tmp/lept/dewapply/002.png");
+ pixWrite("/tmp/lept/dewtest/010.png", pixt1, IFF_PNG);
+ pixDestroy(&pixt1);
+ pixt1 = pixRead("/tmp/lept/dewapply/003.png");
+ pixWrite("/tmp/lept/dewtest/011.png", pixt1, IFF_PNG);
+ pixt2 = pixThresholdToBinary(pixt1, 130);
+ pixWrite("/tmp/lept/dewtest/012.png", pixt2, IFF_TIFF_G4);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixt1 = pixRead("/tmp/lept/dewmod/0041.png");
+ pixWrite("/tmp/lept/dewtest/013.png", pixt1, IFF_PNG);
+ pixDestroy(&pixt1);
+ pixt1 = pixRead("/tmp/lept/dewmod/0042.png");
+ pixWrite("/tmp/lept/dewtest/014.png", pixt1, IFF_PNG);
+ pixDestroy(&pixt1);
+ pixt1 = pixRead("/tmp/lept/dewmod/0051.png");
+ pixWrite("/tmp/lept/dewtest/015.png", pixt1, IFF_PNG);
+ pixDestroy(&pixt1);
+ pixt1 = pixRead("/tmp/lept/dewmod/0052.png");
+ pixWrite("/tmp/lept/dewtest/016.png", pixt1, IFF_PNG);
+ pixDestroy(&pixt1);
+
+ /* Normalize another image, that may not have enough textlines
+ * to build an accurate model */
+/* pixs2 = pixRead("1555.003.jpg"); */
+ pixs2 = pixRead("cat.007.jpg");
+/* pixs2 = pixRead("cat.014.jpg"); */
+ pixn2 = pixBackgroundNormSimple(pixs2, NULL, NULL);
+ pixg2 = pixConvertRGBToGray(pixn2, 0.5, 0.3, 0.2);
+ pixb2 = pixThresholdToBinary(pixg2, 130);
+
+ /* Apply the previous disparity model to this image */
+ dew2 = dewarpCreate(pixb2, 7);
+ dewarpaInsertDewarp(dewa, dew2);
+ dewarpaInsertRefModels(dewa, 0, 1);
+ dewarpaInfo(stderr, dewa);
+ dewarpaApplyDisparity(dewa, 7, pixg2, 200, 0, 0, &pixd2,
+ "/tmp/lept/model/dewarp_apply2.pdf");
+ dewarpaDestroy(&dewa);
+
+ /* Write out files for the second image */
+ pixWrite("/tmp/lept/dewtest/017.jpg", pixs2, IFF_JFIF_JPEG);
+ pixWrite("/tmp/lept/dewtest/018.jpg", pixg2, IFF_JFIF_JPEG);
+ pixWrite("/tmp/lept/dewtest/019.png", pixb2, IFF_TIFF_G4);
+ pixWrite("/tmp/lept/dewtest/020.jpg", pixd2, IFF_JFIF_JPEG);
+ pixt1 = pixRead("/tmp/lept/dewmod/0060.png");
+ pixWrite("/tmp/lept/dewtest/021.png", pixt1, IFF_PNG);
+ pixDestroy(&pixt1);
+ pixt1 = pixRead("/tmp/lept/dewapply/002.png");
+ pixWrite("/tmp/lept/dewtest/022.png", pixt1, IFF_PNG);
+ pixt2 = pixThresholdToBinary(pixt1, 130);
+ pixWrite("/tmp/lept/dewtest/023.png", pixt2, IFF_TIFF_G4);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixt1 = pixRead("/tmp/lept/dewmod/0070.png");
+ pixWrite("/tmp/lept/dewtest/024.png", pixt1, IFF_PNG);
+ pixDestroy(&pixt1);
+ pixt1 = pixRead("/tmp/lept/dewapply/003.png");
+ pixWrite("/tmp/lept/dewtest/025.png", pixt1, IFF_PNG);
+ pixt2 = pixThresholdToBinary(pixt1, 130);
+ pixWrite("/tmp/lept/dewtest/026.png", pixt2, IFF_TIFF_G4);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ /* Generate the big pdf file */
+ convertFilesToPdf("/tmp/lept/dewtest", NULL, 135, 1.0, 0, 0, "Dewarp Test",
+ "/tmp/lept/dewarptest1.pdf");
+ lept_stderr("pdf file made: /tmp/lept/model/dewarptest1.pdf\n");
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixn);
+ pixDestroy(&pixg);
+ pixDestroy(&pixb);
+ pixDestroy(&pixd);
+ pixDestroy(&pixs2);
+ pixDestroy(&pixn2);
+ pixDestroy(&pixg2);
+ pixDestroy(&pixb2);
+ pixDestroy(&pixd2);
+ return 0;
+}
diff --git a/leptonica/prog/dewarptest2.c b/leptonica/prog/dewarptest2.c
new file mode 100644
index 00000000..8042ada5
--- /dev/null
+++ b/leptonica/prog/dewarptest2.c
@@ -0,0 +1,123 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * dewarptest2.c
+ *
+ * This runs the basic functions for a single page. It can be used
+ * to debug the disparity model-building.
+ *
+ * dewarptest2 method [image pageno]
+ *
+ * where: method = 1 (use single page dewarp function)
+ * 2 (break down into multiple steps)
+ *
+ * Default image is cat.035.jpg.
+ * Others are 1555.007.jpg, shearer.148.tif, lapide.052.100.jpg, etc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define NORMALIZE 1
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 d, method, pageno;
+L_DEWARP *dew1;
+L_DEWARPA *dewa;
+PIX *pixs, *pixn, *pixg, *pixb, *pixd;
+static char mainName[] = "dewarptest2";
+
+ if (argc != 2 && argc != 4)
+ return ERROR_INT("Syntax: dewarptest2 method [image pageno]",
+ mainName, 1);
+ if (argc == 2) {
+ pixs = pixRead("cat.035.jpg");
+ pageno = 35;
+ }
+ else {
+ pixs = pixRead(argv[2]);
+ pageno = atoi(argv[3]);
+ }
+ if (!pixs)
+ return ERROR_INT("image not read", mainName, 1);
+ method = atoi(argv[1]);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/dewarp");
+
+ if (method == 1) { /* Use single page dewarp function */
+ dewarpSinglePage(pixs, 0, 1, 1, 0, &pixd, NULL, 1);
+ } else { /* Break down into multiple steps; require min of only 8 lines */
+ dewa = dewarpaCreate(40, 30, 1, 8, 50);
+ dewarpaUseBothArrays(dewa, 1);
+ dewarpaSetCheckColumns(dewa, 0);
+ d = pixGetDepth(pixs);
+
+#if NORMALIZE
+ /* Normalize for varying background and binarize */
+ if (d > 1) {
+ pixn = pixBackgroundNormSimple(pixs, NULL, NULL);
+ pixg = pixConvertRGBToGray(pixn, 0.5, 0.3, 0.2);
+ pixb = pixThresholdToBinary(pixg, 130);
+ pixDestroy(&pixn);
+ pixDestroy(&pixg);
+ } else {
+ pixb = pixClone(pixs);
+ }
+#else
+ /* Don't normalize; just threshold and clean edges */
+ if (d > 1) {
+ pixg = pixConvertTo8(pixs, 0);
+ pixb = pixThresholdToBinary(pixg, 100);
+ pixDestroy(&pixg);
+ } else {
+ pixb = pixClone(pixs);
+ }
+ pixSetOrClearBorder(pixb, 30, 30, 40, 40, PIX_CLR);
+#endif
+
+ /* Run the basic functions */
+ dew1 = dewarpCreate(pixb, pageno);
+ dewarpaInsertDewarp(dewa, dew1);
+ dewarpBuildPageModel(dew1, "/tmp/lept/dewarp/test2_model.pdf");
+ dewarpaApplyDisparity(dewa, pageno, pixb, -1, 0, 0, &pixd,
+ "/tmp/lept/dewarp/test2_apply.pdf");
+
+ dewarpaInfo(stderr, dewa);
+ dewarpaDestroy(&dewa);
+ pixDestroy(&pixb);
+ }
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
diff --git a/leptonica/prog/dewarptest3.c b/leptonica/prog/dewarptest3.c
new file mode 100644
index 00000000..c6095660
--- /dev/null
+++ b/leptonica/prog/dewarptest3.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * dewarptest3.c
+ *
+ * This exercise functions in dewarp.c for dewarping based on lines
+ * of horizontal text, showing results for different interpolations
+ * (quadratic, cubic, quartic).
+ *
+ * Inspection of the output pdf shows that using LS fitting beyond
+ * quadratic has a tendency to overfit. So we choose to use
+ * quadratic LSF for the textlines.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i, n;
+l_float32 a, b, c, d, e;
+NUMA *nax, *nafit;
+PIX *pixs, *pixn, *pixg, *pixb, *pixt1, *pixt2;
+PIXA *pixa;
+PTA *pta, *ptad;
+PTAA *ptaa1, *ptaa2;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept");
+
+ pixs = pixRead("cat.035.jpg");
+/* pixs = pixRead("zanotti-78.jpg"); */
+
+ /* Normalize for varying background and binarize */
+ pixn = pixBackgroundNormSimple(pixs, NULL, NULL);
+ pixg = pixConvertRGBToGray(pixn, 0.5, 0.3, 0.2);
+ pixb = pixThresholdToBinary(pixg, 130);
+ pixDestroy(&pixn);
+ pixDestroy(&pixg);
+
+ /* Get the textline centers */
+ pixa = pixaCreate(6);
+ ptaa1 = dewarpGetTextlineCenters(pixb, 0);
+ pixt1 = pixCreateTemplate(pixs);
+ pixSetAll(pixt1);
+ pixt2 = pixDisplayPtaa(pixt1, ptaa1);
+ pixWrite("/tmp/lept/textline1.png", pixt2, IFF_PNG);
+ pixDisplayWithTitle(pixt2, 0, 100, "textline centers 1", 1);
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixDestroy(&pixt1);
+
+ /* Remove short lines */
+ lept_stderr("Num all lines = %d\n", ptaaGetCount(ptaa1));
+ ptaa2 = dewarpRemoveShortLines(pixb, ptaa1, 0.8, 0);
+ pixt1 = pixCreateTemplate(pixs);
+ pixSetAll(pixt1);
+ pixt2 = pixDisplayPtaa(pixt1, ptaa2);
+ pixWrite("/tmp/lept/textline2.png", pixt2, IFF_PNG);
+ pixDisplayWithTitle(pixt2, 300, 100, "textline centers 2", 1);
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixDestroy(&pixt1);
+ n = ptaaGetCount(ptaa2);
+ lept_stderr("Num long lines = %d\n", n);
+ ptaaDestroy(&ptaa1);
+ pixDestroy(&pixb);
+
+ /* Long lines over input image */
+ pixt1 = pixCopy(NULL, pixs);
+ pixt2 = pixDisplayPtaa(pixt1, ptaa2);
+ pixWrite("/tmp/lept/textline3.png", pixt2, IFF_PNG);
+ pixDisplayWithTitle(pixt2, 600, 100, "textline centers 3", 1);
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixDestroy(&pixt1);
+
+ /* Quadratic fit to curve */
+ pixt1 = pixCopy(NULL, pixs);
+ for (i = 0; i < n; i++) {
+ pta = ptaaGetPta(ptaa2, i, L_CLONE);
+ ptaGetArrays(pta, &nax, NULL);
+ ptaGetQuadraticLSF(pta, &a, &b, &c, &nafit);
+ lept_stderr("Quadratic: a = %10.6f, b = %7.3f, c = %7.3f\n", a, b, c);
+ ptad = ptaCreateFromNuma(nax, nafit);
+ pixDisplayPta(pixt1, pixt1, ptad);
+ ptaDestroy(&pta);
+ ptaDestroy(&ptad);
+ numaDestroy(&nax);
+ numaDestroy(&nafit);
+ }
+ pixWrite("/tmp/lept/textline4.png", pixt1, IFF_PNG);
+ pixDisplayWithTitle(pixt1, 900, 100, "textline centers 4", 1);
+ pixaAddPix(pixa, pixt1, L_INSERT);
+
+ /* Cubic fit to curve */
+ pixt1 = pixCopy(NULL, pixs);
+ for (i = 0; i < n; i++) {
+ pta = ptaaGetPta(ptaa2, i, L_CLONE);
+ ptaGetArrays(pta, &nax, NULL);
+ ptaGetCubicLSF(pta, &a, &b, &c, &d, &nafit);
+ lept_stderr("Cubic: a = %10.6f, b = %10.6f, c = %7.3f, d = %7.3f\n",
+ a, b, c, d);
+ ptad = ptaCreateFromNuma(nax, nafit);
+ pixDisplayPta(pixt1, pixt1, ptad);
+ ptaDestroy(&pta);
+ ptaDestroy(&ptad);
+ numaDestroy(&nax);
+ numaDestroy(&nafit);
+ }
+ pixWrite("/tmp/lept/textline5.png", pixt1, IFF_PNG);
+ pixDisplayWithTitle(pixt1, 1200, 100, "textline centers 5", 1);
+ pixaAddPix(pixa, pixt1, L_INSERT);
+
+ /* Quartic fit to curve */
+ pixt1 = pixCopy(NULL, pixs);
+ for (i = 0; i < n; i++) {
+ pta = ptaaGetPta(ptaa2, i, L_CLONE);
+ ptaGetArrays(pta, &nax, NULL);
+ ptaGetQuarticLSF(pta, &a, &b, &c, &d, &e, &nafit);
+ lept_stderr(
+ "Quartic: a = %7.3f, b = %7.3f, c = %9.5f, d = %7.3f, e = %7.3f\n",
+ a, b, c, d, e);
+ ptad = ptaCreateFromNuma(nax, nafit);
+ pixDisplayPta(pixt1, pixt1, ptad);
+ ptaDestroy(&pta);
+ ptaDestroy(&ptad);
+ numaDestroy(&nax);
+ numaDestroy(&nafit);
+ }
+ pixWrite("/tmp/lept/textline6.png", pixt1, IFF_PNG);
+ pixDisplayWithTitle(pixt1, 1500, 100, "textline centers 6", 1);
+ pixaAddPix(pixa, pixt1, L_INSERT);
+
+ pixaConvertToPdf(pixa, 300, 0.5, L_JPEG_ENCODE, 75,
+ "LS fittings to textlines",
+ "/tmp/lept/dewarp_fittings.pdf");
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs);
+ ptaaDestroy(&ptaa2);
+ return 0;
+}
diff --git a/leptonica/prog/dewarptest4.c b/leptonica/prog/dewarptest4.c
new file mode 100644
index 00000000..8e8f4c0a
--- /dev/null
+++ b/leptonica/prog/dewarptest4.c
@@ -0,0 +1,123 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * dewarptest4.c
+ *
+ * Tests serialization functions for dewarpa and dewarp structs.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+L_DEWARP *dew1, *dew2, *dew3;
+L_DEWARPA *dewa1, *dewa2, *dewa3;
+PIX *pixs, *pixn, *pixg, *pixb, *pixd;
+PIX *pixs2, *pixn2, *pixg2, *pixb2, *pixd2;
+PIX *pixd3, *pixc1, *pixc2;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept");
+
+/* pixs = pixRead("1555.007.jpg"); */
+ pixs = pixRead("cat.035.jpg");
+ dewa1 = dewarpaCreate(40, 30, 1, 15, 10);
+ dewarpaUseBothArrays(dewa1, 1);
+
+ /* Normalize for varying background and binarize */
+ pixn = pixBackgroundNormSimple(pixs, NULL, NULL);
+ pixg = pixConvertRGBToGray(pixn, 0.5, 0.3, 0.2);
+ pixb = pixThresholdToBinary(pixg, 130);
+
+ /* Run the basic functions */
+ dew1 = dewarpCreate(pixb, 35);
+ dewarpaInsertDewarp(dewa1, dew1);
+ dewarpBuildPageModel(dew1, "/tmp/lept/dewarp_junk35.pdf");
+ dewarpPopulateFullRes(dew1, pixg, 0, 0);
+ dewarpaApplyDisparity(dewa1, 35, pixg, 200, 0, 0, &pixd,
+ "/tmp/lept/dewarp_debug_35.pdf");
+
+ /* Normalize another image. */
+/* pixs2 = pixRead("1555.003.jpg"); */
+ pixs2 = pixRead("cat.007.jpg");
+ pixn2 = pixBackgroundNormSimple(pixs2, NULL, NULL);
+ pixg2 = pixConvertRGBToGray(pixn2, 0.5, 0.3, 0.2);
+ pixb2 = pixThresholdToBinary(pixg2, 130);
+
+ /* Run the basic functions */
+ dew2 = dewarpCreate(pixb2, 7);
+ dewarpaInsertDewarp(dewa1, dew2);
+ dewarpBuildPageModel(dew2, "/tmp/lept/dewarp_junk7.pdf");
+ dewarpaApplyDisparity(dewa1, 7, pixg, 200, 0, 0, &pixd2,
+ "/tmp/lept/dewarp_debug_7.pdf");
+
+ /* Serialize and deserialize dewarpa */
+ dewarpaWrite("/tmp/lept/dewarpa1.dewa", dewa1);
+ dewa2 = dewarpaRead("/tmp/lept/dewarpa1.dewa");
+ dewarpaWrite("/tmp/lept/dewarpa2.dewa", dewa2);
+ dewa3 = dewarpaRead("/tmp/lept/dewarpa2.dewa");
+ dewarpDebug(dewa3->dewarp[7], "dew1", 7);
+ dewarpaWrite("/tmp/lept/dewarpa3.dewa", dewa3);
+
+ /* Repopulate and show the vertical disparity arrays */
+ dewarpPopulateFullRes(dew1, NULL, 0, 0);
+ pixc1 = fpixRenderContours(dew1->fullvdispar, 2.0, 0.2);
+ pixDisplay(pixc1, 1400, 900);
+ dew3 = dewarpaGetDewarp(dewa2, 35);
+ dewarpPopulateFullRes(dew3, pixs, 0, 0);
+ pixc2 = fpixRenderContours(dew3->fullvdispar, 2.0, 0.2);
+ pixDisplay(pixc2, 1400, 900);
+ dewarpaApplyDisparity(dewa2, 35, pixb, 200, 0, 0, &pixd3,
+ "/tmp/lept/dewarp_debug_35b.pdf");
+ pixDisplay(pixd, 0, 1000);
+ pixDisplay(pixd2, 600, 1000);
+ pixDisplay(pixd3, 1200, 1000);
+ pixDestroy(&pixd3);
+
+ dewarpaDestroy(&dewa1);
+ dewarpaDestroy(&dewa2);
+ dewarpaDestroy(&dewa3);
+ pixDestroy(&pixs);
+ pixDestroy(&pixn);
+ pixDestroy(&pixg);
+ pixDestroy(&pixb);
+ pixDestroy(&pixd);
+ pixDestroy(&pixs2);
+ pixDestroy(&pixn2);
+ pixDestroy(&pixg2);
+ pixDestroy(&pixb2);
+ pixDestroy(&pixd2);
+ pixDestroy(&pixc1);
+ pixDestroy(&pixc2);
+ return 0;
+}
diff --git a/leptonica/prog/dewarptest5.c b/leptonica/prog/dewarptest5.c
new file mode 100644
index 00000000..ec88a7b8
--- /dev/null
+++ b/leptonica/prog/dewarptest5.c
@@ -0,0 +1,140 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * dewarptest5.c
+ *
+ * Tests dewarping model applied to word bounding boxes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static l_int32 pageno = 35;
+static l_int32 build_output = 0;
+static l_int32 apply_output = 0;
+static l_int32 map_output = 1;
+
+l_int32 main(int argc,
+ char **argv)
+{
+char buf[64];
+BOXA *boxa1, *boxa2, *boxa3, *boxa4;
+L_DEWARP *dew;
+L_DEWARPA *dewa;
+PIX *pixs, *pixn, *pixg, *pixb, *pix2, *pix3, *pix4, *pix5, *pix6;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept");
+
+ snprintf(buf, sizeof(buf), "cat.%03d.jpg", pageno);
+ pixs = pixRead(buf);
+ dewa = dewarpaCreate(40, 30, 1, 15, 10);
+ dewarpaUseBothArrays(dewa, 1);
+
+ /* Normalize for varying background and binarize */
+ pixn = pixBackgroundNormSimple(pixs, NULL, NULL);
+ pixg = pixConvertRGBToGray(pixn, 0.5, 0.3, 0.2);
+ pixb = pixThresholdToBinary(pixg, 130);
+ pixDisplay(pixb, 0, 100);
+
+ /* Build the model */
+ dew = dewarpCreate(pixb, pageno);
+ dewarpaInsertDewarp(dewa, dew);
+ if (build_output) {
+ snprintf(buf, sizeof(buf), "/tmp/lept/dewarp_build_%d.pdf", pageno);
+ dewarpBuildPageModel(dew, buf);
+ } else {
+ dewarpBuildPageModel(dew, NULL);
+ }
+
+ /* Apply the model */
+ dewarpPopulateFullRes(dew, pixg, 0, 0);
+ if (apply_output) {
+ snprintf(buf, sizeof(buf), "/tmp/lept/dewarp_apply_%d.pdf", pageno);
+ dewarpaApplyDisparity(dewa, pageno, pixb, 200, 0, 0, &pix2, buf);
+ } else {
+ dewarpaApplyDisparity(dewa, pageno, pixb, 200, 0, 0, &pix2, NULL);
+ }
+ pixDisplay(pix2, 200, 100);
+
+ /* Reverse direction: get the word boxes for the dewarped pix ... */
+ pixGetWordBoxesInTextlines(pix2, 5, 5, 500, 100, &boxa1, NULL);
+ pix3 = pixConvertTo32(pix2);
+ pixRenderBoxaArb(pix3, boxa1, 2, 255, 0, 0);
+ pixDisplay(pix3, 400, 100);
+
+ /* ... and map to the word boxes for the input image */
+ if (map_output) {
+ snprintf(buf, sizeof(buf), "/tmp/lept/dewarp_map1_%d.pdf", pageno);
+ dewarpaApplyDisparityBoxa(dewa, pageno, pix2, boxa1, 0, 0, 0, &boxa2,
+ buf);
+ } else {
+ dewarpaApplyDisparityBoxa(dewa, pageno, pix2, boxa1, 0, 0, 0, &boxa2,
+ NULL);
+ }
+ pix4 = pixConvertTo32(pixb);
+ pixRenderBoxaArb(pix4, boxa2, 2, 0, 255, 0);
+ pixDisplay(pix4, 600, 100);
+
+ /* Forward direction: get the word boxes for the input pix ... */
+ pixGetWordBoxesInTextlines(pixb, 5, 5, 500, 100, &boxa3, NULL);
+ pix5 = pixConvertTo32(pixb);
+ pixRenderBoxaArb(pix5, boxa3, 2, 255, 0, 0);
+ pixDisplay(pix5, 800, 100);
+
+ /* ... and map to the word boxes for the dewarped image */
+ if (map_output) {
+ snprintf(buf, sizeof(buf), "/tmp/lept/dewarp_map2_%d.pdf", pageno);
+ dewarpaApplyDisparityBoxa(dewa, pageno, pixb, boxa3, 1, 0, 0, &boxa4,
+ buf);
+ } else {
+ dewarpaApplyDisparityBoxa(dewa, pageno, pixb, boxa3, 1, 0, 0, &boxa4,
+ NULL);
+ }
+ pix6 = pixConvertTo32(pix2);
+ pixRenderBoxaArb(pix6, boxa4, 2, 0, 255, 0);
+ pixDisplay(pix6, 1000, 100);
+
+ dewarpaDestroy(&dewa);
+ pixDestroy(&pixs);
+ pixDestroy(&pixn);
+ pixDestroy(&pixg);
+ pixDestroy(&pixb);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+ boxaDestroy(&boxa4);
+ return 0;
+}
diff --git a/leptonica/prog/digitprep1.c b/leptonica/prog/digitprep1.c
new file mode 100644
index 00000000..835d8015
--- /dev/null
+++ b/leptonica/prog/digitprep1.c
@@ -0,0 +1,106 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * digitprep1.c
+ *
+ * Extract barcode digits and put in a pixaa (a resource file for
+ * readnum.c).
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 HEIGHT = 32; /* pixels */
+
+int main(int argc,
+ char **argv)
+{
+char buf[16];
+l_int32 i, n, h;
+l_float32 scalefact;
+BOXA *boxa;
+PIX *pixs, *pix1, *pix2;
+PIXA *pixa, *pixas, *pixad;
+PIXAA *paa;
+static char mainName[] = "digitprep1";
+
+ if (argc != 1) {
+ ERROR_INT(" Syntax: digitprep1", mainName, 1);
+ return 1;
+ }
+
+ setLeptDebugOK(1);
+ if ((pixs = pixRead("barcode-digits.png")) == NULL)
+ return ERROR_INT("pixs not read", mainName, 1);
+
+ /* Extract the digits and scale to HEIGHT */
+ boxa = pixConnComp(pixs, &pixa, 8);
+ pixas = pixaSort(pixa, L_SORT_BY_X, L_SORT_INCREASING, NULL, L_CLONE);
+ n = pixaGetCount(pixas);
+
+ /* Move the last ("0") to the first position */
+ pix1 = pixaGetPix(pixas, n - 1, L_CLONE);
+ pixaInsertPix(pixas, 0, pix1, NULL);
+ pixaRemovePix(pixas, n);
+
+ /* Make the output scaled pixa */
+ pixad = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ pixGetDimensions(pix1, NULL, &h, NULL);
+ scalefact = HEIGHT / (l_float32)h;
+ pix2 = pixScale(pix1, scalefact, scalefact);
+ if (pixGetHeight(pix2) != 32)
+ return ERROR_INT("height not 32!", mainName, 1);
+ snprintf(buf, sizeof(buf), "%d", i);
+ pixSetText(pix2, buf);
+ pixaAddPix(pixad, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ /* Save in a pixaa, with 1 pix in each pixa */
+ paa = pixaaCreateFromPixa(pixad, 1, L_CHOOSE_CONSECUTIVE, L_CLONE);
+ pixaaWrite("/tmp/lept/barcode_digits.paa", paa);
+
+ /* Show result */
+ pix1 = pixaaDisplayByPixa(paa, 50, 1.0, 20, 20, 0);
+ pixDisplay(pix1, 100, 100);
+ pixDestroy(&pix1);
+
+ pixDestroy(&pixs);
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa);
+ pixaDestroy(&pixas);
+ pixaDestroy(&pixad);
+ pixaaDestroy(&paa);
+ return 0;
+}
+
+
diff --git a/leptonica/prog/dinos.pac b/leptonica/prog/dinos.pac
new file mode 100644
index 00000000..42ba57d0
--- /dev/null
+++ b/leptonica/prog/dinos.pac
Binary files differ
diff --git a/leptonica/prog/displayboxa.c b/leptonica/prog/displayboxa.c
new file mode 100644
index 00000000..9d096486
--- /dev/null
+++ b/leptonica/prog/displayboxa.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * displayboxa.c
+ *
+ * displayboxa filein first last width fileout
+ *
+ * This reads a boxa from file and generates a composite view of the
+ * boxes, one per "page", tiled in rows.
+ * Set last == -1 to go to the end.
+ * The pix that backs each box is chosen to be the minimum size that
+ * supports every box in the boxa. Each pix (and the box it backs)
+ * is scaled so that the pix width is @width in pixels.
+ * The number of each box is written below the box.
+ *
+ * The minimum allowed width of the backing pix is 30, and the default
+ * width is 100.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 w, h, width, sep, first, last;
+l_float32 scalefact;
+BOXA *boxa1, *boxa2;
+PIX *pixd;
+static char mainName[] = "displayboxa";
+
+ if (argc != 6) {
+ lept_stderr("Syntax error in displayboxa:\n"
+ " displayboxa filein first last width fileout\n");
+ return 1;
+ }
+ filein = argv[1];
+ first = atoi(argv[2]);
+ last = atoi(argv[3]);
+ width = atoi(argv[4]);
+ fileout = argv[5];
+ if (width < 30) {
+ L_ERROR("width too small; setting to 100\n", mainName);
+ width = 100;
+ }
+ setLeptDebugOK(1);
+
+ if ((boxa1 = boxaRead(filein)) == NULL)
+ return ERROR_INT("boxa not made", mainName, 1);
+ boxaGetExtent(boxa1, &w, &h, NULL);
+ scalefact = (l_float32)width / (l_float32)w;
+ boxa2 = boxaTransform(boxa1, 0, 0, scalefact, scalefact);
+ sep = L_MIN(width / 5, 20);
+ pixd = boxaDisplayTiled(boxa2, NULL, first, last, 1500, 2, 1.0, 0, sep, 2);
+ pixWrite(fileout, pixd, IFF_PNG);
+ pixDisplay(pixd, 100, 100);
+
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ pixDestroy(&pixd);
+ return 0;
+}
+
diff --git a/leptonica/prog/displayboxes_on_pixa.c b/leptonica/prog/displayboxes_on_pixa.c
new file mode 100644
index 00000000..db0055dd
--- /dev/null
+++ b/leptonica/prog/displayboxes_on_pixa.c
@@ -0,0 +1,98 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * displayboxes_on_pixa.c
+ *
+ * displayboxes_on_pixa pixain boxaain type width pixaout display
+ *
+ * where 'type' follows the enum in pix.h:
+ * 0: draw red
+ * 1: draw green
+ * 2: draw blue
+ * 4: draw rgb (sequentially)
+ * 5: draw randomly selected colors
+ * and 'display' is a boolean:
+ * 0: no display on screen
+ * 1: display the resulting pixa on the screen, with the images
+ * tiled in rows
+ *
+ * This reads a pixa or a pixacomp from file and a boxaa file, draws
+ * the boxes on the appropriate images, and writes the new pixa out.
+ * No scaling is done.
+ *
+ * The boxa in the input boxaa should be in 1:1 correspondence with the
+ * pix in the input pixa. The number of boxes in each boxa is arbitrary.
+ *
+ * For example, you can call this with:
+ * displayboxes_on_pixa showboxes.pac showboxes2.baa 4 2 /tmp/result.pa 1
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *fileout;
+l_int32 width, type, display;
+BOXAA *baa;
+PIX *pix1;
+PIXA *pixa1, *pixa2;
+
+ if (argc != 7) {
+ lept_stderr("Syntax error:"
+ " displaybaa_on_pixa pixain boxaain type width pixaout display\n");
+ return 1;
+ }
+ setLeptDebugOK(1);
+
+ /* Input file can be either pixa or pixacomp */
+ pixa1 = pixaReadBoth(argv[1]);
+ baa = boxaaRead(argv[2]);
+ type = atoi(argv[3]);
+ width = atoi(argv[4]);
+ fileout = argv[5];
+ display = atoi(argv[6]);
+
+ pixa2 = pixaDisplayBoxaa(pixa1, baa, type, width);
+ pixaWrite(fileout, pixa2);
+
+ if (display) {
+ pix1 = pixaDisplayTiledInRows(pixa2, 32, 1400, 1.0, 0, 10, 0);
+ pixDisplay(pix1, 100, 100);
+ pixDestroy(&pix1);
+ }
+
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ boxaaDestroy(&baa);
+ return 0;
+}
+
diff --git a/leptonica/prog/displaypix.c b/leptonica/prog/displaypix.c
new file mode 100644
index 00000000..40f8e417
--- /dev/null
+++ b/leptonica/prog/displaypix.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * displaypix.c
+ *
+ * This calls pixDisplay(), which:
+ * (1) automatically downscales the image if necessary to display
+ * it without scrollbars, and
+ * (2) launches the selected viewer (default is xzgv)
+ *
+ * Downscaling uses area mapping to avoid moire.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs;
+char *filein;
+static char mainName[] = "displaypix";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: displaypix filein", mainName, 1);
+ filein = argv[1];
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ setLeptDebugOK(1);
+ pixDisplay(pixs, 20, 20);
+ pixDestroy(&pixs);
+ return 0;
+}
diff --git a/leptonica/prog/displaypixa.c b/leptonica/prog/displaypixa.c
new file mode 100644
index 00000000..6638d751
--- /dev/null
+++ b/leptonica/prog/displaypixa.c
@@ -0,0 +1,176 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * displaypixa.c
+ *
+ * displaypixa filein fileout showtext
+ * displaypixa filein scalefact border lossless disp fileout showtext
+ *
+ * where:
+ * showtext = 1 to print text in the text field of each pix below
+ * the image; 0 to skip
+ * disp = 1 to display on the screen; 0 to skip
+ * lossless = 1 for tiff or png
+ *
+ * This reads a pixa or a pixacomp from file and generates a composite of the
+ * images tiled in rows. It also optionally displays on the screen.
+ * No scaling is done if @scalefact == 0.0 or @scalefact == 1.0.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char buf[32];
+char *fileout, *textstr;
+l_int32 n, i, same, maxd, ntext, border, lossless, display, showtext;
+l_float32 scalefact;
+L_BMF *bmf;
+PIX *pix1, *pix2, *pix3, *pix4, *pixd;
+PIXA *pixa, *pixad;
+static char mainName[] = "displaypixa";
+
+ if (argc != 4 && argc != 8) {
+ lept_stderr("Syntax error in displaypixa:\n"
+ " displaypixa filein fileout showtext\n"
+ " displaypixa filein scalefact border"
+ " lossless disp fileout showtext\n");
+ return 1;
+ }
+ setLeptDebugOK(1);
+
+ /* Input file can be either pixa or pixacomp */
+ pixa = pixaReadBoth(argv[1]);
+ pixaCountText(pixa, &ntext);
+
+ if (argc == 4) {
+ fileout = argv[2];
+ showtext = atoi(argv[3]);
+ }
+
+ /* Simple specification; no output text */
+ if (argc == 4 && (showtext == 0 || ntext == 0)) { /* no text output */
+ pixaVerifyDepth(pixa, &same, &maxd);
+ pixd = pixaDisplayTiledInRows(pixa, maxd, 1400, 1.0, 0, 10, 0);
+ pixDisplay(pixd, 100, 100);
+ if (pixGetDepth(pixd) == 1)
+ pixWrite(fileout, pixd, IFF_PNG);
+ else
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ return 0;
+ }
+
+ /* Simple specification with output text */
+ if (argc == 4) { /* showtext == 1 && ntext > 0 */
+ n = pixaGetCount(pixa);
+ bmf = bmfCreate(NULL, 10);
+ pixad = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa, i, L_CLONE);
+ pix2 = pixConvertTo32(pix1);
+ pix3 = pixAddBorderGeneral(pix2, 10, 10, 5, 5, 0xffffff00);
+ textstr = pixGetText(pix1);
+ if (textstr && strlen(textstr) > 0) {
+ snprintf(buf, sizeof(buf), "%s", textstr);
+ pix4 = pixAddSingleTextblock(pix3, bmf, buf, 0xff000000,
+ L_ADD_BELOW, NULL);
+ } else {
+ pix4 = pixClone(pix3);
+ }
+ pixaAddPix(pixad, pix4, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ bmfDestroy(&bmf);
+ pixaVerifyDepth(pixad, &same, &maxd);
+ pixd = pixaDisplayTiledInRows(pixad, maxd, 1400, 1.0, 0, 10, 0);
+ pixDisplay(pixd, 100, 100);
+ if (pixGetDepth(pixd) == 1)
+ pixWrite(fileout, pixd, IFF_PNG);
+ else
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ pixaDestroy(&pixad);
+ return 0;
+ }
+
+ /* Full specification */
+ scalefact = atof(argv[2]);
+ border = atoi(argv[3]);
+ lossless = atoi(argv[4]);
+ display = atoi(argv[5]);
+ fileout = argv[6];
+ showtext = atoi(argv[7]);
+ if (showtext && ntext == 0)
+ L_INFO("No text found in any of the pix\n", mainName);
+ bmf = (showtext && ntext > 0) ? bmfCreate(NULL, 10) : NULL;
+ n = pixaGetCount(pixa);
+ pixad = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa, i, L_CLONE);
+ pix2 = pixConvertTo32(pix1);
+ pix3 = pixAddBorderGeneral(pix2, 10, 10, 5, 5, 0xffffff00);
+ textstr = pixGetText(pix1);
+ if (bmf && textstr && strlen(textstr) > 0) {
+ snprintf(buf, sizeof(buf), "%s", textstr);
+ pix4 = pixAddSingleTextblock(pix3, bmf, buf, 0xff000000,
+ L_ADD_BELOW, NULL);
+ } else {
+ pix4 = pixClone(pix3);
+ }
+ pixaAddPix(pixad, pix4, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ bmfDestroy(&bmf);
+
+ pixaVerifyDepth(pixad, &same, &maxd);
+ pixd = pixaDisplayTiledInRows(pixad, maxd, 1400, scalefact,
+ 0, 10, border);
+ if (display) pixDisplay(pixd, 20, 20);
+ if (pixGetDepth(pixd) == 1 || lossless)
+ pixWrite(fileout, pixd, IFF_PNG);
+ else
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ pixaDestroy(&pixad);
+ return 0;
+}
+
diff --git a/leptonica/prog/distance_reg.c b/leptonica/prog/distance_reg.c
new file mode 100644
index 00000000..642991e3
--- /dev/null
+++ b/leptonica/prog/distance_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * distance_reg.c
+ *
+ * This tests pixDistanceFunction for a variety of usage
+ * with all 8 combinations of these parameters:
+ *
+ * connectivity : 4 or 8
+ * dest depth : 8 or 16
+ * boundary cond : L_BOUNDARY_BG or L_BOUNDARY_FG
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static void TestDistance(PIXA *pixa, PIX *pixs, l_int32 conn,
+ l_int32 depth, l_int32 bc, L_REGPARAMS *rp);
+
+#define DEBUG 0
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, k, index, conn, depth, bc;
+BOX *box;
+PIX *pix, *pixs, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pix = pixRead("feyn.tif");
+ box = boxCreate(383, 338, 1480, 1050);
+ pixs = pixClipRectangle(pix, box, NULL);
+ regTestWritePixAndCheck(rp, pixs, IFF_PNG); /* 0 */
+
+ for (i = 0; i < 2; i++) {
+ conn = 4 + 4 * i;
+ for (j = 0; j < 2; j++) {
+ depth = 8 + 8 * j;
+ for (k = 0; k < 2; k++) {
+ bc = k + 1;
+ index = 4 * i + 2 * j + k;
+ lept_stderr("Set %d\n", index);
+ if (DEBUG) {
+ lept_stderr("%d: conn = %d, depth = %d, bc = %d\n",
+ rp->index + 1, conn, depth, bc);
+ }
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixs, L_COPY);
+ TestDistance(pixa, pixs, conn, depth, bc, rp);
+ pixd = pixaDisplayTiledInColumns(pixa, 4, 1.0, 20, 2);
+ pixDisplayWithTitle(pixd, 0, 0, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+ }
+ }
+ }
+
+ boxDestroy(&box);
+ pixDestroy(&pix);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
+
+
+static void
+TestDistance(PIXA *pixa,
+ PIX *pixs,
+ l_int32 conn,
+ l_int32 depth,
+ l_int32 bc,
+ L_REGPARAMS *rp)
+{
+PIX *pixt1, *pixt2, *pixt3, *pixt4, *pixt5;
+
+ /* Test the distance function and display */
+ pixInvert(pixs, pixs);
+ pixt1 = pixDistanceFunction(pixs, conn, depth, bc);
+ regTestWritePixAndCheck(rp, pixt1, IFF_PNG); /* a + 1 */
+ pixaAddPix(pixa, pixt1, L_INSERT);
+ pixInvert(pixs, pixs);
+ pixt2 = pixMaxDynamicRange(pixt1, L_LOG_SCALE);
+ regTestWritePixAndCheck(rp, pixt2, IFF_JFIF_JPEG); /* a + 2 */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+
+ /* Test the distance function and display with contour rendering */
+ pixInvert(pixs, pixs);
+ pixt1 = pixDistanceFunction(pixs, conn, depth, bc);
+ regTestWritePixAndCheck(rp, pixt1, IFF_PNG); /* a + 3 */
+ pixaAddPix(pixa, pixt1, L_INSERT);
+ pixInvert(pixs, pixs);
+ pixt2 = pixRenderContours(pixt1, 2, 4, 1); /* binary output */
+ regTestWritePixAndCheck(rp, pixt2, IFF_PNG); /* a + 4 */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixt3 = pixRenderContours(pixt1, 2, 4, depth);
+ pixt4 = pixMaxDynamicRange(pixt3, L_LINEAR_SCALE);
+ regTestWritePixAndCheck(rp, pixt4, IFF_JFIF_JPEG); /* a + 5 */
+ pixaAddPix(pixa, pixt4, L_INSERT);
+ pixt5 = pixMaxDynamicRange(pixt3, L_LOG_SCALE);
+ regTestWritePixAndCheck(rp, pixt5, IFF_JFIF_JPEG); /* a + 6 */
+ pixaAddPix(pixa, pixt5, L_INSERT);
+ pixDestroy(&pixt3);
+
+ /* Label all pixels in each c.c. with a color equal to the
+ * max distance of any pixel within that c.c. from the bg.
+ * Note that we've normalized so the dynamic range extends
+ * to 255. For the image here, each unit of distance is
+ * represented by about 21 grayscale units. The largest
+ * distance is 12. */
+ if (depth == 8) {
+ pixt1 = pixDistanceFunction(pixs, conn, depth, bc);
+ pixt4 = pixMaxDynamicRange(pixt1, L_LOG_SCALE);
+ regTestWritePixAndCheck(rp, pixt4, IFF_JFIF_JPEG); /* b + 1 */
+ pixaAddPix(pixa, pixt4, L_INSERT);
+ pixt2 = pixCreateTemplate(pixt1);
+ pixSetMasked(pixt2, pixs, 255);
+ regTestWritePixAndCheck(rp, pixt2, IFF_JFIF_JPEG); /* b + 2 */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixSeedfillGray(pixt1, pixt2, 4);
+ pixt3 = pixMaxDynamicRange(pixt1, L_LINEAR_SCALE);
+ regTestWritePixAndCheck(rp, pixt3, IFF_JFIF_JPEG); /* b + 3 */
+ pixaAddPix(pixa, pixt3, L_INSERT);
+ pixDestroy(&pixt1);
+ }
+
+ return;
+}
diff --git a/leptonica/prog/dither_reg.c b/leptonica/prog/dither_reg.c
new file mode 100644
index 00000000..67eee053
--- /dev/null
+++ b/leptonica/prog/dither_reg.c
@@ -0,0 +1,86 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * dither_reg.c
+ *
+ * Test dithering from 8 bpp to 1 bpp and 2 bpp.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+PIX *pix, *pixs, *pix1, *pix2;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pix = pixRead("test8.jpg");
+ pixs = pixGammaTRC(NULL, pix, 1.3, 0, 255); /* gamma of 1.3, for fun */
+
+ /* Dither to 1 bpp */
+ pix1 = pixDitherToBinary(pixs);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ /* Dither to 2 bpp, with colormap */
+ pix1 = pixDitherTo2bpp(pixs, 1);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix1, 400, 0, NULL, rp->display);
+
+ /* Dither to 2 bpp, without colormap */
+ pix2 = pixDitherTo2bpp(pixs, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix2, 800, 0, NULL, rp->display);
+ regTestComparePix(rp, pix1, pix2); /* 3 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Dither 2x upscale to 1 bpp */
+ pix1 = pixScaleGray2xLIDither(pixs);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix1, 0, 400, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ /* Dither 4x upscale to 1 bpp */
+ pix1 = pixScaleGray4xLIDither(pixs);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix1, 700, 400, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ pixDestroy(&pix);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/dna_reg.c b/leptonica/prog/dna_reg.c
new file mode 100644
index 00000000..fd57efd4
--- /dev/null
+++ b/leptonica/prog/dna_reg.c
@@ -0,0 +1,123 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * dna_reg.c
+ *
+ * Tests basic functioning of L_Dna (number array of doubles)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, nbins, ival;
+l_float64 pi, angle, val, sum;
+L_DNA *da1, *da2, *da3, *da4, *da5;
+L_DNAA *daa1, *daa2;
+GPLOT *gplot;
+NUMA *na, *nahisto, *nax;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "dna_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pi = 3.1415926535;
+ da1 = l_dnaCreate(50);
+ for (i = 0; i < 5000; i++) {
+ angle = 0.02293 * i * pi;
+ val = 999. * sin(angle);
+ l_dnaAddNumber(da1, val);
+ }
+
+ /* Conversion to Numa; I/O for Dna */
+ na = l_dnaConvertToNuma(da1);
+ da2 = numaConvertToDna(na);
+ l_dnaWrite("/tmp/lept/regout/dna1.da", da1);
+ l_dnaWrite("/tmp/lept/regout/dna2.da", da2);
+ da3 = l_dnaRead("/tmp/lept/regout/dna2.da");
+ l_dnaWrite("/tmp/lept/regout/dna3.da", da3);
+ regTestCheckFile(rp, "/tmp/lept/regout/dna1.da"); /* 0 */
+ regTestCheckFile(rp, "/tmp/lept/regout/dna2.da"); /* 1 */
+ regTestCheckFile(rp, "/tmp/lept/regout/dna3.da"); /* 2 */
+ regTestCompareFiles(rp, 1, 2); /* 3 */
+
+ /* I/O for Dnaa */
+ daa1 = l_dnaaCreate(3);
+ l_dnaaAddDna(daa1, da1, L_INSERT);
+ l_dnaaAddDna(daa1, da2, L_INSERT);
+ l_dnaaAddDna(daa1, da3, L_INSERT);
+ l_dnaaWrite("/tmp/lept/regout/dnaa1.daa", daa1);
+ daa2 = l_dnaaRead("/tmp/lept/regout/dnaa1.daa");
+ l_dnaaWrite("/tmp/lept/regout/dnaa2.daa", daa2);
+ regTestCheckFile(rp, "/tmp/lept/regout/dnaa1.daa"); /* 4 */
+ regTestCheckFile(rp, "/tmp/lept/regout/dnaa2.daa"); /* 5 */
+ regTestCompareFiles(rp, 4, 5); /* 6 */
+ l_dnaaDestroy(&daa1);
+ l_dnaaDestroy(&daa2);
+
+ /* Just for fun -- is the numa ok? */
+ nahisto = numaMakeHistogramClipped(na, 12, 2000);
+ nbins = numaGetCount(nahisto);
+ nax = numaMakeSequence(0, 1, nbins);
+ gplot = gplotCreate("/tmp/lept/regout/historoot", GPLOT_PNG,
+ "Histo example", "i", "histo[i]");
+ gplotAddPlot(gplot, nax, nahisto, GPLOT_LINES, "sine");
+ gplotMakeOutput(gplot);
+ regTestCheckFile(rp, "/tmp/lept/regout/historoot.png"); /* 7 */
+ gplotDestroy(&gplot);
+ numaDestroy(&na);
+ numaDestroy(&nax);
+ numaDestroy(&nahisto);
+
+ /* Handling precision of int32 in double */
+ da4 = l_dnaCreate(25);
+ for (i = 0; i < 1000; i++)
+ l_dnaAddNumber(da4, 1928374 * i);
+ l_dnaWrite("/tmp/lept/regout/dna4.da", da4);
+ da5 = l_dnaRead("/tmp/lept/regout/dna4.da");
+ sum = 0;
+ for (i = 0; i < 1000; i++) {
+ l_dnaGetIValue(da5, i, &ival);
+ sum += L_ABS(ival - i * 1928374); /* we better be adding 0 each time */
+ }
+ regTestCompareValues(rp, sum, 0.0, 0.0); /* 8 */
+ l_dnaDestroy(&da4);
+ l_dnaDestroy(&da5);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/dreyfus1.png b/leptonica/prog/dreyfus1.png
new file mode 100644
index 00000000..72f770af
--- /dev/null
+++ b/leptonica/prog/dreyfus1.png
Binary files differ
diff --git a/leptonica/prog/dreyfus2.png b/leptonica/prog/dreyfus2.png
new file mode 100644
index 00000000..aa12421e
--- /dev/null
+++ b/leptonica/prog/dreyfus2.png
Binary files differ
diff --git a/leptonica/prog/dreyfus4.png b/leptonica/prog/dreyfus4.png
new file mode 100644
index 00000000..40184316
--- /dev/null
+++ b/leptonica/prog/dreyfus4.png
Binary files differ
diff --git a/leptonica/prog/dreyfus8.png b/leptonica/prog/dreyfus8.png
new file mode 100644
index 00000000..8e7fe0af
--- /dev/null
+++ b/leptonica/prog/dreyfus8.png
Binary files differ
diff --git a/leptonica/prog/dwalinear.3.c b/leptonica/prog/dwalinear.3.c
new file mode 100644
index 00000000..c08afe39
--- /dev/null
+++ b/leptonica/prog/dwalinear.3.c
@@ -0,0 +1,343 @@
+/*====================================================================*
+ - 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_3()
+ * PIX *pixFMorphopGen_3()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+PIX *pixMorphDwa_3(PIX *pixd, PIX *pixs, l_int32 operation, char *selname);
+PIX *pixFMorphopGen_3(PIX *pixd, PIX *pixs, l_int32 operation, char *selname);
+l_int32 fmorphopgen_low_3(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 = 124;
+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_16h",
+ "sel_17h",
+ "sel_18h",
+ "sel_19h",
+ "sel_20h",
+ "sel_21h",
+ "sel_22h",
+ "sel_23h",
+ "sel_24h",
+ "sel_25h",
+ "sel_26h",
+ "sel_27h",
+ "sel_28h",
+ "sel_29h",
+ "sel_30h",
+ "sel_31h",
+ "sel_32h",
+ "sel_33h",
+ "sel_34h",
+ "sel_35h",
+ "sel_36h",
+ "sel_37h",
+ "sel_38h",
+ "sel_39h",
+ "sel_40h",
+ "sel_41h",
+ "sel_42h",
+ "sel_43h",
+ "sel_44h",
+ "sel_45h",
+ "sel_46h",
+ "sel_47h",
+ "sel_48h",
+ "sel_49h",
+ "sel_50h",
+ "sel_51h",
+ "sel_52h",
+ "sel_53h",
+ "sel_54h",
+ "sel_55h",
+ "sel_56h",
+ "sel_57h",
+ "sel_58h",
+ "sel_59h",
+ "sel_60h",
+ "sel_61h",
+ "sel_62h",
+ "sel_63h",
+ "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_16v",
+ "sel_17v",
+ "sel_18v",
+ "sel_19v",
+ "sel_20v",
+ "sel_21v",
+ "sel_22v",
+ "sel_23v",
+ "sel_24v",
+ "sel_25v",
+ "sel_26v",
+ "sel_27v",
+ "sel_28v",
+ "sel_29v",
+ "sel_30v",
+ "sel_31v",
+ "sel_32v",
+ "sel_33v",
+ "sel_34v",
+ "sel_35v",
+ "sel_36v",
+ "sel_37v",
+ "sel_38v",
+ "sel_39v",
+ "sel_40v",
+ "sel_41v",
+ "sel_42v",
+ "sel_43v",
+ "sel_44v",
+ "sel_45v",
+ "sel_46v",
+ "sel_47v",
+ "sel_48v",
+ "sel_49v",
+ "sel_50v",
+ "sel_51v",
+ "sel_52v",
+ "sel_53v",
+ "sel_54v",
+ "sel_55v",
+ "sel_56v",
+ "sel_57v",
+ "sel_58v",
+ "sel_59v",
+ "sel_60v",
+ "sel_61v",
+ "sel_62v",
+ "sel_63v"};
+
+/*!
+ * \brief pixMorphDwa_3()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] operation L_MORPH_DILATE, L_MORPH_ERODE,
+ * L_MORPH_OPEN, L_MORPH_CLOSE
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixMorphDwa_3(PIX *pixd,
+ PIX *pixs,
+ l_int32 operation,
+ char *selname)
+{
+l_int32 bordercolor, bordersize;
+PIX *pixt1, *pixt2, *pixt3;
+
+ PROCNAME("pixMorphDwa_3");
+
+ 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_3(NULL, pixt1, operation, selname);
+ pixt3 = pixRemoveBorder(pixt2, bordersize);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ if (!pixd)
+ return pixt3;
+
+ pixCopy(pixd, pixt3);
+ pixDestroy(&pixt3);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixFMorphopGen_3()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] operation L_MORPH_DILATE, L_MORPH_ERODE,
+ * L_MORPH_OPEN, L_MORPH_CLOSE
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixFMorphopGen_3(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_3");
+
+ 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_3(datad, w, h, wpld, datat, wpls, index);
+ pixDestroy(&pixt);
+ }
+ else { /* not in-place */
+ pixSetOrClearBorder(pixs, 32, 32, 32, 32, borderop);
+ fmorphopgen_low_3(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_3(datat, w, h, wpls, datas, wpls, index+1);
+ pixSetOrClearBorder(pixt, 32, 32, 32, 32, PIX_CLR);
+ fmorphopgen_low_3(datad, w, h, wpld, datat, wpls, index);
+ }
+ else { /* closing */
+ pixSetOrClearBorder(pixs, 32, 32, 32, 32, PIX_CLR);
+ fmorphopgen_low_3(datat, w, h, wpls, datas, wpls, index);
+ pixSetOrClearBorder(pixt, 32, 32, 32, 32, erodeop);
+ fmorphopgen_low_3(datad, w, h, wpld, datat, wpls, index+1);
+ }
+ pixDestroy(&pixt);
+ }
+
+ return pixd;
+}
+
diff --git a/leptonica/prog/dwalineargen.c b/leptonica/prog/dwalineargen.c
new file mode 100644
index 00000000..bb28fae9
--- /dev/null
+++ b/leptonica/prog/dwalineargen.c
@@ -0,0 +1,79 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * dwalineargen.c
+ *
+ * This generates the C code for the full set of linear Sels,
+ * for dilation, erosion, opening and closing, and for both
+ * horizontal and vertical operations, from length 2 to 63.
+ *
+ * These are put in files:
+ * dwalinear.3.c
+ * dwalinearlow.3.c
+ *
+ * Q. Why is this C code generated here in prog, and not placed
+ * in the library where it can be linked in with all programs?
+ * A. Because the two files it generates have 17K lines of code!
+ * We also make this code available here ("out of the box") so that you
+ * can build and run dwamorph1_reg and dwamorph2_reg, without
+ * first building and running dwalineargen.c
+ *
+ * Q. Why do we build code for operations up to 63 in width and height?
+ * A. Atomic DWA operations work on Sels that have hits and misses
+ * that are not larger than 31 pixel positions from the origin.
+ * Thus, they can implement a horizontal closing up to 63 pixels
+ * wide if the origin is in the center.
+ *
+ * Note the word "atomic". DWA operations can be done on arbitrarily
+ * large Sels using the *ExtendDwa() functions. See morphdwa.c
+ * for details.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+SELA *sela;
+static char mainName[] = "dwalineargen";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: dwalineargen", mainName, 1);
+ setLeptDebugOK(1);
+
+ /* Generate the linear sel dwa code */
+ sela = selaAddDwaLinear(NULL);
+ if (fmorphautogen(sela, 3, "dwalinear"))
+ return 1;
+ selaDestroy(&sela);
+ return 0;
+}
+
diff --git a/leptonica/prog/dwalinearlow.3.c b/leptonica/prog/dwalinearlow.3.c
new file mode 100644
index 00000000..cf654e35
--- /dev/null
+++ b/leptonica/prog/dwalinearlow.3.c
@@ -0,0 +1,16986 @@
+/*====================================================================*
+ - 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_3()
+ *
+ * Static Low-level:
+ * void fdilate_3_*()
+ * void ferode_3_*()
+ */
+
+#include "allheaders.h"
+
+static void fdilate_3_0(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_0(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_1(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_1(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_2(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_2(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_3(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_3(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_4(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_4(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_5(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_5(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_6(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_6(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_7(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_7(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_8(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_8(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_9(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_9(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_10(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_10(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_11(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_11(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_12(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_12(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_13(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_13(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_14(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_14(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_15(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_15(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_16(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_16(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_17(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_17(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_18(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_18(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_19(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_19(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_20(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_20(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_21(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_21(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_22(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_22(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_23(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_23(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_24(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_24(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_25(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_25(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_26(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_26(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_27(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_27(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_28(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_28(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_29(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_29(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_30(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_30(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_31(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_31(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_32(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_32(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_33(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_33(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_34(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_34(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_35(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_35(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_36(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_36(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_37(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_37(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_38(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_38(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_39(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_39(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_40(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_40(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_41(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_41(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_42(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_42(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_43(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_43(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_44(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_44(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_45(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_45(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_46(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_46(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_47(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_47(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_48(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_48(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_49(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_49(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_50(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_50(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_51(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_51(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_52(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_52(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_53(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_53(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_54(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_54(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_55(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_55(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_56(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_56(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_57(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_57(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_58(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_58(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_59(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_59(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_60(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_60(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_61(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_61(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_62(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_62(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_63(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_63(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_64(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_64(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_65(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_65(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_66(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_66(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_67(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_67(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_68(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_68(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_69(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_69(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_70(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_70(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_71(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_71(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_72(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_72(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_73(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_73(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_74(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_74(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_75(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_75(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_76(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_76(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_77(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_77(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_78(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_78(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_79(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_79(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_80(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_80(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_81(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_81(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_82(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_82(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_83(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_83(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_84(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_84(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_85(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_85(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_86(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_86(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_87(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_87(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_88(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_88(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_89(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_89(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_90(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_90(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_91(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_91(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_92(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_92(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_93(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_93(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_94(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_94(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_95(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_95(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_96(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_96(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_97(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_97(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_98(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_98(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_99(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_99(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_100(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_100(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_101(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_101(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_102(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_102(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_103(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_103(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_104(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_104(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_105(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_105(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_106(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_106(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_107(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_107(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_108(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_108(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_109(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_109(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_110(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_110(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_111(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_111(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_112(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_112(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_113(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_113(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_114(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_114(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_115(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_115(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_116(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_116(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_117(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_117(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_118(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_118(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_119(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_119(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_120(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_120(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_121(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_121(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_122(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_122(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void fdilate_3_123(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void ferode_3_123(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+
+
+/*---------------------------------------------------------------------*
+ * Fast morph dispatcher *
+ *---------------------------------------------------------------------*/
+/*!
+ * fmorphopgen_low_3()
+ *
+ * a dispatcher to appropriate low-level code
+ */
+l_int32
+fmorphopgen_low_3(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_3_0(datad, w, h, wpld, datas, wpls);
+ break;
+ case 1:
+ ferode_3_0(datad, w, h, wpld, datas, wpls);
+ break;
+ case 2:
+ fdilate_3_1(datad, w, h, wpld, datas, wpls);
+ break;
+ case 3:
+ ferode_3_1(datad, w, h, wpld, datas, wpls);
+ break;
+ case 4:
+ fdilate_3_2(datad, w, h, wpld, datas, wpls);
+ break;
+ case 5:
+ ferode_3_2(datad, w, h, wpld, datas, wpls);
+ break;
+ case 6:
+ fdilate_3_3(datad, w, h, wpld, datas, wpls);
+ break;
+ case 7:
+ ferode_3_3(datad, w, h, wpld, datas, wpls);
+ break;
+ case 8:
+ fdilate_3_4(datad, w, h, wpld, datas, wpls);
+ break;
+ case 9:
+ ferode_3_4(datad, w, h, wpld, datas, wpls);
+ break;
+ case 10:
+ fdilate_3_5(datad, w, h, wpld, datas, wpls);
+ break;
+ case 11:
+ ferode_3_5(datad, w, h, wpld, datas, wpls);
+ break;
+ case 12:
+ fdilate_3_6(datad, w, h, wpld, datas, wpls);
+ break;
+ case 13:
+ ferode_3_6(datad, w, h, wpld, datas, wpls);
+ break;
+ case 14:
+ fdilate_3_7(datad, w, h, wpld, datas, wpls);
+ break;
+ case 15:
+ ferode_3_7(datad, w, h, wpld, datas, wpls);
+ break;
+ case 16:
+ fdilate_3_8(datad, w, h, wpld, datas, wpls);
+ break;
+ case 17:
+ ferode_3_8(datad, w, h, wpld, datas, wpls);
+ break;
+ case 18:
+ fdilate_3_9(datad, w, h, wpld, datas, wpls);
+ break;
+ case 19:
+ ferode_3_9(datad, w, h, wpld, datas, wpls);
+ break;
+ case 20:
+ fdilate_3_10(datad, w, h, wpld, datas, wpls);
+ break;
+ case 21:
+ ferode_3_10(datad, w, h, wpld, datas, wpls);
+ break;
+ case 22:
+ fdilate_3_11(datad, w, h, wpld, datas, wpls);
+ break;
+ case 23:
+ ferode_3_11(datad, w, h, wpld, datas, wpls);
+ break;
+ case 24:
+ fdilate_3_12(datad, w, h, wpld, datas, wpls);
+ break;
+ case 25:
+ ferode_3_12(datad, w, h, wpld, datas, wpls);
+ break;
+ case 26:
+ fdilate_3_13(datad, w, h, wpld, datas, wpls);
+ break;
+ case 27:
+ ferode_3_13(datad, w, h, wpld, datas, wpls);
+ break;
+ case 28:
+ fdilate_3_14(datad, w, h, wpld, datas, wpls);
+ break;
+ case 29:
+ ferode_3_14(datad, w, h, wpld, datas, wpls);
+ break;
+ case 30:
+ fdilate_3_15(datad, w, h, wpld, datas, wpls);
+ break;
+ case 31:
+ ferode_3_15(datad, w, h, wpld, datas, wpls);
+ break;
+ case 32:
+ fdilate_3_16(datad, w, h, wpld, datas, wpls);
+ break;
+ case 33:
+ ferode_3_16(datad, w, h, wpld, datas, wpls);
+ break;
+ case 34:
+ fdilate_3_17(datad, w, h, wpld, datas, wpls);
+ break;
+ case 35:
+ ferode_3_17(datad, w, h, wpld, datas, wpls);
+ break;
+ case 36:
+ fdilate_3_18(datad, w, h, wpld, datas, wpls);
+ break;
+ case 37:
+ ferode_3_18(datad, w, h, wpld, datas, wpls);
+ break;
+ case 38:
+ fdilate_3_19(datad, w, h, wpld, datas, wpls);
+ break;
+ case 39:
+ ferode_3_19(datad, w, h, wpld, datas, wpls);
+ break;
+ case 40:
+ fdilate_3_20(datad, w, h, wpld, datas, wpls);
+ break;
+ case 41:
+ ferode_3_20(datad, w, h, wpld, datas, wpls);
+ break;
+ case 42:
+ fdilate_3_21(datad, w, h, wpld, datas, wpls);
+ break;
+ case 43:
+ ferode_3_21(datad, w, h, wpld, datas, wpls);
+ break;
+ case 44:
+ fdilate_3_22(datad, w, h, wpld, datas, wpls);
+ break;
+ case 45:
+ ferode_3_22(datad, w, h, wpld, datas, wpls);
+ break;
+ case 46:
+ fdilate_3_23(datad, w, h, wpld, datas, wpls);
+ break;
+ case 47:
+ ferode_3_23(datad, w, h, wpld, datas, wpls);
+ break;
+ case 48:
+ fdilate_3_24(datad, w, h, wpld, datas, wpls);
+ break;
+ case 49:
+ ferode_3_24(datad, w, h, wpld, datas, wpls);
+ break;
+ case 50:
+ fdilate_3_25(datad, w, h, wpld, datas, wpls);
+ break;
+ case 51:
+ ferode_3_25(datad, w, h, wpld, datas, wpls);
+ break;
+ case 52:
+ fdilate_3_26(datad, w, h, wpld, datas, wpls);
+ break;
+ case 53:
+ ferode_3_26(datad, w, h, wpld, datas, wpls);
+ break;
+ case 54:
+ fdilate_3_27(datad, w, h, wpld, datas, wpls);
+ break;
+ case 55:
+ ferode_3_27(datad, w, h, wpld, datas, wpls);
+ break;
+ case 56:
+ fdilate_3_28(datad, w, h, wpld, datas, wpls);
+ break;
+ case 57:
+ ferode_3_28(datad, w, h, wpld, datas, wpls);
+ break;
+ case 58:
+ fdilate_3_29(datad, w, h, wpld, datas, wpls);
+ break;
+ case 59:
+ ferode_3_29(datad, w, h, wpld, datas, wpls);
+ break;
+ case 60:
+ fdilate_3_30(datad, w, h, wpld, datas, wpls);
+ break;
+ case 61:
+ ferode_3_30(datad, w, h, wpld, datas, wpls);
+ break;
+ case 62:
+ fdilate_3_31(datad, w, h, wpld, datas, wpls);
+ break;
+ case 63:
+ ferode_3_31(datad, w, h, wpld, datas, wpls);
+ break;
+ case 64:
+ fdilate_3_32(datad, w, h, wpld, datas, wpls);
+ break;
+ case 65:
+ ferode_3_32(datad, w, h, wpld, datas, wpls);
+ break;
+ case 66:
+ fdilate_3_33(datad, w, h, wpld, datas, wpls);
+ break;
+ case 67:
+ ferode_3_33(datad, w, h, wpld, datas, wpls);
+ break;
+ case 68:
+ fdilate_3_34(datad, w, h, wpld, datas, wpls);
+ break;
+ case 69:
+ ferode_3_34(datad, w, h, wpld, datas, wpls);
+ break;
+ case 70:
+ fdilate_3_35(datad, w, h, wpld, datas, wpls);
+ break;
+ case 71:
+ ferode_3_35(datad, w, h, wpld, datas, wpls);
+ break;
+ case 72:
+ fdilate_3_36(datad, w, h, wpld, datas, wpls);
+ break;
+ case 73:
+ ferode_3_36(datad, w, h, wpld, datas, wpls);
+ break;
+ case 74:
+ fdilate_3_37(datad, w, h, wpld, datas, wpls);
+ break;
+ case 75:
+ ferode_3_37(datad, w, h, wpld, datas, wpls);
+ break;
+ case 76:
+ fdilate_3_38(datad, w, h, wpld, datas, wpls);
+ break;
+ case 77:
+ ferode_3_38(datad, w, h, wpld, datas, wpls);
+ break;
+ case 78:
+ fdilate_3_39(datad, w, h, wpld, datas, wpls);
+ break;
+ case 79:
+ ferode_3_39(datad, w, h, wpld, datas, wpls);
+ break;
+ case 80:
+ fdilate_3_40(datad, w, h, wpld, datas, wpls);
+ break;
+ case 81:
+ ferode_3_40(datad, w, h, wpld, datas, wpls);
+ break;
+ case 82:
+ fdilate_3_41(datad, w, h, wpld, datas, wpls);
+ break;
+ case 83:
+ ferode_3_41(datad, w, h, wpld, datas, wpls);
+ break;
+ case 84:
+ fdilate_3_42(datad, w, h, wpld, datas, wpls);
+ break;
+ case 85:
+ ferode_3_42(datad, w, h, wpld, datas, wpls);
+ break;
+ case 86:
+ fdilate_3_43(datad, w, h, wpld, datas, wpls);
+ break;
+ case 87:
+ ferode_3_43(datad, w, h, wpld, datas, wpls);
+ break;
+ case 88:
+ fdilate_3_44(datad, w, h, wpld, datas, wpls);
+ break;
+ case 89:
+ ferode_3_44(datad, w, h, wpld, datas, wpls);
+ break;
+ case 90:
+ fdilate_3_45(datad, w, h, wpld, datas, wpls);
+ break;
+ case 91:
+ ferode_3_45(datad, w, h, wpld, datas, wpls);
+ break;
+ case 92:
+ fdilate_3_46(datad, w, h, wpld, datas, wpls);
+ break;
+ case 93:
+ ferode_3_46(datad, w, h, wpld, datas, wpls);
+ break;
+ case 94:
+ fdilate_3_47(datad, w, h, wpld, datas, wpls);
+ break;
+ case 95:
+ ferode_3_47(datad, w, h, wpld, datas, wpls);
+ break;
+ case 96:
+ fdilate_3_48(datad, w, h, wpld, datas, wpls);
+ break;
+ case 97:
+ ferode_3_48(datad, w, h, wpld, datas, wpls);
+ break;
+ case 98:
+ fdilate_3_49(datad, w, h, wpld, datas, wpls);
+ break;
+ case 99:
+ ferode_3_49(datad, w, h, wpld, datas, wpls);
+ break;
+ case 100:
+ fdilate_3_50(datad, w, h, wpld, datas, wpls);
+ break;
+ case 101:
+ ferode_3_50(datad, w, h, wpld, datas, wpls);
+ break;
+ case 102:
+ fdilate_3_51(datad, w, h, wpld, datas, wpls);
+ break;
+ case 103:
+ ferode_3_51(datad, w, h, wpld, datas, wpls);
+ break;
+ case 104:
+ fdilate_3_52(datad, w, h, wpld, datas, wpls);
+ break;
+ case 105:
+ ferode_3_52(datad, w, h, wpld, datas, wpls);
+ break;
+ case 106:
+ fdilate_3_53(datad, w, h, wpld, datas, wpls);
+ break;
+ case 107:
+ ferode_3_53(datad, w, h, wpld, datas, wpls);
+ break;
+ case 108:
+ fdilate_3_54(datad, w, h, wpld, datas, wpls);
+ break;
+ case 109:
+ ferode_3_54(datad, w, h, wpld, datas, wpls);
+ break;
+ case 110:
+ fdilate_3_55(datad, w, h, wpld, datas, wpls);
+ break;
+ case 111:
+ ferode_3_55(datad, w, h, wpld, datas, wpls);
+ break;
+ case 112:
+ fdilate_3_56(datad, w, h, wpld, datas, wpls);
+ break;
+ case 113:
+ ferode_3_56(datad, w, h, wpld, datas, wpls);
+ break;
+ case 114:
+ fdilate_3_57(datad, w, h, wpld, datas, wpls);
+ break;
+ case 115:
+ ferode_3_57(datad, w, h, wpld, datas, wpls);
+ break;
+ case 116:
+ fdilate_3_58(datad, w, h, wpld, datas, wpls);
+ break;
+ case 117:
+ ferode_3_58(datad, w, h, wpld, datas, wpls);
+ break;
+ case 118:
+ fdilate_3_59(datad, w, h, wpld, datas, wpls);
+ break;
+ case 119:
+ ferode_3_59(datad, w, h, wpld, datas, wpls);
+ break;
+ case 120:
+ fdilate_3_60(datad, w, h, wpld, datas, wpls);
+ break;
+ case 121:
+ ferode_3_60(datad, w, h, wpld, datas, wpls);
+ break;
+ case 122:
+ fdilate_3_61(datad, w, h, wpld, datas, wpls);
+ break;
+ case 123:
+ ferode_3_61(datad, w, h, wpld, datas, wpls);
+ break;
+ case 124:
+ fdilate_3_62(datad, w, h, wpld, datas, wpls);
+ break;
+ case 125:
+ ferode_3_62(datad, w, h, wpld, datas, wpls);
+ break;
+ case 126:
+ fdilate_3_63(datad, w, h, wpld, datas, wpls);
+ break;
+ case 127:
+ ferode_3_63(datad, w, h, wpld, datas, wpls);
+ break;
+ case 128:
+ fdilate_3_64(datad, w, h, wpld, datas, wpls);
+ break;
+ case 129:
+ ferode_3_64(datad, w, h, wpld, datas, wpls);
+ break;
+ case 130:
+ fdilate_3_65(datad, w, h, wpld, datas, wpls);
+ break;
+ case 131:
+ ferode_3_65(datad, w, h, wpld, datas, wpls);
+ break;
+ case 132:
+ fdilate_3_66(datad, w, h, wpld, datas, wpls);
+ break;
+ case 133:
+ ferode_3_66(datad, w, h, wpld, datas, wpls);
+ break;
+ case 134:
+ fdilate_3_67(datad, w, h, wpld, datas, wpls);
+ break;
+ case 135:
+ ferode_3_67(datad, w, h, wpld, datas, wpls);
+ break;
+ case 136:
+ fdilate_3_68(datad, w, h, wpld, datas, wpls);
+ break;
+ case 137:
+ ferode_3_68(datad, w, h, wpld, datas, wpls);
+ break;
+ case 138:
+ fdilate_3_69(datad, w, h, wpld, datas, wpls);
+ break;
+ case 139:
+ ferode_3_69(datad, w, h, wpld, datas, wpls);
+ break;
+ case 140:
+ fdilate_3_70(datad, w, h, wpld, datas, wpls);
+ break;
+ case 141:
+ ferode_3_70(datad, w, h, wpld, datas, wpls);
+ break;
+ case 142:
+ fdilate_3_71(datad, w, h, wpld, datas, wpls);
+ break;
+ case 143:
+ ferode_3_71(datad, w, h, wpld, datas, wpls);
+ break;
+ case 144:
+ fdilate_3_72(datad, w, h, wpld, datas, wpls);
+ break;
+ case 145:
+ ferode_3_72(datad, w, h, wpld, datas, wpls);
+ break;
+ case 146:
+ fdilate_3_73(datad, w, h, wpld, datas, wpls);
+ break;
+ case 147:
+ ferode_3_73(datad, w, h, wpld, datas, wpls);
+ break;
+ case 148:
+ fdilate_3_74(datad, w, h, wpld, datas, wpls);
+ break;
+ case 149:
+ ferode_3_74(datad, w, h, wpld, datas, wpls);
+ break;
+ case 150:
+ fdilate_3_75(datad, w, h, wpld, datas, wpls);
+ break;
+ case 151:
+ ferode_3_75(datad, w, h, wpld, datas, wpls);
+ break;
+ case 152:
+ fdilate_3_76(datad, w, h, wpld, datas, wpls);
+ break;
+ case 153:
+ ferode_3_76(datad, w, h, wpld, datas, wpls);
+ break;
+ case 154:
+ fdilate_3_77(datad, w, h, wpld, datas, wpls);
+ break;
+ case 155:
+ ferode_3_77(datad, w, h, wpld, datas, wpls);
+ break;
+ case 156:
+ fdilate_3_78(datad, w, h, wpld, datas, wpls);
+ break;
+ case 157:
+ ferode_3_78(datad, w, h, wpld, datas, wpls);
+ break;
+ case 158:
+ fdilate_3_79(datad, w, h, wpld, datas, wpls);
+ break;
+ case 159:
+ ferode_3_79(datad, w, h, wpld, datas, wpls);
+ break;
+ case 160:
+ fdilate_3_80(datad, w, h, wpld, datas, wpls);
+ break;
+ case 161:
+ ferode_3_80(datad, w, h, wpld, datas, wpls);
+ break;
+ case 162:
+ fdilate_3_81(datad, w, h, wpld, datas, wpls);
+ break;
+ case 163:
+ ferode_3_81(datad, w, h, wpld, datas, wpls);
+ break;
+ case 164:
+ fdilate_3_82(datad, w, h, wpld, datas, wpls);
+ break;
+ case 165:
+ ferode_3_82(datad, w, h, wpld, datas, wpls);
+ break;
+ case 166:
+ fdilate_3_83(datad, w, h, wpld, datas, wpls);
+ break;
+ case 167:
+ ferode_3_83(datad, w, h, wpld, datas, wpls);
+ break;
+ case 168:
+ fdilate_3_84(datad, w, h, wpld, datas, wpls);
+ break;
+ case 169:
+ ferode_3_84(datad, w, h, wpld, datas, wpls);
+ break;
+ case 170:
+ fdilate_3_85(datad, w, h, wpld, datas, wpls);
+ break;
+ case 171:
+ ferode_3_85(datad, w, h, wpld, datas, wpls);
+ break;
+ case 172:
+ fdilate_3_86(datad, w, h, wpld, datas, wpls);
+ break;
+ case 173:
+ ferode_3_86(datad, w, h, wpld, datas, wpls);
+ break;
+ case 174:
+ fdilate_3_87(datad, w, h, wpld, datas, wpls);
+ break;
+ case 175:
+ ferode_3_87(datad, w, h, wpld, datas, wpls);
+ break;
+ case 176:
+ fdilate_3_88(datad, w, h, wpld, datas, wpls);
+ break;
+ case 177:
+ ferode_3_88(datad, w, h, wpld, datas, wpls);
+ break;
+ case 178:
+ fdilate_3_89(datad, w, h, wpld, datas, wpls);
+ break;
+ case 179:
+ ferode_3_89(datad, w, h, wpld, datas, wpls);
+ break;
+ case 180:
+ fdilate_3_90(datad, w, h, wpld, datas, wpls);
+ break;
+ case 181:
+ ferode_3_90(datad, w, h, wpld, datas, wpls);
+ break;
+ case 182:
+ fdilate_3_91(datad, w, h, wpld, datas, wpls);
+ break;
+ case 183:
+ ferode_3_91(datad, w, h, wpld, datas, wpls);
+ break;
+ case 184:
+ fdilate_3_92(datad, w, h, wpld, datas, wpls);
+ break;
+ case 185:
+ ferode_3_92(datad, w, h, wpld, datas, wpls);
+ break;
+ case 186:
+ fdilate_3_93(datad, w, h, wpld, datas, wpls);
+ break;
+ case 187:
+ ferode_3_93(datad, w, h, wpld, datas, wpls);
+ break;
+ case 188:
+ fdilate_3_94(datad, w, h, wpld, datas, wpls);
+ break;
+ case 189:
+ ferode_3_94(datad, w, h, wpld, datas, wpls);
+ break;
+ case 190:
+ fdilate_3_95(datad, w, h, wpld, datas, wpls);
+ break;
+ case 191:
+ ferode_3_95(datad, w, h, wpld, datas, wpls);
+ break;
+ case 192:
+ fdilate_3_96(datad, w, h, wpld, datas, wpls);
+ break;
+ case 193:
+ ferode_3_96(datad, w, h, wpld, datas, wpls);
+ break;
+ case 194:
+ fdilate_3_97(datad, w, h, wpld, datas, wpls);
+ break;
+ case 195:
+ ferode_3_97(datad, w, h, wpld, datas, wpls);
+ break;
+ case 196:
+ fdilate_3_98(datad, w, h, wpld, datas, wpls);
+ break;
+ case 197:
+ ferode_3_98(datad, w, h, wpld, datas, wpls);
+ break;
+ case 198:
+ fdilate_3_99(datad, w, h, wpld, datas, wpls);
+ break;
+ case 199:
+ ferode_3_99(datad, w, h, wpld, datas, wpls);
+ break;
+ case 200:
+ fdilate_3_100(datad, w, h, wpld, datas, wpls);
+ break;
+ case 201:
+ ferode_3_100(datad, w, h, wpld, datas, wpls);
+ break;
+ case 202:
+ fdilate_3_101(datad, w, h, wpld, datas, wpls);
+ break;
+ case 203:
+ ferode_3_101(datad, w, h, wpld, datas, wpls);
+ break;
+ case 204:
+ fdilate_3_102(datad, w, h, wpld, datas, wpls);
+ break;
+ case 205:
+ ferode_3_102(datad, w, h, wpld, datas, wpls);
+ break;
+ case 206:
+ fdilate_3_103(datad, w, h, wpld, datas, wpls);
+ break;
+ case 207:
+ ferode_3_103(datad, w, h, wpld, datas, wpls);
+ break;
+ case 208:
+ fdilate_3_104(datad, w, h, wpld, datas, wpls);
+ break;
+ case 209:
+ ferode_3_104(datad, w, h, wpld, datas, wpls);
+ break;
+ case 210:
+ fdilate_3_105(datad, w, h, wpld, datas, wpls);
+ break;
+ case 211:
+ ferode_3_105(datad, w, h, wpld, datas, wpls);
+ break;
+ case 212:
+ fdilate_3_106(datad, w, h, wpld, datas, wpls);
+ break;
+ case 213:
+ ferode_3_106(datad, w, h, wpld, datas, wpls);
+ break;
+ case 214:
+ fdilate_3_107(datad, w, h, wpld, datas, wpls);
+ break;
+ case 215:
+ ferode_3_107(datad, w, h, wpld, datas, wpls);
+ break;
+ case 216:
+ fdilate_3_108(datad, w, h, wpld, datas, wpls);
+ break;
+ case 217:
+ ferode_3_108(datad, w, h, wpld, datas, wpls);
+ break;
+ case 218:
+ fdilate_3_109(datad, w, h, wpld, datas, wpls);
+ break;
+ case 219:
+ ferode_3_109(datad, w, h, wpld, datas, wpls);
+ break;
+ case 220:
+ fdilate_3_110(datad, w, h, wpld, datas, wpls);
+ break;
+ case 221:
+ ferode_3_110(datad, w, h, wpld, datas, wpls);
+ break;
+ case 222:
+ fdilate_3_111(datad, w, h, wpld, datas, wpls);
+ break;
+ case 223:
+ ferode_3_111(datad, w, h, wpld, datas, wpls);
+ break;
+ case 224:
+ fdilate_3_112(datad, w, h, wpld, datas, wpls);
+ break;
+ case 225:
+ ferode_3_112(datad, w, h, wpld, datas, wpls);
+ break;
+ case 226:
+ fdilate_3_113(datad, w, h, wpld, datas, wpls);
+ break;
+ case 227:
+ ferode_3_113(datad, w, h, wpld, datas, wpls);
+ break;
+ case 228:
+ fdilate_3_114(datad, w, h, wpld, datas, wpls);
+ break;
+ case 229:
+ ferode_3_114(datad, w, h, wpld, datas, wpls);
+ break;
+ case 230:
+ fdilate_3_115(datad, w, h, wpld, datas, wpls);
+ break;
+ case 231:
+ ferode_3_115(datad, w, h, wpld, datas, wpls);
+ break;
+ case 232:
+ fdilate_3_116(datad, w, h, wpld, datas, wpls);
+ break;
+ case 233:
+ ferode_3_116(datad, w, h, wpld, datas, wpls);
+ break;
+ case 234:
+ fdilate_3_117(datad, w, h, wpld, datas, wpls);
+ break;
+ case 235:
+ ferode_3_117(datad, w, h, wpld, datas, wpls);
+ break;
+ case 236:
+ fdilate_3_118(datad, w, h, wpld, datas, wpls);
+ break;
+ case 237:
+ ferode_3_118(datad, w, h, wpld, datas, wpls);
+ break;
+ case 238:
+ fdilate_3_119(datad, w, h, wpld, datas, wpls);
+ break;
+ case 239:
+ ferode_3_119(datad, w, h, wpld, datas, wpls);
+ break;
+ case 240:
+ fdilate_3_120(datad, w, h, wpld, datas, wpls);
+ break;
+ case 241:
+ ferode_3_120(datad, w, h, wpld, datas, wpls);
+ break;
+ case 242:
+ fdilate_3_121(datad, w, h, wpld, datas, wpls);
+ break;
+ case 243:
+ ferode_3_121(datad, w, h, wpld, datas, wpls);
+ break;
+ case 244:
+ fdilate_3_122(datad, w, h, wpld, datas, wpls);
+ break;
+ case 245:
+ ferode_3_122(datad, w, h, wpld, datas, wpls);
+ break;
+ case 246:
+ fdilate_3_123(datad, w, h, wpld, datas, wpls);
+ break;
+ case 247:
+ ferode_3_123(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_3_0(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_0(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_1(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_1(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_2(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_2(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_3(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_3(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_4(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_4(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_5(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_5(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_6(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_6(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_7(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_7(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_8(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_8(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_9(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_9(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_10(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_10(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_11(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_11(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_12(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_12(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_13(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_13(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_14(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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_3_14(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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_3_15(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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));
+ }
+ }
+}
+
+static void
+ferode_3_15(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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));
+ }
+ }
+}
+
+static void
+fdilate_3_16(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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));
+ }
+ }
+}
+
+static void
+ferode_3_16(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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));
+ }
+ }
+}
+
+static void
+fdilate_3_17(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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_3_17(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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_3_18(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_18(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_19(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_19(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_20(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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_3_20(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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_3_21(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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));
+ }
+ }
+}
+
+static void
+ferode_3_21(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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));
+ }
+ }
+}
+
+static void
+fdilate_3_22(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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));
+ }
+ }
+}
+
+static void
+ferode_3_22(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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));
+ }
+ }
+}
+
+static void
+fdilate_3_23(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_23(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_24(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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_3_24(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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_3_25(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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));
+ }
+ }
+}
+
+static void
+ferode_3_25(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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));
+ }
+ }
+}
+
+static void
+fdilate_3_26(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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));
+ }
+ }
+}
+
+static void
+ferode_3_26(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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));
+ }
+ }
+}
+
+static void
+fdilate_3_27(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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_3_27(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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_3_28(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_28(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_29(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_29(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_30(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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_3_30(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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_3_31(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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));
+ }
+ }
+}
+
+static void
+ferode_3_31(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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));
+ }
+ }
+}
+
+static void
+fdilate_3_32(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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));
+ }
+ }
+}
+
+static void
+ferode_3_32(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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));
+ }
+ }
+}
+
+static void
+fdilate_3_33(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_33(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_34(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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_3_34(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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_3_35(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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));
+ }
+ }
+}
+
+static void
+ferode_3_35(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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));
+ }
+ }
+}
+
+static void
+fdilate_3_36(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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));
+ }
+ }
+}
+
+static void
+ferode_3_36(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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));
+ }
+ }
+}
+
+static void
+fdilate_3_37(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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_3_37(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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_3_38(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_38(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_39(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_39(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_40(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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_3_40(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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_3_41(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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));
+ }
+ }
+}
+
+static void
+ferode_3_41(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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));
+ }
+ }
+}
+
+static void
+fdilate_3_42(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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));
+ }
+ }
+}
+
+static void
+ferode_3_42(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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));
+ }
+ }
+}
+
+static void
+fdilate_3_43(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_43(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_44(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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_3_44(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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_3_45(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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));
+ }
+ }
+}
+
+static void
+ferode_3_45(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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));
+ }
+ }
+}
+
+static void
+fdilate_3_46(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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));
+ }
+ }
+}
+
+static void
+ferode_3_46(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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));
+ }
+ }
+}
+
+static void
+fdilate_3_47(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 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_3_47(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 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_3_48(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_48(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_49(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_49(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_50(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 26) | (*(sptr + 1) >> 6)) |
+ ((*(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_3_50(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 26) | (*(sptr - 1) << 6)) &
+ ((*(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_3_51(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 26) | (*(sptr + 1) >> 6)) |
+ ((*(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)) |
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6));
+ }
+ }
+}
+
+static void
+ferode_3_51(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 26) | (*(sptr - 1) << 6)) &
+ ((*(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)) &
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6));
+ }
+ }
+}
+
+static void
+fdilate_3_52(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 26) | (*(sptr + 1) >> 6)) |
+ ((*(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)) |
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6));
+ }
+ }
+}
+
+static void
+ferode_3_52(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 26) | (*(sptr - 1) << 6)) &
+ ((*(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)) &
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6));
+ }
+ }
+}
+
+static void
+fdilate_3_53(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 26) | (*(sptr + 1) >> 6)) |
+ ((*(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)) |
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) |
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5));
+ }
+ }
+}
+
+static void
+ferode_3_53(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 26) | (*(sptr - 1) << 6)) &
+ ((*(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)) &
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) &
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5));
+ }
+ }
+}
+
+static void
+fdilate_3_54(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 28) | (*(sptr + 1) >> 4)) |
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) |
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) |
+ ((*(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)) |
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) |
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5));
+ }
+ }
+}
+
+static void
+ferode_3_54(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 28) | (*(sptr - 1) << 4)) &
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) &
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) &
+ ((*(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)) &
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) &
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5));
+ }
+ }
+}
+
+static void
+fdilate_3_55(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 28) | (*(sptr + 1) >> 4)) |
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) |
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) |
+ ((*(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)) |
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) |
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) |
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4));
+ }
+ }
+}
+
+static void
+ferode_3_55(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 28) | (*(sptr - 1) << 4)) &
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) &
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) &
+ ((*(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)) &
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) &
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) &
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4));
+ }
+ }
+}
+
+static void
+fdilate_3_56(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 29) | (*(sptr + 1) >> 3)) |
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4)) |
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) |
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) |
+ ((*(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)) |
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) |
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) |
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4));
+ }
+ }
+}
+
+static void
+ferode_3_56(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 29) | (*(sptr - 1) << 3)) &
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4)) &
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) &
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) &
+ ((*(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)) &
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) &
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) &
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4));
+ }
+ }
+}
+
+static void
+fdilate_3_57(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 29) | (*(sptr + 1) >> 3)) |
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4)) |
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) |
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) |
+ ((*(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)) |
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) |
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) |
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4)) |
+ ((*(sptr) >> 29) | (*(sptr - 1) << 3));
+ }
+ }
+}
+
+static void
+ferode_3_57(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 29) | (*(sptr - 1) << 3)) &
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4)) &
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) &
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) &
+ ((*(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)) &
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) &
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) &
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4)) &
+ ((*(sptr) << 29) | (*(sptr + 1) >> 3));
+ }
+ }
+}
+
+static void
+fdilate_3_58(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 30) | (*(sptr + 1) >> 2)) |
+ ((*(sptr) << 29) | (*(sptr + 1) >> 3)) |
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4)) |
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) |
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) |
+ ((*(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)) |
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) |
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) |
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4)) |
+ ((*(sptr) >> 29) | (*(sptr - 1) << 3));
+ }
+ }
+}
+
+static void
+ferode_3_58(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 30) | (*(sptr - 1) << 2)) &
+ ((*(sptr) >> 29) | (*(sptr - 1) << 3)) &
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4)) &
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) &
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) &
+ ((*(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)) &
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) &
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) &
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4)) &
+ ((*(sptr) << 29) | (*(sptr + 1) >> 3));
+ }
+ }
+}
+
+static void
+fdilate_3_59(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 30) | (*(sptr + 1) >> 2)) |
+ ((*(sptr) << 29) | (*(sptr + 1) >> 3)) |
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4)) |
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) |
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) |
+ ((*(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)) |
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) |
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) |
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4)) |
+ ((*(sptr) >> 29) | (*(sptr - 1) << 3)) |
+ ((*(sptr) >> 30) | (*(sptr - 1) << 2));
+ }
+ }
+}
+
+static void
+ferode_3_59(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 30) | (*(sptr - 1) << 2)) &
+ ((*(sptr) >> 29) | (*(sptr - 1) << 3)) &
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4)) &
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) &
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) &
+ ((*(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)) &
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) &
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) &
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4)) &
+ ((*(sptr) << 29) | (*(sptr + 1) >> 3)) &
+ ((*(sptr) << 30) | (*(sptr + 1) >> 2));
+ }
+ }
+}
+
+static void
+fdilate_3_60(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 31) | (*(sptr + 1) >> 1)) |
+ ((*(sptr) << 30) | (*(sptr + 1) >> 2)) |
+ ((*(sptr) << 29) | (*(sptr + 1) >> 3)) |
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4)) |
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) |
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) |
+ ((*(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)) |
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) |
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) |
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4)) |
+ ((*(sptr) >> 29) | (*(sptr - 1) << 3)) |
+ ((*(sptr) >> 30) | (*(sptr - 1) << 2));
+ }
+ }
+}
+
+static void
+ferode_3_60(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 31) | (*(sptr - 1) << 1)) &
+ ((*(sptr) >> 30) | (*(sptr - 1) << 2)) &
+ ((*(sptr) >> 29) | (*(sptr - 1) << 3)) &
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4)) &
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) &
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) &
+ ((*(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)) &
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) &
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) &
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4)) &
+ ((*(sptr) << 29) | (*(sptr + 1) >> 3)) &
+ ((*(sptr) << 30) | (*(sptr + 1) >> 2));
+ }
+ }
+}
+
+static void
+fdilate_3_61(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) << 31) | (*(sptr + 1) >> 1)) |
+ ((*(sptr) << 30) | (*(sptr + 1) >> 2)) |
+ ((*(sptr) << 29) | (*(sptr + 1) >> 3)) |
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4)) |
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) |
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) |
+ ((*(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)) |
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) |
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) |
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4)) |
+ ((*(sptr) >> 29) | (*(sptr - 1) << 3)) |
+ ((*(sptr) >> 30) | (*(sptr - 1) << 2)) |
+ ((*(sptr) >> 31) | (*(sptr - 1) << 1));
+ }
+ }
+}
+
+static void
+ferode_3_61(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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) >> 31) | (*(sptr - 1) << 1)) &
+ ((*(sptr) >> 30) | (*(sptr - 1) << 2)) &
+ ((*(sptr) >> 29) | (*(sptr - 1) << 3)) &
+ ((*(sptr) >> 28) | (*(sptr - 1) << 4)) &
+ ((*(sptr) >> 27) | (*(sptr - 1) << 5)) &
+ ((*(sptr) >> 26) | (*(sptr - 1) << 6)) &
+ ((*(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)) &
+ ((*(sptr) << 26) | (*(sptr + 1) >> 6)) &
+ ((*(sptr) << 27) | (*(sptr + 1) >> 5)) &
+ ((*(sptr) << 28) | (*(sptr + 1) >> 4)) &
+ ((*(sptr) << 29) | (*(sptr + 1) >> 3)) &
+ ((*(sptr) << 30) | (*(sptr + 1) >> 2)) &
+ ((*(sptr) << 31) | (*(sptr + 1) >> 1));
+ }
+ }
+}
+
+static void
+fdilate_3_62(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_62(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_63(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_63(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_64(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_64(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_65(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_65(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_66(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_66(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_67(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_67(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_68(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_68(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_69(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_69(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_70(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_70(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_71(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_71(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_72(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_72(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_73(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_73(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_74(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_74(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_75(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_75(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_76(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+
+ wpls2 = 2 * wpls;
+ wpls3 = 3 * wpls;
+ wpls4 = 4 * wpls;
+ wpls5 = 5 * wpls;
+ wpls6 = 6 * 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 + 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_3_76(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+
+ wpls2 = 2 * wpls;
+ wpls3 = 3 * wpls;
+ wpls4 = 4 * wpls;
+ wpls5 = 5 * wpls;
+ wpls6 = 6 * 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 - 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_3_77(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+
+ wpls2 = 2 * wpls;
+ wpls3 = 3 * wpls;
+ wpls4 = 4 * wpls;
+ wpls5 = 5 * wpls;
+ wpls6 = 6 * 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 + 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));
+ }
+ }
+}
+
+static void
+ferode_3_77(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+
+ wpls2 = 2 * wpls;
+ wpls3 = 3 * wpls;
+ wpls4 = 4 * wpls;
+ wpls5 = 5 * wpls;
+ wpls6 = 6 * 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 - 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));
+ }
+ }
+}
+
+static void
+fdilate_3_78(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+l_int32 wpls9;
+
+ wpls2 = 2 * wpls;
+ wpls3 = 3 * wpls;
+ wpls4 = 4 * wpls;
+ wpls5 = 5 * wpls;
+ wpls6 = 6 * wpls;
+ wpls7 = 7 * wpls;
+ wpls8 = 8 * 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 + 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));
+ }
+ }
+}
+
+static void
+ferode_3_78(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+l_int32 wpls9;
+
+ wpls2 = 2 * wpls;
+ wpls3 = 3 * wpls;
+ wpls4 = 4 * wpls;
+ wpls5 = 5 * wpls;
+ wpls6 = 6 * wpls;
+ wpls7 = 7 * wpls;
+ wpls8 = 8 * 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 - 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));
+ }
+ }
+}
+
+static void
+fdilate_3_79(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+l_int32 wpls9;
+
+ wpls2 = 2 * wpls;
+ wpls3 = 3 * wpls;
+ wpls4 = 4 * wpls;
+ wpls5 = 5 * wpls;
+ wpls6 = 6 * wpls;
+ wpls7 = 7 * wpls;
+ wpls8 = 8 * 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 + 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_3_79(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+l_int32 wpls9;
+
+ wpls2 = 2 * wpls;
+ wpls3 = 3 * wpls;
+ wpls4 = 4 * wpls;
+ wpls5 = 5 * wpls;
+ wpls6 = 6 * wpls;
+ wpls7 = 7 * wpls;
+ wpls8 = 8 * 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 - 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_3_80(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_80(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_81(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_81(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_82(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+l_int32 wpls9, wpls10, wpls11;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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_3_82(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+l_int32 wpls9, wpls10, wpls11;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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_3_83(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+l_int32 wpls9, wpls10, wpls11;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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));
+ }
+ }
+}
+
+static void
+ferode_3_83(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+l_int32 wpls9, wpls10, wpls11;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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));
+ }
+ }
+}
+
+static void
+fdilate_3_84(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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));
+ }
+ }
+}
+
+static void
+ferode_3_84(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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));
+ }
+ }
+}
+
+static void
+fdilate_3_85(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_85(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_86(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+l_int32 wpls9, wpls10, wpls11, wpls12;
+l_int32 wpls13;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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_3_86(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+l_int32 wpls9, wpls10, wpls11, wpls12;
+l_int32 wpls13;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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_3_87(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+l_int32 wpls9, wpls10, wpls11, wpls12;
+l_int32 wpls13;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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));
+ }
+ }
+}
+
+static void
+ferode_3_87(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+l_uint32 *sptr, *dptr;
+l_int32 wpls2, wpls3, wpls4;
+l_int32 wpls5, wpls6, wpls7, wpls8;
+l_int32 wpls9, wpls10, wpls11, wpls12;
+l_int32 wpls13;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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));
+ }
+ }
+}
+
+static void
+fdilate_3_88(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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));
+ }
+ }
+}
+
+static void
+ferode_3_88(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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));
+ }
+ }
+}
+
+static void
+fdilate_3_89(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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_3_89(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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_3_90(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_90(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_91(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_91(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_92(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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_3_92(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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_3_93(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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));
+ }
+ }
+}
+
+static void
+ferode_3_93(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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));
+ }
+ }
+}
+
+static void
+fdilate_3_94(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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));
+ }
+ }
+}
+
+static void
+ferode_3_94(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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));
+ }
+ }
+}
+
+static void
+fdilate_3_95(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_95(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_96(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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_3_96(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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_3_97(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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));
+ }
+ }
+}
+
+static void
+ferode_3_97(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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));
+ }
+ }
+}
+
+static void
+fdilate_3_98(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(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));
+ }
+ }
+}
+
+static void
+ferode_3_98(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(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));
+ }
+ }
+}
+
+static void
+fdilate_3_99(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(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_3_99(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(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_3_100(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_100(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_101(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_101(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_102(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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_3_102(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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_3_103(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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));
+ }
+ }
+}
+
+static void
+ferode_3_103(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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));
+ }
+ }
+}
+
+static void
+fdilate_3_104(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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));
+ }
+ }
+}
+
+static void
+ferode_3_104(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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));
+ }
+ }
+}
+
+static void
+fdilate_3_105(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_105(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_106(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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_3_106(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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_3_107(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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));
+ }
+ }
+}
+
+static void
+ferode_3_107(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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));
+ }
+ }
+}
+
+static void
+fdilate_3_108(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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));
+ }
+ }
+}
+
+static void
+ferode_3_108(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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));
+ }
+ }
+}
+
+static void
+fdilate_3_109(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + 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_3_109(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - 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_3_110(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_110(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_111(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_111(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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_3_112(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr + wpls26)) |
+ (*(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_3_112(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr - wpls26)) &
+ (*(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_3_113(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr + wpls26)) |
+ (*(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)) |
+ (*(sptr - wpls26));
+ }
+ }
+}
+
+static void
+ferode_3_113(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr - wpls26)) &
+ (*(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)) &
+ (*(sptr + wpls26));
+ }
+ }
+}
+
+static void
+fdilate_3_114(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + wpls26)) |
+ (*(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)) |
+ (*(sptr - wpls26));
+ }
+ }
+}
+
+static void
+ferode_3_114(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - wpls26)) &
+ (*(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)) &
+ (*(sptr + wpls26));
+ }
+ }
+}
+
+static void
+fdilate_3_115(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 + wpls26)) |
+ (*(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)) |
+ (*(sptr - wpls26)) |
+ (*(sptr - wpls27));
+ }
+ }
+}
+
+static void
+ferode_3_115(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ 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 - wpls26)) &
+ (*(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)) &
+ (*(sptr + wpls26)) &
+ (*(sptr + wpls27));
+ }
+ }
+}
+
+static void
+fdilate_3_116(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr + wpls28)) |
+ (*(sptr + wpls27)) |
+ (*(sptr + wpls26)) |
+ (*(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)) |
+ (*(sptr - wpls26)) |
+ (*(sptr - wpls27));
+ }
+ }
+}
+
+static void
+ferode_3_116(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr - wpls28)) &
+ (*(sptr - wpls27)) &
+ (*(sptr - wpls26)) &
+ (*(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)) &
+ (*(sptr + wpls26)) &
+ (*(sptr + wpls27));
+ }
+ }
+}
+
+static void
+fdilate_3_117(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr + wpls28)) |
+ (*(sptr + wpls27)) |
+ (*(sptr + wpls26)) |
+ (*(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)) |
+ (*(sptr - wpls26)) |
+ (*(sptr - wpls27)) |
+ (*(sptr - wpls28));
+ }
+ }
+}
+
+static void
+ferode_3_117(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr - wpls28)) &
+ (*(sptr - wpls27)) &
+ (*(sptr - wpls26)) &
+ (*(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)) &
+ (*(sptr + wpls26)) &
+ (*(sptr + wpls27)) &
+ (*(sptr + wpls28));
+ }
+ }
+}
+
+static void
+fdilate_3_118(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+l_int32 wpls29;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr + wpls29)) |
+ (*(sptr + wpls28)) |
+ (*(sptr + wpls27)) |
+ (*(sptr + wpls26)) |
+ (*(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)) |
+ (*(sptr - wpls26)) |
+ (*(sptr - wpls27)) |
+ (*(sptr - wpls28));
+ }
+ }
+}
+
+static void
+ferode_3_118(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+l_int32 wpls29;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr - wpls29)) &
+ (*(sptr - wpls28)) &
+ (*(sptr - wpls27)) &
+ (*(sptr - wpls26)) &
+ (*(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)) &
+ (*(sptr + wpls26)) &
+ (*(sptr + wpls27)) &
+ (*(sptr + wpls28));
+ }
+ }
+}
+
+static void
+fdilate_3_119(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+l_int32 wpls29;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr + wpls29)) |
+ (*(sptr + wpls28)) |
+ (*(sptr + wpls27)) |
+ (*(sptr + wpls26)) |
+ (*(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)) |
+ (*(sptr - wpls26)) |
+ (*(sptr - wpls27)) |
+ (*(sptr - wpls28)) |
+ (*(sptr - wpls29));
+ }
+ }
+}
+
+static void
+ferode_3_119(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+l_int32 wpls29;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr - wpls29)) &
+ (*(sptr - wpls28)) &
+ (*(sptr - wpls27)) &
+ (*(sptr - wpls26)) &
+ (*(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)) &
+ (*(sptr + wpls26)) &
+ (*(sptr + wpls27)) &
+ (*(sptr + wpls28)) &
+ (*(sptr + wpls29));
+ }
+ }
+}
+
+static void
+fdilate_3_120(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+l_int32 wpls29, wpls30;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr + wpls30)) |
+ (*(sptr + wpls29)) |
+ (*(sptr + wpls28)) |
+ (*(sptr + wpls27)) |
+ (*(sptr + wpls26)) |
+ (*(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)) |
+ (*(sptr - wpls26)) |
+ (*(sptr - wpls27)) |
+ (*(sptr - wpls28)) |
+ (*(sptr - wpls29));
+ }
+ }
+}
+
+static void
+ferode_3_120(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+l_int32 wpls29, wpls30;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr - wpls30)) &
+ (*(sptr - wpls29)) &
+ (*(sptr - wpls28)) &
+ (*(sptr - wpls27)) &
+ (*(sptr - wpls26)) &
+ (*(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)) &
+ (*(sptr + wpls26)) &
+ (*(sptr + wpls27)) &
+ (*(sptr + wpls28)) &
+ (*(sptr + wpls29));
+ }
+ }
+}
+
+static void
+fdilate_3_121(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+l_int32 wpls29, wpls30;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr + wpls30)) |
+ (*(sptr + wpls29)) |
+ (*(sptr + wpls28)) |
+ (*(sptr + wpls27)) |
+ (*(sptr + wpls26)) |
+ (*(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)) |
+ (*(sptr - wpls26)) |
+ (*(sptr - wpls27)) |
+ (*(sptr - wpls28)) |
+ (*(sptr - wpls29)) |
+ (*(sptr - wpls30));
+ }
+ }
+}
+
+static void
+ferode_3_121(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+l_int32 wpls29, wpls30;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr - wpls30)) &
+ (*(sptr - wpls29)) &
+ (*(sptr - wpls28)) &
+ (*(sptr - wpls27)) &
+ (*(sptr - wpls26)) &
+ (*(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)) &
+ (*(sptr + wpls26)) &
+ (*(sptr + wpls27)) &
+ (*(sptr + wpls28)) &
+ (*(sptr + wpls29)) &
+ (*(sptr + wpls30));
+ }
+ }
+}
+
+static void
+fdilate_3_122(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+l_int32 wpls29, wpls30, wpls31;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr + wpls31)) |
+ (*(sptr + wpls30)) |
+ (*(sptr + wpls29)) |
+ (*(sptr + wpls28)) |
+ (*(sptr + wpls27)) |
+ (*(sptr + wpls26)) |
+ (*(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)) |
+ (*(sptr - wpls26)) |
+ (*(sptr - wpls27)) |
+ (*(sptr - wpls28)) |
+ (*(sptr - wpls29)) |
+ (*(sptr - wpls30));
+ }
+ }
+}
+
+static void
+ferode_3_122(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+l_int32 wpls29, wpls30, wpls31;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr - wpls31)) &
+ (*(sptr - wpls30)) &
+ (*(sptr - wpls29)) &
+ (*(sptr - wpls28)) &
+ (*(sptr - wpls27)) &
+ (*(sptr - wpls26)) &
+ (*(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)) &
+ (*(sptr + wpls26)) &
+ (*(sptr + wpls27)) &
+ (*(sptr + wpls28)) &
+ (*(sptr + wpls29)) &
+ (*(sptr + wpls30));
+ }
+ }
+}
+
+static void
+fdilate_3_123(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+l_int32 wpls29, wpls30, wpls31;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr + wpls31)) |
+ (*(sptr + wpls30)) |
+ (*(sptr + wpls29)) |
+ (*(sptr + wpls28)) |
+ (*(sptr + wpls27)) |
+ (*(sptr + wpls26)) |
+ (*(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)) |
+ (*(sptr - wpls26)) |
+ (*(sptr - wpls27)) |
+ (*(sptr - wpls28)) |
+ (*(sptr - wpls29)) |
+ (*(sptr - wpls30)) |
+ (*(sptr - wpls31));
+ }
+ }
+}
+
+static void
+ferode_3_123(l_uint32 *datad,
+ l_int32 w,
+ l_int32 h,
+ l_int32 wpld,
+ l_uint32 *datas,
+ l_int32 wpls)
+{
+l_int32 i;
+l_int32 j, pwpls;
+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, wpls26, wpls27, wpls28;
+l_int32 wpls29, wpls30, wpls31;
+
+ 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;
+ pwpls = (l_uint32)(w + 31) / 32; /* proper wpl of src */
+
+ for (i = 0; i < h; i++) {
+ sptr = datas + i * wpls;
+ dptr = datad + i * wpld;
+ for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+ *dptr = (*(sptr - wpls31)) &
+ (*(sptr - wpls30)) &
+ (*(sptr - wpls29)) &
+ (*(sptr - wpls28)) &
+ (*(sptr - wpls27)) &
+ (*(sptr - wpls26)) &
+ (*(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)) &
+ (*(sptr + wpls26)) &
+ (*(sptr + wpls27)) &
+ (*(sptr + wpls28)) &
+ (*(sptr + wpls29)) &
+ (*(sptr + wpls30)) &
+ (*(sptr + wpls31));
+ }
+ }
+}
+
diff --git a/leptonica/prog/dwamorph1_reg.c b/leptonica/prog/dwamorph1_reg.c
new file mode 100644
index 00000000..bc08f5ef
--- /dev/null
+++ b/leptonica/prog/dwamorph1_reg.c
@@ -0,0 +1,244 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * dwamorph1_reg.c
+ *
+ * Fairly thorough regression test for autogen'd dwa.
+ *
+ * The dwa code always implements safe closing. With asymmetric
+ * b.c., the rasterop function must be pixCloseSafe().
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* defined in morph.c */
+LEPT_DLL extern l_int32 MORPH_BC;
+
+ /* Complete set of linear brick dwa operations */
+PIX *pixMorphDwa_3(PIX *pixd, PIX *pixs, l_int32 operation, char *selname);
+PIX *pixFMorphopGen_3(PIX *pixd, PIX *pixs, l_int32 operation, char *selname);
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, nsels, same, xorcount;
+char *selname;
+PIX *pixs, *pixt1, *pixt2, *pixt3;
+SEL *sel;
+SELA *sela;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ if ((pixs = pixRead("feyn-fract.tif")) == NULL) {
+ rp->success = FALSE;
+ return regTestCleanup(rp);
+ }
+ sela = selaAddDwaLinear(NULL);
+ nsels = selaGetCount(sela);
+
+ for (i = 0; i < nsels; i++)
+ {
+ sel = selaGetSel(sela, i);
+ selname = selGetName(sel);
+
+ /* --------- dilation ----------*/
+
+ pixt1 = pixDilate(NULL, pixs, sel);
+ pixt2 = pixMorphDwa_3(NULL, pixs, L_MORPH_DILATE, selname);
+ pixEqual(pixt1, pixt2, &same);
+
+ if (same == 1) {
+ if (rp->display)
+ lept_stderr("dilations are identical for sel %d (%s)\n",
+ i, selname);
+ } else {
+ rp->success = FALSE;
+ fprintf(rp->fp, "dilations differ for sel %d (%s)\n", i, selname);
+ pixt3 = pixXor(NULL, pixt1, pixt2);
+ pixCountPixels(pixt3, &xorcount, NULL);
+ fprintf(rp->fp, "Number of pixels in XOR: %d\n", xorcount);
+ pixDestroy(&pixt3);
+ }
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ /* --------- erosion with asymmetric b.c ----------*/
+
+ resetMorphBoundaryCondition(ASYMMETRIC_MORPH_BC);
+ if (rp->display) lept_stderr("MORPH_BC = %d ... ", MORPH_BC);
+
+ pixt1 = pixErode(NULL, pixs, sel);
+ pixt2 = pixMorphDwa_3(NULL, pixs, L_MORPH_ERODE, selname);
+ pixEqual(pixt1, pixt2, &same);
+
+ if (same == 1) {
+ if (rp->display)
+ lept_stderr("erosions are identical for sel %d (%s)\n",
+ i, selname);
+ } else {
+ rp->success = FALSE;
+ fprintf(rp->fp, "erosions differ for sel %d (%s)\n", i, selname);
+ pixt3 = pixXor(NULL, pixt1, pixt2);
+ pixCountPixels(pixt3, &xorcount, NULL);
+ fprintf(rp->fp, "Number of pixels in XOR: %d\n", xorcount);
+ pixDestroy(&pixt3);
+ }
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ /* --------- erosion with symmetric b.c ----------*/
+
+ resetMorphBoundaryCondition(SYMMETRIC_MORPH_BC);
+ if (rp->display) lept_stderr("MORPH_BC = %d ... ", MORPH_BC);
+
+ pixt1 = pixErode(NULL, pixs, sel);
+ pixt2 = pixMorphDwa_3(NULL, pixs, L_MORPH_ERODE, selname);
+ pixEqual(pixt1, pixt2, &same);
+
+ if (same == 1) {
+ if (rp->display)
+ lept_stderr("erosions are identical for sel %d (%s)\n",
+ i, selname);
+ } else {
+ rp->success = FALSE;
+ fprintf(rp->fp, "erosions differ for sel %d (%s)\n", i, selname);
+ pixt3 = pixXor(NULL, pixt1, pixt2);
+ pixCountPixels(pixt3, &xorcount, NULL);
+ fprintf(rp->fp, "Number of pixels in XOR: %d\n", xorcount);
+ pixDestroy(&pixt3);
+ }
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ /* --------- opening with asymmetric b.c ----------*/
+
+ resetMorphBoundaryCondition(ASYMMETRIC_MORPH_BC);
+ if (rp->display) lept_stderr("MORPH_BC = %d ... ", MORPH_BC);
+
+ pixt1 = pixOpen(NULL, pixs, sel);
+ pixt2 = pixMorphDwa_3(NULL, pixs, L_MORPH_OPEN, selname);
+ pixEqual(pixt1, pixt2, &same);
+
+ if (same == 1) {
+ if (rp->display)
+ lept_stderr("openings are identical for sel %d (%s)\n",
+ i, selname);
+ } else {
+ rp->success = FALSE;
+ fprintf(rp->fp, "openings differ for sel %d (%s)\n", i, selname);
+ pixt3 = pixXor(NULL, pixt1, pixt2);
+ pixCountPixels(pixt3, &xorcount, NULL);
+ fprintf(rp->fp, "Number of pixels in XOR: %d\n", xorcount);
+ pixDestroy(&pixt3);
+ }
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ /* --------- opening with symmetric b.c ----------*/
+
+ resetMorphBoundaryCondition(SYMMETRIC_MORPH_BC);
+ if (rp->display) lept_stderr("MORPH_BC = %d ... ", MORPH_BC);
+
+ pixt1 = pixOpen(NULL, pixs, sel);
+ pixt2 = pixMorphDwa_3(NULL, pixs, L_MORPH_OPEN, selname);
+ pixEqual(pixt1, pixt2, &same);
+
+ if (same == 1) {
+ if (rp->display)
+ lept_stderr("openings are identical for sel %d (%s)\n",
+ i, selname);
+ } else {
+ rp->success = FALSE;
+ fprintf(rp->fp, "openings differ for sel %d (%s)\n", i, selname);
+ pixt3 = pixXor(NULL, pixt1, pixt2);
+ pixCountPixels(pixt3, &xorcount, NULL);
+ fprintf(rp->fp, "Number of pixels in XOR: %d\n", xorcount);
+ pixDestroy(&pixt3);
+ }
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ /* --------- safe closing with asymmetric b.c ----------*/
+
+ resetMorphBoundaryCondition(ASYMMETRIC_MORPH_BC);
+ if (rp->display) lept_stderr("MORPH_BC = %d ... ", MORPH_BC);
+
+ pixt1 = pixCloseSafe(NULL, pixs, sel); /* must use safe version */
+ pixt2 = pixMorphDwa_3(NULL, pixs, L_MORPH_CLOSE, selname);
+ pixEqual(pixt1, pixt2, &same);
+
+ if (same == 1) {
+ if (rp->display)
+ lept_stderr("closings are identical for sel %d (%s)\n",
+ i, selname);
+ } else {
+ rp->success = FALSE;
+ fprintf(rp->fp, "closings differ for sel %d (%s)\n", i, selname);
+ pixt3 = pixXor(NULL, pixt1, pixt2);
+ pixCountPixels(pixt3, &xorcount, NULL);
+ fprintf(rp->fp, "Number of pixels in XOR: %d\n", xorcount);
+ pixDestroy(&pixt3);
+ }
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ /* --------- safe closing with symmetric b.c ----------*/
+
+ resetMorphBoundaryCondition(SYMMETRIC_MORPH_BC);
+ if (rp->display) lept_stderr("MORPH_BC = %d ... ", MORPH_BC);
+
+ pixt1 = pixClose(NULL, pixs, sel); /* safe version not required */
+ pixt2 = pixMorphDwa_3(NULL, pixs, L_MORPH_CLOSE, selname);
+ pixEqual(pixt1, pixt2, &same);
+
+ if (same == 1) {
+ if (rp->display)
+ lept_stderr("closings are identical for sel %d (%s)\n",
+ i, selname);
+ } else {
+ rp->success = FALSE;
+ fprintf(rp->fp, "closings differ for sel %d (%s)\n", i, selname);
+ pixt3 = pixXor(NULL, pixt1, pixt2);
+ pixCountPixels(pixt3, &xorcount, NULL);
+ fprintf(rp->fp, "Number of pixels in XOR: %d\n", xorcount);
+ pixDestroy(&pixt3);
+ }
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ }
+
+ selaDestroy(&sela);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/dwamorph2_reg.c b/leptonica/prog/dwamorph2_reg.c
new file mode 100644
index 00000000..8e2a2aea
--- /dev/null
+++ b/leptonica/prog/dwamorph2_reg.c
@@ -0,0 +1,321 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * dwamorph2_reg.c
+ *
+ * Compare the timings of various binary morphological implementations.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define HALFWIDTH 3
+
+ /* Complete set of linear brick dwa operations */
+PIX *pixMorphDwa_3(PIX *pixd, PIX *pixs, l_int32 operation, char *selname);
+
+static const l_int32 NTIMES = 20;
+
+int main(int argc,
+ char **argv)
+{
+char *selname;
+l_int32 i, j, nsels, sx, sy;
+l_float32 fact, time;
+GPLOT *gplot;
+NUMA *na1, *na2, *na3, *na4, *nac1, *nac2, *nac3, *nac4, *nax;
+PIX *pixs, *pixt;
+PIXA *pixa;
+SEL *sel;
+SELA *selalinear;
+static char mainName[] = "dwamorph2_reg";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: dwamorph2_reg", mainName, 1);
+ setLeptDebugOK(1);
+
+ pixs = pixRead("feyn-fract.tif");
+ pixt = pixCreateTemplate(pixs);
+ selalinear = selaAddDwaLinear(NULL);
+ nsels = selaGetCount(selalinear);
+
+ fact = 1000. / (l_float32)NTIMES; /* converts to time in msec */
+ na1 = numaCreate(64);
+ na2 = numaCreate(64);
+ na3 = numaCreate(64);
+ na4 = numaCreate(64);
+
+ lept_mkdir("lept/morph");
+
+ /* --------- dilation ----------*/
+
+ for (i = 0; i < nsels / 2; i++)
+ {
+ sel = selaGetSel(selalinear, i);
+ selGetParameters(sel, &sy, &sx, NULL, NULL);
+ selname = selGetName(sel);
+ lept_stderr(" %d .", i);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixDilate(pixt, pixs, sel);
+ time = fact * stopTimer();
+ numaAddNumber(na1, time);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixDilateCompBrick(pixt, pixs, sx, sy);
+ time = fact * stopTimer();
+ numaAddNumber(na2, time);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixMorphDwa_3(pixt, pixs, L_MORPH_DILATE, selname);
+ time = fact * stopTimer();
+ numaAddNumber(na3, time);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixDilateCompBrickDwa(pixt, pixs, sx, sy);
+ time = fact * stopTimer();
+ numaAddNumber(na4, time);
+ }
+
+ nax = numaMakeSequence(2, 1, nsels / 2);
+ nac1 = numaWindowedMean(na1, HALFWIDTH);
+ nac2 = numaWindowedMean(na2, HALFWIDTH);
+ nac3 = numaWindowedMean(na3, HALFWIDTH);
+ nac4 = numaWindowedMean(na4, HALFWIDTH);
+ gplot = gplotCreate("/tmp/lept/morph/dilate", GPLOT_PNG,
+ "Dilation time vs sel size", "size", "time (ms)");
+ gplotAddPlot(gplot, nax, nac1, GPLOT_LINES, "linear rasterop");
+ gplotAddPlot(gplot, nax, nac2, GPLOT_LINES, "composite rasterop");
+ gplotAddPlot(gplot, nax, nac3, GPLOT_LINES, "linear dwa");
+ gplotAddPlot(gplot, nax, nac4, GPLOT_LINES, "composite dwa");
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ numaDestroy(&nac1);
+ numaDestroy(&nac2);
+ numaDestroy(&nac3);
+ numaDestroy(&nac4);
+
+ /* --------- erosion ----------*/
+
+ numaEmpty(na1);
+ numaEmpty(na2);
+ numaEmpty(na3);
+ numaEmpty(na4);
+ for (i = 0; i < nsels / 2; i++)
+ {
+ sel = selaGetSel(selalinear, i);
+ selGetParameters(sel, &sy, &sx, NULL, NULL);
+ selname = selGetName(sel);
+ lept_stderr(" %d .", i);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixErode(pixt, pixs, sel);
+ time = fact * stopTimer();
+ numaAddNumber(na1, time);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixErodeCompBrick(pixt, pixs, sx, sy);
+ time = fact * stopTimer();
+ numaAddNumber(na2, time);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixMorphDwa_3(pixt, pixs, L_MORPH_ERODE, selname);
+ time = fact * stopTimer();
+ numaAddNumber(na3, time);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixErodeCompBrickDwa(pixt, pixs, sx, sy);
+ time = fact * stopTimer();
+ numaAddNumber(na4, time);
+ }
+
+ nac1 = numaWindowedMean(na1, HALFWIDTH);
+ nac2 = numaWindowedMean(na2, HALFWIDTH);
+ nac3 = numaWindowedMean(na3, HALFWIDTH);
+ nac4 = numaWindowedMean(na4, HALFWIDTH);
+ gplot = gplotCreate("/tmp/lept/morph/erode", GPLOT_PNG,
+ "Erosion time vs sel size", "size", "time (ms)");
+ gplotAddPlot(gplot, nax, nac1, GPLOT_LINES, "linear rasterop");
+ gplotAddPlot(gplot, nax, nac2, GPLOT_LINES, "composite rasterop");
+ gplotAddPlot(gplot, nax, nac3, GPLOT_LINES, "linear dwa");
+ gplotAddPlot(gplot, nax, nac4, GPLOT_LINES, "composite dwa");
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ numaDestroy(&nac1);
+ numaDestroy(&nac2);
+ numaDestroy(&nac3);
+ numaDestroy(&nac4);
+
+ /* --------- opening ----------*/
+
+ numaEmpty(na1);
+ numaEmpty(na2);
+ numaEmpty(na3);
+ numaEmpty(na4);
+ for (i = 0; i < nsels / 2; i++)
+ {
+ sel = selaGetSel(selalinear, i);
+ selGetParameters(sel, &sy, &sx, NULL, NULL);
+ selname = selGetName(sel);
+ lept_stderr(" %d .", i);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixOpen(pixt, pixs, sel);
+ time = fact * stopTimer();
+ numaAddNumber(na1, time);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixOpenCompBrick(pixt, pixs, sx, sy);
+ time = fact * stopTimer();
+ numaAddNumber(na2, time);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixMorphDwa_3(pixt, pixs, L_MORPH_OPEN, selname);
+ time = fact * stopTimer();
+ numaAddNumber(na3, time);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixOpenCompBrickDwa(pixt, pixs, sx, sy);
+ time = fact * stopTimer();
+ numaAddNumber(na4, time);
+ }
+
+ nac1 = numaWindowedMean(na1, HALFWIDTH);
+ nac2 = numaWindowedMean(na2, HALFWIDTH);
+ nac3 = numaWindowedMean(na3, HALFWIDTH);
+ nac4 = numaWindowedMean(na4, HALFWIDTH);
+ gplot = gplotCreate("/tmp/lept/morph/open", GPLOT_PNG,
+ "Opening time vs sel size", "size", "time (ms)");
+ gplotAddPlot(gplot, nax, nac1, GPLOT_LINES, "linear rasterop");
+ gplotAddPlot(gplot, nax, nac2, GPLOT_LINES, "composite rasterop");
+ gplotAddPlot(gplot, nax, nac3, GPLOT_LINES, "linear dwa");
+ gplotAddPlot(gplot, nax, nac4, GPLOT_LINES, "composite dwa");
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ numaDestroy(&nac1);
+ numaDestroy(&nac2);
+ numaDestroy(&nac3);
+ numaDestroy(&nac4);
+
+ /* --------- closing ----------*/
+
+ numaEmpty(na1);
+ numaEmpty(na2);
+ numaEmpty(na3);
+ numaEmpty(na4);
+ for (i = 0; i < nsels / 2; i++)
+ {
+ sel = selaGetSel(selalinear, i);
+ selGetParameters(sel, &sy, &sx, NULL, NULL);
+ selname = selGetName(sel);
+ lept_stderr(" %d .", i);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixClose(pixt, pixs, sel);
+ time = fact * stopTimer();
+ numaAddNumber(na1, time);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixCloseCompBrick(pixt, pixs, sx, sy);
+ time = fact * stopTimer();
+ numaAddNumber(na2, time);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixMorphDwa_3(pixt, pixs, L_MORPH_CLOSE, selname);
+ time = fact * stopTimer();
+ numaAddNumber(na3, time);
+
+ startTimer();
+ for (j = 0; j < NTIMES; j++)
+ pixCloseCompBrickDwa(pixt, pixs, sx, sy);
+ time = fact * stopTimer();
+ numaAddNumber(na4, time);
+ }
+
+ nac1 = numaWindowedMean(na1, HALFWIDTH);
+ nac2 = numaWindowedMean(na2, HALFWIDTH);
+ nac3 = numaWindowedMean(na3, HALFWIDTH);
+ nac4 = numaWindowedMean(na4, HALFWIDTH);
+ gplot = gplotCreate("/tmp/lept/morph/close", GPLOT_PNG,
+ "Closing time vs sel size", "size", "time (ms)");
+ gplotAddPlot(gplot, nax, nac1, GPLOT_LINES, "linear rasterop");
+ gplotAddPlot(gplot, nax, nac2, GPLOT_LINES, "composite rasterop");
+ gplotAddPlot(gplot, nax, nac3, GPLOT_LINES, "linear dwa");
+ gplotAddPlot(gplot, nax, nac4, GPLOT_LINES, "composite dwa");
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ numaDestroy(&nac1);
+ numaDestroy(&nac2);
+ numaDestroy(&nac3);
+ numaDestroy(&nac4);
+
+
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+ numaDestroy(&nax);
+ selaDestroy(&selalinear);
+ pixDestroy(&pixt);
+ pixDestroy(&pixs);
+
+ /* Display the results together */
+ pixa = pixaCreate(0);
+ pixs = pixRead("/tmp/lept/morph/dilate.png");
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixs = pixRead("/tmp/lept/morph/erode.png");
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixs = pixRead("/tmp/lept/morph/open.png");
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixs = pixRead("/tmp/lept/morph/close.png");
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixt = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 40, 3);
+ pixWrite("/tmp/lept/morph/timings.png", pixt, IFF_PNG);
+ pixDisplay(pixt, 100, 100);
+ pixDestroy(&pixt);
+ pixaDestroy(&pixa);
+ return 0;
+}
+
diff --git a/leptonica/prog/edge_reg.c b/leptonica/prog/edge_reg.c
new file mode 100644
index 00000000..ba0a7932
--- /dev/null
+++ b/leptonica/prog/edge_reg.c
@@ -0,0 +1,90 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * edgetest.c
+ *
+ * Regression test for sobel edge filter.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, w, h;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("test8.jpg");
+
+ /* Test speed: about 60 Mpix/sec/GHz */
+ startTimer();
+ for (i = 0; i < 100; i++) {
+ pix1 = pixSobelEdgeFilter(pixs, L_HORIZONTAL_EDGES);
+ pix2 = pixThresholdToBinary(pix1, 60);
+ pixInvert(pix2, pix2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ pixGetDimensions(pixs, &w, &h, NULL);
+ lept_stderr("Sobel edge MPix/sec: %7.3f\n", 0.0001 * w * h / stopTimer());
+
+ /* Horiz and vert sobel edges (1 bpp) */
+ pix1 = pixSobelEdgeFilter(pixs, L_HORIZONTAL_EDGES);
+ pix2 = pixThresholdToBinary(pix1, 60);
+ pixInvert(pix2, pix2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix2, 0, 50, "Horizontal edges", rp->display);
+ pix3 = pixSobelEdgeFilter(pixs, L_VERTICAL_EDGES);
+ pix4 = pixThresholdToBinary(pix3, 60);
+ pixInvert(pix4, pix4);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix4, 625, 50, "Vertical edges", rp->display);
+ pixOr(pix4, pix4, pix2);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix4, 1200, 50, "Horiz and vert edges", rp->display);
+ pixDestroy(&pix2);
+ pixDestroy(&pix4);
+
+ /* Horizontal and vertical sobel edges (8 bpp) */
+ pixMinOrMax(pix1, pix1, pix3, L_CHOOSE_MAX);
+ pixInvert(pix1, pix1);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 3 */
+ pixDisplayWithTitle(pix1, 0, 525, "8bpp Horiz and vert edges", rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/encoding_reg.c b/leptonica/prog/encoding_reg.c
new file mode 100644
index 00000000..412c05c9
--- /dev/null
+++ b/leptonica/prog/encoding_reg.c
@@ -0,0 +1,107 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * encoding_reg.c
+ *
+ * Regression test for encoding/decoding of binary data
+ *
+ * Ascii85 encoding/decoding Works properly with 0, 1, 2 or 3 extra
+ * bytes after the final full word.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+size_t nbytes1, nbytes2, nbytes3, nbytes4, nbytes5, nbytes6, fbytes;
+char *a85a, *a85c, *a85c2;
+l_uint8 *bina, *bina2, *bin85c, *bin85c2;
+PIX *pix1;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/encode");
+
+ /* Test ascii85 encoding */
+ bina = l_binaryRead("karen8.jpg", &fbytes);
+ a85a = encodeAscii85(bina, fbytes, &nbytes1);
+ bina2 = decodeAscii85(a85a, nbytes1, &nbytes2);
+ regTestCompareValues(rp, fbytes, nbytes2, 0.0); /* 0 */
+
+ if (rp->display) {
+ lept_stderr("file bytes = %zu, a85 bytes = %zu, bina2 bytes = %zu\n",
+ fbytes, nbytes1, nbytes2);
+ }
+ l_binaryWrite("/tmp/lept/encode/ascii85", "w", a85a, nbytes1);
+ l_binaryWrite("/tmp/lept/encode/bina2", "w", bina2, nbytes2);
+
+ /* Test the reconstructed image */
+ pix1 = pixReadMem(bina2, nbytes2);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 1 */
+ pixDisplayWithTitle(pix1, 100, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ /* Test with compression, starting with ascii data */
+ a85c = encodeAscii85WithComp((l_uint8 *)a85a, nbytes1, &nbytes3);
+ bin85c = decodeAscii85WithComp(a85c, nbytes3, &nbytes4);
+ regTestCompareStrings(rp, (l_uint8 *)a85a, nbytes1,
+ bin85c, nbytes4); /* 2 */
+
+ /* Test with compression, starting with binary data */
+ a85c2 = encodeAscii85WithComp(bin85c, nbytes4, &nbytes5);
+ bin85c2 = decodeAscii85WithComp(a85c2, nbytes5, &nbytes6);
+ regTestCompareStrings(rp, bin85c, nbytes4,
+ bin85c2, nbytes6); /* 3 */
+ lept_free(bina);
+ lept_free(bina2);
+ lept_free(a85a);
+ lept_free(a85c);
+ lept_free(bin85c);
+ lept_free(a85c2);
+ lept_free(bin85c2);
+
+ /* Test storing and retrieving compressed text from pix */
+ bina = l_binaryRead("weasel32.png", &nbytes1);
+ pix1 = pixRead("rabi.png");
+ pixSetTextCompNew(pix1, bina, nbytes1);
+ bina2 = pixGetTextCompNew(pix1, &nbytes2);
+ if (rp->display)
+ lept_stderr("nbytes1 = %zu, nbytes2 = %zu\n", nbytes1, nbytes2);
+ regTestCompareStrings(rp, bina, nbytes1, bina2, nbytes2); /* 4 */
+ pixDestroy(&pix1);
+ lept_free(bina);
+ lept_free(bina2);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/enhance_reg.c b/leptonica/prog/enhance_reg.c
new file mode 100644
index 00000000..addb15f6
--- /dev/null
+++ b/leptonica/prog/enhance_reg.c
@@ -0,0 +1,298 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * This tests the following global "enhancement" functions:
+ * * TRC transforms with variation of gamma and black point
+ * * HSV transforms with variation of hue, saturation and intensity
+ * * Contrast variation
+ * * Sharpening
+ * * Color mapping to lighten background with constant hue
+ * * Linear color transform without mixing (diagonal)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+
+int main(int argc,
+ char **argv)
+{
+char textstr[256];
+l_int32 i, k, w, h;
+l_uint32 srcval;
+l_float32 scalefact, sat, fract;
+L_BMF *bmf10;
+L_KERNEL *kel;
+NUMA *na1, *na2, *na3;
+PIX *pix, *pixs, *pixs1, *pixs2, *pixd;
+PIX *pix0, *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa1, *pixa2, *pixaf;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "enhance_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/enhance");
+ pix = pixRead("test24.jpg"); /* rgb */
+ w = pixGetWidth(pix);
+ scalefact = 150.0 / (l_float32)w; /* scale to w = 150 */
+ pixs = pixScale(pix, scalefact, scalefact);
+ w = pixGetWidth(pixs);
+ pixaf = pixaCreate(5);
+ pixDestroy(&pix);
+
+ /* TRC: vary gamma */
+ pixa1 = pixaCreate(20);
+ for (i = 0; i < 20; i++) {
+ pix0 = pixGammaTRC(NULL, pixs, 0.3 + 0.15 * i, 0, 255);
+ pixaAddPix(pixa1, pix0, L_INSERT);
+ }
+ pix1 = pixaDisplayTiledAndScaled(pixa1, 32, w, 5, 0, 10, 2);
+ pixaAddPix(pixaf, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix1, 0, 100, "TRC Gamma", rp->display);
+ pixaDestroy(&pixa1);
+
+ /* TRC: vary black point */
+ pixa1 = pixaCreate(20);
+ for (i = 0; i < 20; i++) {
+ pix0 = pixGammaTRC(NULL, pixs, 1.0, 5 * i, 255);
+ pixaAddPix(pixa1, pix0, L_INSERT);
+ }
+ pix1 = pixaDisplayTiledAndScaled(pixa1, 32, w, 5, 0, 10, 2);
+ pixaAddPix(pixaf, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix1, 300, 100, "TRC", rp->display);
+ pixaDestroy(&pixa1);
+
+ /* Vary hue */
+ pixa1 = pixaCreate(20);
+ for (i = 0; i < 20; i++) {
+ pix0 = pixModifyHue(NULL, pixs, 0.01 + 0.05 * i);
+ pixaAddPix(pixa1, pix0, L_INSERT);
+ }
+ pix1 = pixaDisplayTiledAndScaled(pixa1, 32, w, 5, 0, 10, 2);
+ pixaAddPix(pixaf, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix1, 600, 100, "Hue", rp->display);
+ pixaDestroy(&pixa1);
+
+ /* Vary saturation */
+ pixa1 = pixaCreate(20);
+ na1 = numaCreate(20);
+ for (i = 0; i < 20; i++) {
+ pix0 = pixModifySaturation(NULL, pixs, -0.9 + 0.1 * i);
+ pixMeasureSaturation(pix0, 1, &sat);
+ pixaAddPix(pixa1, pix0, L_INSERT);
+ numaAddNumber(na1, sat);
+ }
+ pix1 = pixaDisplayTiledAndScaled(pixa1, 32, w, 5, 0, 10, 2);
+ pixaAddPix(pixaf, pix1, L_INSERT);
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/regout/enhance.7",
+ "Average Saturation");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pix1, 900, 100, "Saturation", rp->display);
+ numaDestroy(&na1);
+ pixaDestroy(&pixa1);
+
+ /* Vary contrast */
+ pixa1 = pixaCreate(20);
+ for (i = 0; i < 20; i++) {
+ pix0 = pixContrastTRC(NULL, pixs, 0.1 * i);
+ pixaAddPix(pixa1, pix0, L_INSERT);
+ }
+ pix1 = pixaDisplayTiledAndScaled(pixa1, 32, w, 5, 0, 10, 2);
+ pixaAddPix(pixaf, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix1, 0, 400, "Contrast", rp->display);
+ pixaDestroy(&pixa1);
+
+ /* Vary sharpening */
+ pixa1 = pixaCreate(20);
+ for (i = 0; i < 20; i++) {
+ pix0 = pixUnsharpMasking(pixs, 3, 0.01 + 0.15 * i);
+ pixaAddPix(pixa1, pix0, L_INSERT);
+ }
+ pix1 = pixaDisplayTiledAndScaled(pixa1, 32, w, 5, 0, 10, 2);
+ pixaAddPix(pixaf, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix1, 300, 400, "Sharp", rp->display);
+ pixaDestroy(&pixa1);
+
+ /* Hue constant mapping to lighter background */
+ pixa2 = pixaCreate(2);
+ bmf10 = bmfCreate("fonts", 10);
+ pix0 = pixRead("candelabrum.011.jpg");
+ composeRGBPixel(230, 185, 144, &srcval); /* select typical bg pixel */
+ for (k = 1; k > -2; k -= 2) {
+ pixa1 = pixaCreate(11);
+ for (i = 0; i <= 10; i++) {
+ fract = k * 0.10 * i;
+ pix1 = pixMapWithInvariantHue(NULL, pix0, srcval, fract);
+ snprintf(textstr, 50, "Fract = %5.1f", fract);
+ pix2 = pixAddSingleTextblock(pix1, bmf10, textstr, 0xff000000,
+ L_ADD_BELOW, NULL);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ pix3 = pixaDisplayTiledInColumns(pixa1, 4, 1.0, 30, 2);
+ pixaAddPix(pixa2, pix3, L_INSERT);
+ pixaDestroy(&pixa1);
+ }
+ pixd = pixaDisplayTiledInColumns(pixa2, 2, 0.5, 30, 2);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 6 */
+ pixDisplayWithTitle(pixd, 600, 400, "Constant hue", rp->display);
+ pixaDestroy(&pixa2);
+ bmfDestroy(&bmf10);
+ pixDestroy(&pix0);
+ pixDestroy(&pixd);
+
+ /* Delayed testing of saturation plot */
+ regTestCheckFile(rp, "/tmp/lept/regout/enhance.7.png"); /* 7 */
+
+ /* Display results */
+ pixd = pixaDisplayTiledInColumns(pixaf, 1, 1.0, 20, 2);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 8 */
+ pixDisplayWithTitle(pixd, 100, 100, "All", rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixaf);
+
+ /* Test color shifts */
+ pixd = pixMosaicColorShiftRGB(pixs, -0.1, 0.0, 0.0, 0.0999, 1);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 9 */
+ pixDisplayWithTitle(pixd, 1000, 100, "Color shift", rp->display);
+ pixDestroy(&pixd);
+ pixDestroy(&pixs);
+
+ /* More trc testing */
+ pix = pixRead("test24.jpg"); /* rgb */
+ pixs = pixScale(pix, 0.3, 0.3);
+ pixDestroy(&pix);
+ pixa1 = pixaCreate(5);
+ pixaAddPix(pixa1, pixs, L_COPY);
+
+ na1 = numaGammaTRC(0.6, 40, 200);
+ na2 = numaGammaTRC(1.2, 40, 225);
+ na3 = numaGammaTRC(0.6, 40, 255);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pix1 = pixCopy(NULL, pixs);
+ pix2 = pixMakeSymmetricMask(w, h, 0.5, 0.5, L_USE_INNER);
+ pixaAddPix(pixa1, pix2, L_COPY);
+ pixTRCMapGeneral(pix1, pix2, na1, na2, na3);
+ pixaAddPix(pixa1, pix1, L_COPY);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10 */
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ na1 = numaGammaTRC(1.0, 0, 255);
+ na2 = numaGammaTRC(1.0, 0, 255);
+ na3 = numaGammaTRC(1.0, 0, 255);
+ pix1 = pixCopy(NULL, pixs);
+ pixTRCMapGeneral(pix1, NULL, na1, na2, na3);
+ regTestComparePix(rp, pixs, pix1); /* 11 */
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ pixDestroy(&pix1);
+
+ na1 = numaGammaTRC(1.7, 150, 255);
+ na2 = numaGammaTRC(0.7, 0, 150);
+ na3 = numaGammaTRC(1.2, 80, 200);
+ pixTRCMapGeneral(pixs, NULL, na1, na2, na3);
+ regTestWritePixAndCheck(rp, pixs, IFF_PNG); /* 12 */
+ pixaAddPix(pixa1, pixs, L_COPY);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ pixDestroy(&pixs);
+
+ na1 = numaGammaTRC(0.8, 0, 220);
+ na2 = numaGammaTRC(1.0, 40, 220);
+ gplotSimple2(na1, na2, GPLOT_PNG, "/tmp/lept/enhance/junkp", NULL);
+ pix1 = pixRead("/tmp/lept/enhance/junkp.png");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 13 */
+ pixaAddPix(pixa1, pix1, L_COPY);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ pixDestroy(&pix1);
+
+ pixd = pixaDisplayTiledInColumns(pixa1, 4, 1.0, 30, 2);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 14 */
+ pixDisplayWithTitle(pixd, 100, 800, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa1);
+
+ /* -----------------------------------------------*
+ * Test global color transforms *
+ * -----------------------------------------------*/
+ /* Make identical cmap and rgb images */
+ pix = pixRead("wet-day.jpg");
+ pixs1 = pixOctreeColorQuant(pix, 200, 0);
+ pixs2 = pixRemoveColormap(pixs1, REMOVE_CMAP_TO_FULL_COLOR);
+ regTestComparePix(rp, pixs1, pixs2); /* 15 */
+
+ /* Make a diagonal color transform matrix */
+ kel = kernelCreate(3, 3);
+ kernelSetElement(kel, 0, 0, 0.7);
+ kernelSetElement(kel, 1, 1, 0.4);
+ kernelSetElement(kel, 2, 2, 1.3);
+
+ /* Apply to both cmap and rgb images. */
+ pix1 = pixMultMatrixColor(pixs1, kel);
+ pix2 = pixMultMatrixColor(pixs2, kel);
+ regTestComparePix(rp, pix1, pix2); /* 16 */
+ kernelDestroy(&kel);
+
+ /* Apply the same transform in the simpler interface */
+ pix3 = pixMultConstantColor(pixs1, 0.7, 0.4, 1.3);
+ pix4 = pixMultConstantColor(pixs2, 0.7, 0.4, 1.3);
+ regTestComparePix(rp, pix3, pix4); /* 17 */
+ regTestComparePix(rp, pix1, pix3); /* 18 */
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 19 */
+
+ pixDestroy(&pix);
+ pixDestroy(&pixs1);
+ pixDestroy(&pixs2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/equal_reg.c b/leptonica/prog/equal_reg.c
new file mode 100644
index 00000000..2433b9ee
--- /dev/null
+++ b/leptonica/prog/equal_reg.c
@@ -0,0 +1,139 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * equal_reg.c
+ *
+ * Tests the pixEqual() function in many situations.
+ *
+ * This also tests the quantization of grayscale and color
+ * images (to generate a colormapped image), and removal of
+ * the colormap to either RGB or grayscale.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* use this set */
+#define FEYN1 "feyn.tif" /* 1 bpp */
+#define DREYFUS2 "dreyfus2.png" /* 2 bpp cmapped */
+#define DREYFUS4 "dreyfus4.png" /* 4 bpp cmapped */
+#define DREYFUS8 "dreyfus8.png" /* 8 bpp cmapped */
+#define KAREN8 "karen8.jpg" /* 8 bpp, not cmapped */
+#define MARGE32 "marge.jpg" /* rgb */
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pix1, *pix2, *pix3, *pix4;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/equal");
+
+ pixs = pixRead(FEYN1);
+ pixWrite("/tmp/lept/equal/junkfeyn.png", pixs, IFF_PNG);
+ pix1 = pixRead("/tmp/lept/equal/junkfeyn.png");
+ regTestComparePix(rp, pixs, pix1); /* 0 */
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+
+ pixs = pixRead(DREYFUS2);
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+ pix2 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+ pix3 = pixOctreeQuantNumColors(pix2, 64, 1);
+ pix4 = pixConvertRGBToColormap(pix2, 1);
+ regTestComparePix(rp, pixs, pix1); /* 1 */
+ regTestComparePix(rp, pixs, pix2); /* 2 */
+ regTestComparePix(rp, pixs, pix3); /* 3 */
+ regTestComparePix(rp, pixs, pix4); /* 4 */
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ pixs = pixRead(DREYFUS4);
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+ pix2 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+ pix3 = pixOctreeQuantNumColors(pix2, 256, 1);
+ pix4 = pixConvertRGBToColormap(pix2, 1);
+ regTestComparePix(rp, pixs, pix1); /* 5 */
+ regTestComparePix(rp, pixs, pix2); /* 6 */
+ regTestComparePix(rp, pixs, pix3); /* 7 */
+ regTestComparePix(rp, pixs, pix4); /* 8 */
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ pixs = pixRead(DREYFUS8);
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+ pix2 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+ pix3 = pixConvertRGBToColormap(pix2, 1);
+ regTestComparePix(rp, pixs, pix1); /* 9 */
+ regTestComparePix(rp, pixs, pix2); /* 10 */
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ pixs = pixRead(KAREN8);
+ pix1 = pixThresholdTo4bpp(pixs, 16, 1);
+ pix2 = pixRemoveColormap(pix1, REMOVE_CMAP_BASED_ON_SRC);
+ pix3 = pixRemoveColormap(pix1, REMOVE_CMAP_TO_FULL_COLOR);
+ pix4 = pixConvertRGBToColormap(pix3, 1);
+ regTestComparePix(rp, pix1, pix2); /* 11 */
+ regTestComparePix(rp, pix1, pix3); /* 12 */
+ regTestComparePix(rp, pix1, pix4); /* 13 */
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ pixs = pixRead(MARGE32);
+ pix1 = pixOctreeQuantNumColors(pixs, 32, 0);
+ pix2 = pixRemoveColormap(pix1, REMOVE_CMAP_TO_FULL_COLOR);
+ pix3 = pixConvertRGBToColormap(pix2, 1);
+ pix4 = pixOctreeQuantNumColors(pix2, 64, 0);
+ regTestComparePix(rp, pix1, pix2); /* 14 */
+ regTestComparePix(rp, pix1, pix3); /* 15 */
+ regTestComparePix(rp, pix1, pix4); /* 16 */
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/expand_reg.c b/leptonica/prog/expand_reg.c
new file mode 100644
index 00000000..3e99af17
--- /dev/null
+++ b/leptonica/prog/expand_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * expand_reg.c
+ *
+ * Regression test for replicative and power-of-2 expansion (and
+ * corresponding reductions)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define BINARY_IMAGE "test1.png"
+#define TWO_BPP_IMAGE_NO_CMAP "weasel2.4g.png"
+#define TWO_BPP_IMAGE_CMAP "weasel2.4c.png"
+#define FOUR_BPP_IMAGE_NO_CMAP "weasel4.16g.png"
+#define FOUR_BPP_IMAGE_CMAP "weasel4.16c.png"
+#define EIGHT_BPP_IMAGE_NO_CMAP "weasel8.149g.png"
+#define EIGHT_BPP_IMAGE_CMAP "weasel8.240c.png"
+#define RGB_IMAGE "marge.jpg"
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, w, h, format;
+char filename[][64] = {BINARY_IMAGE,
+ TWO_BPP_IMAGE_NO_CMAP, TWO_BPP_IMAGE_CMAP,
+ FOUR_BPP_IMAGE_NO_CMAP, FOUR_BPP_IMAGE_CMAP,
+ EIGHT_BPP_IMAGE_NO_CMAP, EIGHT_BPP_IMAGE_CMAP,
+ RGB_IMAGE};
+BOX *box;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixa = pixaCreate(0);
+ for (i = 0; i < 8; i++) {
+ pixs = pixRead(filename[i]);
+ pix1 = pixExpandReplicate(pixs, 2);
+ format = (i == 7) ? IFF_JFIF_JPEG : IFF_PNG;
+ regTestWritePixAndCheck(rp, pix1, format); /* 0 - 7 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pixs);
+ }
+ for (i = 0; i < 8; i++) {
+ pixs = pixRead(filename[i]);
+ pix1 = pixExpandReplicate(pixs, 3);
+ format = (i == 7) ? IFF_JFIF_JPEG : IFF_PNG;
+ regTestWritePixAndCheck(rp, pix1, format); /* 8 - 15 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pixs);
+ }
+
+ pixs = pixRead("test1.png");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ for (i = 1; i <= 15; i++) {
+ box = boxCreate(13 * i, 13 * i, w - 13 * i, h - 13 * i);
+ pix1 = pixClipRectangle(pixs, box, NULL);
+ pix2 = pixExpandReplicate(pix1, 3);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 16 - 30 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ boxDestroy(&box);
+ pixDestroy(&pix1);
+ }
+ pixDestroy(&pixs);
+
+ /* --------- Power of 2 expansion and reduction -------- */
+ pixs = pixRead("speckle.png");
+
+ /* Test 2x expansion of 1 bpp */
+ pix1 = pixExpandBinaryPower2(pixs, 2);
+ pix2 = pixReduceRankBinary2(pix1, 4, NULL);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 31 */
+ regTestComparePix(rp, pixs, pix2); /* 32 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Test 2x expansion of 2 bpp */
+ pix1 = pixConvert1To2(NULL, pixs, 3, 0);
+ pix2 = pixExpandReplicate(pix1, 2);
+ pix3 = pixConvertTo8(pix2, FALSE);
+ pix4 = pixThresholdToBinary(pix3, 250);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 33 */
+ pix5 = pixReduceRankBinary2(pix4, 4, NULL);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 34 */
+ regTestComparePix(rp, pixs, pix5); /* 35 */
+ pixaAddPix(pixa, pix5, L_INSERT);
+ pix6 = pixExpandBinaryPower2(pix5, 2);
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 36 */
+ pixaAddPix(pixa, pix6, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Test 4x expansion of 4 bpp */
+ pix1 = pixConvert1To4(NULL, pixs, 15, 0);
+ pix2 = pixExpandReplicate(pix1, 4);
+ pix3 = pixConvertTo8(pix2, FALSE);
+ pix4 = pixThresholdToBinary(pix3, 250);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 37 */
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pix5 = pixReduceRankBinaryCascade(pix4, 4, 4, 0, 0);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 38 */
+ regTestComparePix(rp, pixs, pix5); /* 39 */
+ pixaAddPix(pixa, pix5, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Test 8x expansion of 8 bpp */
+ pix1 = pixConvertTo8(pixs, FALSE);
+ pix2 = pixExpandReplicate(pix1, 8);
+ pix3 = pixThresholdToBinary(pix2, 250);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 40 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix4 = pixReduceRankBinaryCascade(pix3, 4, 4, 4, 0);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 41 */
+ regTestComparePix(rp, pixs, pix4); /* 42 */
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixs);
+
+ if (rp->display) {
+ lept_stderr("Writing to: /tmp/lept/expand/test.pdf\n");
+ pixaConvertToPdf(pixa, 0, 1.0, 0, 0, "Replicative expansion",
+ "/tmp/lept/expand/test.pdf");
+ }
+ pixaDestroy(&pixa);
+
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/extrema_reg.c b/leptonica/prog/extrema_reg.c
new file mode 100644
index 00000000..ce653432
--- /dev/null
+++ b/leptonica/prog/extrema_reg.c
@@ -0,0 +1,102 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * extrema_reg.c
+ *
+ * Tests procedure for locating extrema (minima and maxima)
+ * of a sampled function.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_uint8 *data;
+size_t size;
+l_int32 i, ival, n;
+l_float32 f, val;
+GPLOT *gplot;
+NUMA *na1, *na2, *na3;
+PIX *pix1;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "extrema_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/extrema");
+
+ /* Generate a 1D signal and plot it */
+ na1 = numaCreate(500);
+ for (i = 0; i < 500; i++) {
+ f = 48.3 * sin(0.13 * (l_float32)i);
+ f += 63.4 * cos(0.21 * (l_float32)i);
+ numaAddNumber(na1, f);
+ }
+ gplot = gplotCreate("/tmp/lept/extrema/plot", GPLOT_PNG,
+ "Extrema test", "x", "y");
+ gplotAddPlot(gplot, NULL, na1, GPLOT_LINES, "plot 1");
+
+ /* Find the local min and max and plot them */
+ na2 = numaFindExtrema(na1, 38.3, NULL);
+ n = numaGetCount(na2);
+ na3 = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ numaGetIValue(na2, i, &ival);
+ numaGetFValue(na1, ival, &val);
+ numaAddNumber(na3, val);
+ }
+ gplotAddPlot(gplot, na2, na3, GPLOT_POINTS, "plot 2");
+ gplotMakeOutput(gplot);
+
+ numaWriteMem(&data, &size, na2);
+ regTestWriteDataAndCheck(rp, data, size, "na"); /* 0 */
+ lept_free(data);
+
+ regTestCheckFile(rp, "/tmp/lept/extrema/plot.png"); /* 1 */
+ if (rp->display) {
+ pix1 = pixRead("/tmp/lept/extrema/plot.png");
+ pixDisplay(pix1, 100, 100);
+ pixDestroy(&pix1);
+ }
+
+ gplotDestroy(&gplot);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/falsecolor_reg.c b/leptonica/prog/falsecolor_reg.c
new file mode 100644
index 00000000..d0f1ef79
--- /dev/null
+++ b/leptonica/prog/falsecolor_reg.c
@@ -0,0 +1,93 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * falsecolor_reg.c
+ *
+ * Test false color generation from 8 and 16 bpp gray
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_float32 gamma[] = {1.0, 2.0, 3.0};
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, val8, val16;
+NUMA *na;
+PIX *pix1, *pix8, *pix16, *pix8f, *pix16f;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pix8 = pixCreate(768, 100, 8);
+ pix16 = pixCreate(768, 100, 16);
+ for (i = 0; i < 100; i++) {
+ for (j = 0; j < 768; j++) {
+ val16 = 0xffff * j / 768;
+ pixSetPixel(pix16, j, i, val16);
+ val8 = 0xff * j / 768;
+ pixSetPixel(pix8, j, i, val8);
+ }
+ }
+ regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 0 */
+ regTestWritePixAndCheck(rp, pix16, IFF_PNG); /* 1 */
+
+ pixa = pixaCreate(8);
+ pixaAddPix(pixa, pix8, L_INSERT);
+ for (i = 0; i < 3; i++) {
+ pix8f = pixConvertGrayToFalseColor(pix8, gamma[i]);
+ regTestWritePixAndCheck(rp, pix8f, IFF_PNG); /* 2 - 4 */
+ pixaAddPix(pixa, pix8f, L_INSERT);
+ }
+ pixaAddPix(pixa, pix16, L_INSERT);
+ for (i = 0; i < 3; i++) {
+ pix16f = pixConvertGrayToFalseColor(pix16, gamma[i]);
+ regTestWritePixAndCheck(rp, pix16f, IFF_PNG); /* 5 - 7 */
+ pixaAddPix(pixa, pix16f, L_INSERT);
+ }
+
+ if (rp->display) {
+ /* Use na to display them in column major order with 4 rows */
+ na = numaCreate(8);
+ for (i = 0; i < 8; i++)
+ numaAddNumber(na, i / 4);
+ pix1 = pixaDisplayTiledByIndex(pixa, na, 768, 20, 2, 6, 0xff000000);
+ pixDisplay(pix1, 100, 100);
+ numaDestroy(&na);
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/fcombautogen.c b/leptonica/prog/fcombautogen.c
new file mode 100644
index 00000000..31018269
--- /dev/null
+++ b/leptonica/prog/fcombautogen.c
@@ -0,0 +1,80 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * fcombautogen.c
+ *
+ * This program is used to generate the two files of dwa code for combs,
+ * that are used in linear composite dwa operations for brick Sels.
+ * If filename is not given, the files are:
+ * dwacomb.<n>.c
+ * dwacomblow.<n>.c
+ * Otherwise they are:
+ * <filename>.<n>.c
+ * <filename>low.<n>.c
+ * where <n> is the input index.
+ * These two files, when compiled, are used to implement all
+ * the composite dwa operations for brick Sels, that are
+ * generated by selaAddDwaCombs().
+ *
+ * The library files dwacomp.2.c and dwacomblow.2.c were made
+ * using <n> = 2.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filename;
+l_int32 index, ret;
+SELA *sela;
+static char mainName[] = "fcombautogen";
+
+ if (argc != 2 && argc != 3)
+ return ERROR_INT(" Syntax: fcombautogen index <filename>",
+ mainName, 1);
+
+ setLeptDebugOK(1);
+ index = atoi(argv[1]);
+ sela = selaAddDwaCombs(NULL);
+
+ if (argc == 2)
+ filename = stringNew("dwacomb");
+ else
+ filename = argv[2];
+ ret = fmorphautogen(sela, index, filename);
+
+ if (argc == 2)
+ lept_free(filename);
+ selaDestroy(&sela);
+ return ret;
+}
+
diff --git a/leptonica/prog/feyn-fract.tif b/leptonica/prog/feyn-fract.tif
new file mode 100644
index 00000000..25f140a4
--- /dev/null
+++ b/leptonica/prog/feyn-fract.tif
Binary files differ
diff --git a/leptonica/prog/feyn-fract2.tif b/leptonica/prog/feyn-fract2.tif
new file mode 100644
index 00000000..4f92259f
--- /dev/null
+++ b/leptonica/prog/feyn-fract2.tif
Binary files differ
diff --git a/leptonica/prog/feyn-word.tif b/leptonica/prog/feyn-word.tif
new file mode 100644
index 00000000..f1be2a97
--- /dev/null
+++ b/leptonica/prog/feyn-word.tif
Binary files differ
diff --git a/leptonica/prog/feyn.tif b/leptonica/prog/feyn.tif
new file mode 100644
index 00000000..6fd17e40
--- /dev/null
+++ b/leptonica/prog/feyn.tif
Binary files differ
diff --git a/leptonica/prog/feynman-stamp.jpg b/leptonica/prog/feynman-stamp.jpg
new file mode 100644
index 00000000..4a9c22b6
--- /dev/null
+++ b/leptonica/prog/feynman-stamp.jpg
Binary files differ
diff --git a/leptonica/prog/fhmtauto_reg.c b/leptonica/prog/fhmtauto_reg.c
new file mode 100644
index 00000000..7d7331e1
--- /dev/null
+++ b/leptonica/prog/fhmtauto_reg.c
@@ -0,0 +1,90 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Basic regression test for hit-miss transform: rasterops & dwa.
+ *
+ * Tests hmt from a set of hmt structuring elements
+ * by comparing the full image rasterop results with the
+ * automatically generated dwa results.
+ *
+ * Results must be identical for all operations.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, nsels, same1, same2;
+char *selname;
+PIX *pixs, *pixref, *pix1, *pix2, *pix3, *pix4;
+SEL *sel;
+SELA *sela;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn.tif");
+ sela = selaAddHitMiss(NULL);
+ nsels = selaGetCount(sela);
+
+ for (i = 0; i < nsels; i++)
+ {
+ sel = selaGetSel(sela, i);
+ selname = selGetName(sel);
+
+ pixref = pixHMT(NULL, pixs, sel);
+ pix1 = pixAddBorder(pixs, 32, 0);
+ pix2 = pixFHMTGen_1(NULL, pix1, selname);
+ pix3 = pixRemoveBorder(pix2, 32);
+ pix4 = pixHMTDwa_1(NULL, pixs, selname);
+ regTestComparePix(rp, pixref, pix3); /* 0, 2, ... 18 */
+ regTestComparePix(rp, pixref, pix4); /* 1, 3, ... 19 */
+ pixEqual(pixref, pix3, &same1);
+ pixEqual(pixref, pix4, &same2);
+ if (!same1 || !same2)
+ lept_stderr("hmt differ for sel %d (%s)\n", i, selname);
+ if (rp->display && same1 && same2)
+ lept_stderr("hmt are identical for sel %d (%s)\n", i, selname);
+ pixDestroy(&pixref);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ }
+
+ pixDestroy(&pixs);
+ selaDestroy(&sela);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/fhmtautogen.c b/leptonica/prog/fhmtautogen.c
new file mode 100644
index 00000000..ac3bfb98
--- /dev/null
+++ b/leptonica/prog/fhmtautogen.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * fhmtautogen.c
+ *
+ * This program is used to generate the two files.
+ * If filename is not given, the files are:
+ * fhmtgen.<n>.c
+ * fhmtgenlow.<n>.c
+ * where <n> is the input index. Otherwise they are:
+ * <filename>.<n>.c
+ * <filename>low.<n>.c
+ * These two files, when compiled, implement hit-miss dwa
+ * operations for all sels generated by selaAddHitMiss().
+ *
+ * The library files fhmtgen.1.c and fhmtgenlow.1.c
+ * were made using index = 1.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filename;
+l_int32 index;
+SELA *sela;
+static char mainName[] = "fhmtautogen";
+
+ if (argc != 2 && argc != 3)
+ return ERROR_INT(" Syntax: fhmtautogen index <filename>", mainName, 1);
+
+ setLeptDebugOK(1);
+ index = atoi(argv[1]);
+ filename = NULL;
+ if (argc == 3)
+ filename = argv[2];
+
+ sela = selaAddHitMiss(NULL);
+ if (fhmtautogen(sela, index, filename))
+ return 1;
+
+ selaDestroy(&sela);
+ return 0;
+}
+
diff --git a/leptonica/prog/fileinfo.c b/leptonica/prog/fileinfo.c
new file mode 100644
index 00000000..05f50593
--- /dev/null
+++ b/leptonica/prog/fileinfo.c
@@ -0,0 +1,52 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * fileinfo.c
+ *
+ * Returns information about the image data file
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein;
+static char mainName[] = "fileinfo";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: fileinfo filein", mainName, 1);
+ filein = argv[1];
+
+ setLeptDebugOK(1);
+ writeImageFileInfo(filein, stderr, 0);
+ return 0;
+}
diff --git a/leptonica/prog/files_reg.c b/leptonica/prog/files_reg.c
new file mode 100644
index 00000000..49bea4c4
--- /dev/null
+++ b/leptonica/prog/files_reg.c
@@ -0,0 +1,290 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * files_reg.c
+ *
+ * Regression test for lept_*() and other path utilities in utils.h
+ *
+ * Some of these only work properly on unix because they explicitly
+ * use "/tmp" for string compares.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+#include <string.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#else
+#include <direct.h>
+#define getcwd _getcwd /* fix MSVC warning */
+#endif /* !_MSC_VER */
+
+void TestPathJoin(L_REGPARAMS *rp, const char *first, const char *second,
+ const char *result);
+void TestLeptCpRm(L_REGPARAMS *rp, const char *srctail, const char *newdir,
+ const char *newtail);
+void TestGenPathname(L_REGPARAMS *rp, const char *dir, const char *fname,
+ const char *result);
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 exists;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_stderr(" ===================================================\n");
+ lept_stderr(" =================== Test pathJoin() ===============\n");
+ lept_stderr(" ===================================================\n");
+ TestPathJoin(rp, "/a/b//c///d//", "//e//f//g//", "/a/b/c/d/e/f/g"); /* 0 */
+ TestPathJoin(rp, "/tmp/", "junk//", "/tmp/junk"); /* 1 */
+ TestPathJoin(rp, "//tmp/", "junk//", "/tmp/junk"); /* 2 */
+ TestPathJoin(rp, "tmp/", "//junk//", "tmp/junk"); /* 3 */
+ TestPathJoin(rp, "tmp/", "junk/////", "tmp/junk"); /* 4 */
+ TestPathJoin(rp, "/tmp/", "///", "/tmp"); /* 5 */
+ TestPathJoin(rp, "////", NULL, "/"); /* 6 */
+ TestPathJoin(rp, "//", "/junk//", "/junk"); /* 7 */
+ TestPathJoin(rp, NULL, "/junk//", "/junk"); /* 8 */
+ TestPathJoin(rp, NULL, "//junk//", "/junk"); /* 9 */
+ TestPathJoin(rp, NULL, "junk//", "junk"); /* 10 */
+ TestPathJoin(rp, NULL, "//", "/"); /* 11 */
+ TestPathJoin(rp, NULL, NULL, ""); /* 12 */
+ TestPathJoin(rp, "", "", ""); /* 13 */
+ TestPathJoin(rp, "/", "", "/"); /* 14 */
+ TestPathJoin(rp, "", "//", "/"); /* 15 */
+ TestPathJoin(rp, "", "a", "a"); /* 16 */
+
+ lept_stderr("The next 3 joins properly give error messages:\n");
+ lept_stderr("join: .. + a --> NULL\n");
+ pathJoin("..", "a"); /* returns NULL */
+ lept_stderr("join: %s + .. --> NULL\n", "/tmp");
+ pathJoin("/tmp", ".."); /* returns NULL */
+ lept_stderr("join: ./ + .. --> NULL\n");
+ pathJoin("./", ".."); /* returns NULL */
+
+ lept_stderr("\n ===================================================\n");
+ lept_stderr(" ======= Test lept_rmdir() and lept_mkdir()) =======\n");
+ lept_stderr(" ===================================================\n");
+ lept_rmdir("junkfiles");
+ lept_direxists("/tmp/junkfiles", &exists);
+ if (rp->display) lept_stderr("directory removed?: %d\n", !exists);
+ regTestCompareValues(rp, 0, exists, 0.0); /* 17 */
+
+ lept_mkdir("junkfiles");
+ lept_direxists("/tmp/junkfiles", &exists);
+ if (rp->display) lept_stderr("directory made?: %d\n", exists);
+ regTestCompareValues(rp, 1, exists, 0.0); /* 18 */
+
+ lept_stderr("\n ===================================================\n");
+ lept_stderr(" ======= Test lept_mv(), lept_cp(), lept_rm() ======\n");
+ lept_stderr(" ===================================================\n");
+ TestLeptCpRm(rp, "weasel2.png", NULL, NULL); /* 19 - 22 */
+ TestLeptCpRm(rp, "weasel2.png", "junkfiles", NULL); /* 23 - 26 */
+ TestLeptCpRm(rp, "weasel2.png", NULL, "new_weasel2.png"); /* 27 - 30 */
+ TestLeptCpRm(rp, "weasel2.png", "junkfiles", "new_weasel2.png"); /* 31-34 */
+
+ lept_stderr("\n ===================================================\n");
+ lept_stderr(" =============== Test genPathname() ================\n");
+ lept_stderr(" ===================================================\n");
+ TestGenPathname(rp, "what/", NULL, "what"); /* 35 */
+ TestGenPathname(rp, "what", "abc", "what/abc"); /* 36 */
+ TestGenPathname(rp, NULL, "abc/def", "abc/def"); /* 37 */
+ TestGenPathname(rp, "", "abc/def", "abc/def"); /* 38 */
+#ifndef _WIN32 /* unix only */
+ if (getenv("TMPDIR") == NULL) {
+ TestGenPathname(rp, "/tmp", NULL, "/tmp"); /* 39 */
+ TestGenPathname(rp, "/tmp/", NULL, "/tmp"); /* 40 */
+ TestGenPathname(rp, "/tmp/junk", NULL, "/tmp/junk"); /* 41 */
+ TestGenPathname(rp, "/tmp/junk/abc", NULL, "/tmp/junk/abc"); /* 42 */
+ TestGenPathname(rp, "/tmp/junk/", NULL, "/tmp/junk"); /* 43 */
+ TestGenPathname(rp, "/tmp/junk", "abc", "/tmp/junk/abc"); /* 44 */
+ }
+#endif /* !_WIN32 */
+
+ return regTestCleanup(rp);
+}
+
+
+void TestPathJoin(L_REGPARAMS *rp,
+ const char *first,
+ const char *second,
+ const char *result)
+{
+char *newfirst = NULL;
+char *newsecond = NULL;
+char *newpath = NULL;
+char *path = NULL;
+
+ if ((path = pathJoin(first, second)) == NULL) return;
+ regTestCompareStrings(rp, (l_uint8 *)result, strlen(result),
+ (l_uint8 *)path, strlen(path));
+
+ if (first && first[0] == '\0')
+ newfirst = stringNew("\"\"");
+ else if (first)
+ newfirst = stringNew(first);
+ if (second && second[0] == '\0')
+ newsecond = stringNew("\"\"");
+ else if (second)
+ newsecond = stringNew(second);
+ if (path && path[0] == '\0')
+ newpath = stringNew("\"\"");
+ else if (path)
+ newpath = stringNew(path);
+ if (rp->display)
+ lept_stderr("join: %s + %s --> %s\n", newfirst, newsecond, newpath);
+ lept_free(path);
+ lept_free(newfirst);
+ lept_free(newsecond);
+ lept_free(newpath);
+ return;
+}
+
+void TestLeptCpRm(L_REGPARAMS *rp,
+ const char *srctail,
+ const char *newdir,
+ const char *newtail)
+{
+char realnewdir[256], newnewdir[256];
+char *realtail, *newsrc, *fname;
+l_int32 nfiles1, nfiles2, nfiles3;
+SARRAY *sa;
+
+ /* Remove old version if it exists */
+ realtail = (newtail) ? stringNew(newtail) : stringNew(srctail);
+ lept_rm(newdir, realtail);
+ makeTempDirname(realnewdir, 256, newdir);
+ if (rp->display) {
+ lept_stderr("\nInput: srctail = %s, newdir = %s, newtail = %s\n",
+ srctail, newdir, newtail);
+ lept_stderr(" realnewdir = %s, realtail = %s\n", realnewdir, realtail);
+ }
+ sa = getFilenamesInDirectory(realnewdir);
+ nfiles1 = sarrayGetCount(sa);
+ sarrayDestroy(&sa);
+
+ /* Copy */
+ lept_cp(srctail, newdir, newtail, &fname);
+ sa = getFilenamesInDirectory(realnewdir);
+ nfiles2 = sarrayGetCount(sa);
+ if (rp->display) {
+ lept_stderr(" File copied to directory: %s\n", realnewdir);
+ lept_stderr(" ... with this filename: %s\n", fname);
+ lept_stderr(" delta files should be 1: %d\n", nfiles2 - nfiles1);
+ }
+ regTestCompareValues(rp, 1, nfiles2 - nfiles1, 0.0); /* '1' */
+ sarrayDestroy(&sa);
+ lept_free(fname);
+
+ /* Remove it */
+ lept_rm(newdir, realtail);
+ sa = getFilenamesInDirectory(realnewdir);
+ nfiles2 = sarrayGetCount(sa);
+ if (rp->display) {
+ lept_stderr(" File removed from directory: %s\n", realnewdir);
+ lept_stderr(" delta files should be 0: %d\n", nfiles2 - nfiles1);
+ }
+ regTestCompareValues(rp, 0, nfiles2 - nfiles1, 0.0); /* '2' */
+ sarrayDestroy(&sa);
+
+ /* Copy it again ... */
+ lept_cp(srctail, newdir, newtail, &fname);
+ if (rp->display)
+ lept_stderr(" File copied to: %s\n", fname);
+ lept_free(fname);
+
+ /* move it elsewhere ... */
+ lept_rmdir("junko"); /* clear out this directory */
+ lept_mkdir("junko");
+ newsrc = pathJoin(realnewdir, realtail);
+ lept_mv(newsrc, "junko", NULL, &fname);
+ if (rp->display) {
+ lept_stderr(" Move file at: %s\n", newsrc);
+ lept_stderr(" ... to: %s\n", fname);
+ }
+ lept_free(fname);
+ lept_free(newsrc);
+ makeTempDirname(newnewdir, 256, "junko");
+ if (rp->display) lept_stderr(" In this directory: %s\n", newnewdir);
+ sa = getFilenamesInDirectory(newnewdir); /* check if it landed ok */
+ nfiles3 = sarrayGetCount(sa);
+ if (rp->display) lept_stderr(" num files should be 1: %d\n", nfiles3);
+ regTestCompareValues(rp, 1, nfiles3, 0.0); /* '3' */
+ sarrayDestroy(&sa);
+
+ /* and verify it was removed from the original location */
+ sa = getFilenamesInDirectory(realnewdir); /* check if it was removed */
+ nfiles2 = sarrayGetCount(sa);
+ if (rp->display) {
+ lept_stderr(" In this directory: %s\n", realnewdir);
+ lept_stderr(" delta files should be 0: %d\n", nfiles2 - nfiles1);
+ }
+ regTestCompareValues(rp, 0, nfiles2 - nfiles1, 0.0); /* '4' */
+ sarrayDestroy(&sa);
+ lept_free(realtail);
+}
+
+void TestGenPathname(L_REGPARAMS *rp,
+ const char *dir,
+ const char *fname,
+ const char *result)
+{
+char expect[512], localdir[256];
+
+ char *path = genPathname(dir, fname);
+ if (!dir || dir[0] == '\0') {
+ if (!getcwd(localdir, sizeof(localdir)))
+ lept_stderr("bad bad bad -- no local directory!\n");
+ snprintf(expect, sizeof(expect), "%s/%s", localdir, result);
+#ifdef _WIN32
+ convertSepCharsInPath(expect, UNIX_PATH_SEPCHAR);
+#endif /* _WIN32 */
+ regTestCompareStrings(rp, (l_uint8 *)expect, strlen(expect),
+ (l_uint8 *)path, strlen(path));
+ } else {
+ regTestCompareStrings(rp, (l_uint8 *)result, strlen(result),
+ (l_uint8 *)path, strlen(path));
+ }
+ if (rp->display) {
+ char *newdir = NULL;
+ if (dir && dir[0] == '\0')
+ newdir = stringNew("\"\"");
+ else if (dir)
+ newdir = stringNew(dir);
+ lept_stderr("genPathname(%s, %s) --> %s\n", newdir, fname, path);
+ lept_free(newdir);
+ }
+ lept_free(path);
+ return;
+}
+
+
diff --git a/leptonica/prog/find_colorregions.c b/leptonica/prog/find_colorregions.c
new file mode 100644
index 00000000..d5980724
--- /dev/null
+++ b/leptonica/prog/find_colorregions.c
@@ -0,0 +1,343 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * find_colorregions.c
+ *
+ * This shows the output of pixHasColorRegions(), which attempts to locate
+ * colored regions on scanned images. The difficulty arises when the
+ * scanned images are oxidized, dark and reddish.
+ *
+ * It also shows output from pixFindColorRegionsLight(), which is an
+ * inferior implementation that does not work on images with a dark
+ * background.
+ *
+ * The input image should be RGB at 75 ppi resolution.
+ *
+ * Use, e.g. these 75 ppi images:
+ * map.057.jpg
+ * colorpage.030.jpg
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Implementation is in this file */
+static l_int32
+pixFindColorRegionsLight(PIX *pixs, PIX *pixm, l_int32 factor,
+ l_int32 darkthresh, l_int32 lightthresh, l_int32 mindiff,
+ l_int32 colordiff, l_float32 *pcolorfract,
+ PIX **pcolormask1, PIX **pcolormask2, PIXA *pixadb);
+
+int main(int argc,
+ char **argv)
+{
+l_float32 fcolor;
+PIX *pix1, *pix2, *pix3, *pix4;
+PIXA *pixadb;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/color");
+ pix1 = pixRead("colorpage.030.jpg");
+/* pix1 = pixRead("map.057.jpg"); */
+
+ /* More general method */
+ pixadb = pixaCreate(0);
+ pixFindColorRegions(pix1, NULL, 4, 200, 60, 10, 90, 0.05,
+ &fcolor, &pix3, &pix4, pixadb);
+ lept_stderr("ncolor = %f\n", fcolor);
+ if (pix3) pixDisplay(pix3, 0, 800);
+ if (pix4) pixDisplay(pix4, 600, 800);
+
+ pix2 = pixaDisplayTiledInColumns(pixadb, 5, 0.3, 20, 2);
+ pixDisplay(pix2, 0, 0);
+ pixWrite("/tmp/lept/color/result1.png", pix2, IFF_PNG);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixaDestroy(&pixadb);
+
+ /* Method for pages with very light background */
+ pixadb = pixaCreate(0);
+ pixFindColorRegionsLight(pix1, NULL, 4, 60, 230, 40, 20,
+ &fcolor, &pix3, &pix4, pixadb);
+ lept_stderr("ncolor = %f\n", fcolor);
+ if (pix3) pixDisplay(pix3, 1100, 800);
+ if (pix4) pixDisplay(pix4, 1700, 800);
+
+ pix2 = pixaDisplayTiledInColumns(pixadb, 5, 0.3, 20, 2);
+ pixDisplay(pix2, 1100, 0);
+ pixWrite("/tmp/lept/color/result2.png", pix2, IFF_PNG);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixaDestroy(&pixadb);
+
+ pixDestroy(&pix1);
+ return 0;
+}
+
+
+/*!
+ * Note: this method is generally inferior to pixHasColorRegions(); it
+ * is retained as a reference only
+ *
+ * \brief pixFindColorRegionsLight()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] pixm [optional] 1 bpp mask image
+ * \param[in] factor subsample factor; integer >= 1
+ * \param[in] darkthresh threshold to eliminate dark pixels (e.g., text)
+ * from consideration; typ. 70; -1 for default.
+ * \param[in] lightthresh threshold for minimum gray value at 95% rank
+ * near white; typ. 220; -1 for default
+ * \param[in] mindiff minimum difference from 95% rank value, used
+ * to count darker pixels; typ. 50; -1 for default
+ * \param[in] colordiff minimum difference in (max - min) component to
+ * qualify as a color pixel; typ. 40; -1 for default
+ * \param[out] pcolorfract fraction of 'color' pixels found
+ * \param[out] pcolormask1 [optional] mask over background color, if any
+ * \param[out] pcolormask2 [optional] filtered mask over background color
+ * \param[out] pixadb [optional] debug intermediate results
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function tries to determine if there is a significant
+ * color or darker region on a scanned page image where part
+ * of the image is very close to "white". It will also allow
+ * extraction of small regions of lightly colored pixels.
+ * If the background is darker (and reddish), use instead
+ * pixHasColorRegions2().
+ * (2) If %pixm exists, only pixels under fg are considered. Typically,
+ * the inverse of %pixm would have fg pixels over a photograph.
+ * (3) There are four thresholds.
+ * * %darkthresh: ignore pixels darker than this (typ. fg text).
+ * We make a 1 bpp mask of these pixels, and then dilate it to
+ * remove all vestiges of fg from their vicinity.
+ * * %lightthresh: let val95 be the pixel value for which 95%
+ * of the non-masked pixels have a lower value (darker) of
+ * their min component. Then if val95 is darker than
+ * %lightthresh, the image is not considered to have a
+ * light bg, and this returns 0.0 for %colorfract.
+ * * %mindiff: we are interested in the fraction of pixels that
+ * have two conditions. The first is that their min component
+ * is at least %mindiff darker than val95.
+ * * %colordiff: the second condition is that the max-min diff
+ * of the pixel components exceeds %colordiff.
+ * (4) This returns in %pcolorfract the fraction of pixels that have
+ * both a min component that is at least %mindiff below that at the
+ * 95% rank value (where 100% rank is the lightest value), and
+ * a max-min diff that is at least %colordiff. Without the
+ * %colordiff constraint, gray pixels of intermediate value
+ * could get flagged by this function.
+ * (5) No masks are returned unless light color pixels are found.
+ * If colorfract > 0.0 and %pcolormask1 is defined, this returns
+ * a 1 bpp mask with fg pixels over the color background.
+ * This mask may have some holes in it.
+ * (6) If colorfract > 0.0 and %pcolormask2 is defined, this returns
+ * a filtered version of colormask1. The two changes are
+ * (a) small holes have been filled
+ * (b) components near the border have been removed.
+ * The latter insures that dark pixels near the edge of the
+ * image are not included.
+ * (7) To generate a boxa of rectangular regions from the overlap
+ * of components in the filtered mask:
+ * boxa1 = pixConnCompBB(colormask2, 8);
+ * boxa2 = boxaCombineOverlaps(boxa1);
+ * This is done here in debug mode.
+ * </pre>
+ */
+static l_int32
+pixFindColorRegionsLight(PIX *pixs,
+ PIX *pixm,
+ l_int32 factor,
+ l_int32 darkthresh,
+ l_int32 lightthresh,
+ l_int32 mindiff,
+ l_int32 colordiff,
+ l_float32 *pcolorfract,
+ PIX **pcolormask1,
+ PIX **pcolormask2,
+ PIXA *pixadb)
+{
+l_int32 lightbg, w, h, count;
+l_float32 ratio, val95;
+BOXA *boxa1, *boxa2;
+NUMA *nah;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pixm1, *pixm2, *pixm3;
+
+ PROCNAME("pixFindColorRegionsLight");
+
+ if (pcolormask1) *pcolormask1 = NULL;
+ if (pcolormask2) *pcolormask2 = NULL;
+ if (!pcolorfract)
+ return ERROR_INT("&colorfract not defined", procName, 1);
+ *pcolorfract = 0.0;
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return ERROR_INT("pixs not defined or not 32 bpp", procName, 1);
+ if (factor < 1) factor = 1;
+ if (darkthresh < 0) darkthresh = 70; /* defaults */
+ if (lightthresh < 0) lightthresh = 220;
+ if (mindiff < 0) mindiff = 50;
+ if (colordiff < 0) colordiff = 40;
+
+ /* Check if pixm covers most of the image. If so, just return. */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ if (pixm) {
+ pixCountPixels(pixm, &count, NULL);
+ ratio = (l_float32)count / ((l_float32)(w) * h);
+ if (ratio > 0.7) {
+ if (pixadb) L_INFO("pixm has big fg: %f5.2\n", procName, ratio);
+ return 0;
+ }
+ }
+
+ /* Make a mask pixm1 over the dark pixels in the image:
+ * convert to gray using the average of the components;
+ * threshold using %darkthresh; do a small dilation;
+ * combine with pixm. */
+ pix1 = pixConvertRGBToGray(pixs, 0.33, 0.34, 0.33);
+ if (pixadb) pixaAddPix(pixadb, pixs, L_COPY);
+ if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
+ pixm1 = pixThresholdToBinary(pix1, darkthresh);
+ pixDilateBrick(pixm1, pixm1, 7, 7);
+ if (pixadb) pixaAddPix(pixadb, pixm1, L_COPY);
+ if (pixm) {
+ pixOr(pixm1, pixm1, pixm);
+ if (pixadb) pixaAddPix(pixadb, pixm1, L_COPY);
+ }
+ pixDestroy(&pix1);
+
+ /* Convert to gray using the minimum component value and
+ * find the gray value at rank 0.95, that represents the light
+ * pixels in the image. If it is too dark, quit. */
+ pix1 = pixConvertRGBToGrayMinMax(pixs, L_SELECT_MIN);
+ pix2 = pixInvert(NULL, pixm1); /* pixels that are not dark */
+ pixGetRankValueMasked(pix1, pix2, 0, 0, factor, 0.95, &val95, &nah);
+ pixDestroy(&pix2);
+ if (pixadb) {
+ L_INFO("val at 0.95 rank = %5.1f\n", procName, val95);
+ gplotSimple1(nah, GPLOT_PNG, "/tmp/lept/histo1", "gray histo");
+ pix3 = pixRead("/tmp/lept/histo1.png");
+ pix4 = pixExpandReplicate(pix3, 2);
+ pixaAddPix(pixadb, pix4, L_INSERT);
+ pixDestroy(&pix3);
+ }
+ lightbg = (l_int32)val95 >= lightthresh;
+ numaDestroy(&nah);
+ if (!lightbg) {
+ pixDestroy(&pix1);
+ pixDestroy(&pixm1);
+ return 0;
+ }
+
+ /* Make mask pixm2 over pixels that are darker than val95 - mindiff. */
+ pixm2 = pixThresholdToBinary(pix1, val95 - mindiff);
+ if (pixadb) pixaAddPix(pixadb, pixm2, L_COPY);
+ pixDestroy(&pix1);
+
+ /* Make a mask pixm3 over pixels that have some color saturation,
+ * with a (max - min) component difference >= %colordiff,
+ * and combine using AND with pixm2. */
+ pix2 = pixConvertRGBToGrayMinMax(pixs, L_CHOOSE_MAXDIFF);
+ pixm3 = pixThresholdToBinary(pix2, colordiff);
+ pixDestroy(&pix2);
+ pixInvert(pixm3, pixm3); /* need pixels above threshold */
+ if (pixadb) pixaAddPix(pixadb, pixm3, L_COPY);
+ pixAnd(pixm2, pixm2, pixm3);
+ if (pixadb) pixaAddPix(pixadb, pixm2, L_COPY);
+ pixDestroy(&pixm3);
+
+ /* Subtract the dark pixels represented by pixm1.
+ * pixm2 now holds all the color pixels of interest */
+ pixSubtract(pixm2, pixm2, pixm1);
+ pixDestroy(&pixm1);
+ if (pixadb) pixaAddPix(pixadb, pixm2, L_COPY);
+
+ /* But we're not quite finished. Remove pixels from any component
+ * that is touching the image border. False color pixels can
+ * sometimes be found there if the image is much darker near
+ * the border, due to oxidation or reduced illumination. */
+ pixm3 = pixRemoveBorderConnComps(pixm2, 8);
+ pixDestroy(&pixm2);
+ if (pixadb) pixaAddPix(pixadb, pixm3, L_COPY);
+
+ /* Get the fraction of light color pixels */
+ pixCountPixels(pixm3, &count, NULL);
+ *pcolorfract = (l_float32)count / (w * h);
+ if (pixadb) {
+ if (count == 0)
+ L_INFO("no light color pixels found\n", procName);
+ else
+ L_INFO("fraction of light color pixels = %5.3f\n", procName,
+ *pcolorfract);
+ }
+
+ /* Debug: extract the color pixels from pixs */
+ if (pixadb && count > 0) {
+ /* Use pixm3 to extract the color pixels */
+ pix3 = pixCreateTemplate(pixs);
+ pixSetAll(pix3);
+ pixCombineMasked(pix3, pixs, pixm3);
+ pixaAddPix(pixadb, pix3, L_INSERT);
+
+ /* Use additional filtering to extract the color pixels */
+ pix3 = pixCloseSafeBrick(NULL, pixm3, 15, 15);
+ pixaAddPix(pixadb, pix3, L_INSERT);
+ pix5 = pixCreateTemplate(pixs);
+ pixSetAll(pix5);
+ pixCombineMasked(pix5, pixs, pix3);
+ pixaAddPix(pixadb, pix5, L_INSERT);
+
+ /* Get the combined bounding boxes of the mask components
+ * in pix3, and extract those pixels from pixs. */
+ boxa1 = pixConnCompBB(pix3, 8);
+ boxa2 = boxaCombineOverlaps(boxa1, NULL);
+ pix4 = pixCreateTemplate(pix3);
+ pixMaskBoxa(pix4, pix4, boxa2, L_SET_PIXELS);
+ pixaAddPix(pixadb, pix4, L_INSERT);
+ pix5 = pixCreateTemplate(pixs);
+ pixSetAll(pix5);
+ pixCombineMasked(pix5, pixs, pix4);
+ pixaAddPix(pixadb, pix5, L_INSERT);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ pixaAddPix(pixadb, pixs, L_COPY);
+ }
+
+ /* Optional colormask returns */
+ if (pcolormask2 && count > 0)
+ *pcolormask2 = pixCloseSafeBrick(NULL, pixm3, 15, 15);
+ if (pcolormask1 && count > 0)
+ *pcolormask1 = pixm3;
+ else
+ pixDestroy(&pixm3);
+ return 0;
+}
diff --git a/leptonica/prog/findbinding.c b/leptonica/prog/findbinding.c
new file mode 100644
index 00000000..2d1ead8f
--- /dev/null
+++ b/leptonica/prog/findbinding.c
@@ -0,0 +1,164 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * findbinding.c
+ *
+ * Here is a simple approach to find the location of the binding
+ * in an open book that is photographed. It relies on the typical
+ * condition that the background pixels near the binding are
+ * darker than those on the rest of the page, and further, that
+ * the lightest pixels in each column parallel to the binding
+ * exhibit a large variance by column near the binding. This is
+ * because the pixels at the binding are typically even darker
+ * than the pixels near the binding.
+ *
+ * Accurate results are obtained in this example at the very low
+ * resolution of 45 ppi. Better results can be expected at higher
+ * resolution.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w, h, ystart, yend, y, ymax, ymid, i, window, sum1, sum2, rankx;
+l_uint32 uval;
+l_float32 ave, rankval, maxvar, variance, norm, conf, angle, radangle;
+NUMA *na1;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
+PIXA *pixa;
+static char mainName[] = "findbinding";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: findbinding", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/binding");
+ pixa = pixaCreate(0);
+
+ pix1 = pixRead("binding-example.45.jpg");
+ pix2 = pixConvertTo8(pix1, 0);
+
+ /* Find the skew angle */
+ pix3 = pixConvertTo1(pix2, 150);
+ pixFindSkewSweepAndSearch(pix3, &angle, &conf, 2, 2, 7.0, 1.0, 0.01);
+ lept_stderr("angle = %f, conf = %f\n", angle, conf);
+
+ /* Deskew, bringing in black pixels at the edges */
+ if (L_ABS(angle) < 0.1 || conf < 1.5) {
+ pix4 = pixClone(pix2);
+ } else {
+ radangle = 3.1416 * angle / 180.0;
+ pix4 = pixRotate(pix2, radangle, L_ROTATE_AREA_MAP,
+ L_BRING_IN_BLACK, 0, 0);
+ }
+
+ /* Rotate 90 degrees to make binding horizontal */
+ pix5 = pixRotateOrth(pix4, 1);
+
+ /* Sort pixels in each row by their gray value.
+ * Dark pixels on the left, light ones on the right. */
+ pix6 = pixRankRowTransform(pix5);
+ pixDisplay(pix5, 0, 0);
+ pixDisplay(pix6, 550, 0);
+ pixaAddPix(pixa, pix4, L_COPY);
+ pixaAddPix(pixa, pix5, L_COPY);
+ pixaAddPix(pixa, pix6, L_COPY);
+
+ /* Make an a priori estimate of the y-interval within which the
+ * binding will be found. The search will be done in this interval. */
+ pixGetDimensions(pix6, &w, &h, NULL);
+ ystart = 0.25 * h;
+ yend = 0.75 * h;
+
+ /* Choose a very light rank value; close to white, which
+ * corresponds to a column in pix6 near the right side. */
+ rankval = 0.98;
+ rankx = (l_int32)(w * rankval);
+
+ /* Investigate variance in a small window (vertical, size = 5)
+ * of the pixels in that column. These are the %rankval
+ * pixels in each raster of pix6. Find the y-location of
+ * maximum variance. */
+ window = 5;
+ norm = 1.0 / window;
+ maxvar = 0.0;
+ na1 = numaCreate(0);
+ numaSetParameters(na1, ystart, 1);
+ for (y = ystart; y <= yend; y++) {
+ sum1 = sum2 = 0;
+ for (i = 0; i < window; i++) {
+ pixGetPixel(pix6, rankx, y + i, &uval);
+ sum1 += uval;
+ sum2 += uval * uval;
+ }
+ ave = norm * sum1;
+ variance = norm * sum2 - ave * ave;
+ numaAddNumber(na1, variance);
+ ymid = y + window / 2;
+ if (variance > maxvar) {
+ maxvar = variance;
+ ymax = ymid;
+ }
+ }
+
+ /* Plot the windowed variance as a function of the y-value
+ * of the window location */
+ lept_stderr("maxvar = %f, ymax = %d\n", maxvar, ymax);
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/binding/root", NULL);
+ pix7 = pixRead("/tmp/lept/binding/root.png");
+ pixDisplay(pix7, 0, 800);
+ pixaAddPix(pixa, pix7, L_COPY);
+
+ /* Superimpose the variance plot over the image.
+ * The variance peak is at the binding. */
+ pixRenderPlotFromNumaGen(&pix5, na1, L_VERTICAL_LINE, 3, w - 120, 100, 1,
+ 0x0000ff00);
+ pixDisplay(pix5, 1050, 0);
+ pixaAddPix(pixa, pix5, L_COPY);
+
+ /* Bundle the results up in a pdf */
+ lept_stderr("Writing pdf output file: /tmp/lept/binding/binding.pdf\n");
+ pixaConvertToPdf(pixa, 45, 1.0, 0, 0, "Binding locator",
+ "/tmp/lept/binding/binding.pdf");
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixaDestroy(&pixa);
+ numaDestroy(&na1);
+ return 0;
+}
diff --git a/leptonica/prog/findcorners_reg.c b/leptonica/prog/findcorners_reg.c
new file mode 100644
index 00000000..c5e74a92
--- /dev/null
+++ b/leptonica/prog/findcorners_reg.c
@@ -0,0 +1,210 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * findcorners_reg.c
+ *
+ * This reg test demonstrates extraction of deskewed objects (tickets
+ * in this case) using morphological operations to identify the
+ * barcodes on each object. The objects are separately deskewed,
+ * the barcodes are again located, and the objects are extracted.
+ *
+ * We also show how to generate the HMT sela for detecting corners,
+ * and how to use it (with pixUnionOfMorphOps()) to find all the
+ * corners. The corner Sels were constructed to find significant
+ * corners in the presence of the type of noise expected from
+ * scanned images. The located corners are displayed by xor-ing
+ * a pattern (sel_cross) on each one.
+ *
+ * When this function is called with the display argument
+ * findcorners_reg display
+ * we display some results and additionally generate the following pdfs:
+ * /tmp/lept/regout/seq_output_1.pdf (morphological operations of
+ * first call to locate barcodes)
+ * /tmp/lept/regout/tickets.pdf (deskewed result for the set of tickets)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static BOXA *LocateBarcodes(PIX *pixs, PIX **ppixd, l_int32 flag);
+static SELA *GetCornerSela(L_REGPARAMS *rp);
+
+static const char *sel_cross = " xxx "
+ " xxx "
+ " xxx "
+ " xxx "
+ " xxx "
+ "xxxxxxxxxxxxx"
+ "xxxxxxXxxxxxx"
+ "xxxxxxxxxxxxx"
+ " xxx "
+ " xxx "
+ " xxx "
+ " xxx "
+ " xxx ";
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i, n, flag;
+l_float32 angle, conf, deg2rad;
+BOX *box1, *box2, *box3, *box4;
+BOXA *boxa, *boxa2;
+PIX *pixs, *pixd, *pix1, *pix2, *pix3;
+PIXA *pixa;
+SEL *sel;
+SELA *sela;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("tickets.tif");
+ flag = (rp->display) ? -1 : 0;
+ boxa = LocateBarcodes(pixs, &pixd, flag);
+ regTestWritePixAndCheck(rp, pixd, IFF_TIFF_G4); /* 0 */
+ if (rp->display) boxaWriteStream(stderr, boxa);
+ n = boxaGetCount(boxa);
+ deg2rad = 3.14159265 / 180.;
+ pixa = pixaCreate(9);
+ for (i = 0; i < n; i++) {
+ box1 = boxaGetBox(boxa, i, L_CLONE);
+ /* Use a larger adjustment to get entire skewed ticket */
+ box2 = boxAdjustSides(NULL, box1, -266, 346, -1560, 182);
+ pix1 = pixClipRectangle(pixs, box2, NULL);
+ /* Deskew */
+ pixFindSkew(pix1, &angle, &conf);
+ pix2 = pixRotate(pix1, deg2rad * angle, L_ROTATE_SAMPLING,
+ L_BRING_IN_WHITE, 0, 0);
+ /* Find the barcode again ... */
+ boxa2 = LocateBarcodes(pix2, NULL, 0);
+ box3 = boxaGetBox(boxa2, 0, L_CLONE);
+ /* ... and adjust crop box exactly for ticket size */
+ box4 = boxAdjustSides(NULL, box3, -141, 221, -1535, 157);
+ pix3 = pixClipRectangle(pix2, box4, NULL);
+ regTestWritePixAndCheck(rp, pix3, IFF_TIFF_G4); /* 1 - 9 */
+ if (rp->display)
+ pixaAddPix(pixa, pix3, L_INSERT);
+ else
+ pixDestroy(&pix3);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ boxDestroy(&box3);
+ boxDestroy(&box4);
+ boxaDestroy(&boxa2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ if (rp->display) {
+ pixaConvertToPdf(pixa, 0, 1.0, 0, 0, "tickets",
+ "/tmp/lept/regout/tickets.pdf");
+ L_INFO("Output pdf: /tmp/lept/regout/tickets.pdf\n", rp->testname);
+ }
+ pixaDestroy(&pixa);
+
+ /* Downscale by 2x and locate corners */
+ pix1 = pixScale(pixd, 0.5, 0.5);
+ regTestWritePixAndCheck(rp, pix1, IFF_TIFF_G4); /* 10 */
+ pixDisplayWithTitle(pix1, 100, 200, NULL, rp->display);
+ /* Find corners and blit a cross onto each (4 to each barcode) */
+ sela = GetCornerSela(rp);
+ pix2 = pixUnionOfMorphOps(pix1, sela, L_MORPH_HMT);
+ sel = selCreateFromString(sel_cross, 13, 13, "sel_cross");
+ pix3 = pixDilate(NULL, pix2, sel);
+ pixXor(pix3, pix3, pix1);
+ regTestWritePixAndCheck(rp, pix3, IFF_TIFF_G4); /* 11 */
+ pixDisplayWithTitle(pix3, 800, 200, NULL, rp->display);
+
+ boxaDestroy(&boxa);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pixd);
+ selDestroy(&sel);
+ selaDestroy(&sela);
+ return regTestCleanup(rp);
+}
+
+
+static BOXA *
+LocateBarcodes(PIX *pixs,
+ PIX **ppixd,
+ l_int32 flag)
+{
+BOXA *boxa1, *boxa2, *boxad;
+PIX *pix1, *pix2, *pix3;
+
+ pix1 = pixScale(pixs, 0.5, 0.5);
+ pix2 = pixMorphSequence(pix1, "o1.5 + c15.1 + o10.15 + c20.20", flag);
+ boxa1 = pixConnComp(pix2, NULL, 8);
+ boxa2 = boxaSelectBySize(boxa1, 300, 0, L_SELECT_WIDTH,
+ L_SELECT_IF_GT, NULL);
+ boxad = boxaTransform(boxa2, 0, 0, 2.0, 2.0);
+ if (ppixd) {
+ pix3 = pixSelectBySize(pix2, 300, 0, 8, L_SELECT_WIDTH,
+ L_SELECT_IF_GT, NULL);
+ *ppixd = pixScale(pix3, 2.0, 2.0);
+ pixDestroy(&pix3);
+ }
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ return boxad;
+}
+
+
+static SELA *
+GetCornerSela(L_REGPARAMS *rp)
+{
+PIX *pix;
+SEL *sel;
+SELA *sela1, *sela2;
+
+ sela1 = selaAddHitMiss(NULL);
+ sela2 = selaCreate(4);
+ selaFindSelByName(sela1, "sel_ulc", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, 1);
+ selaFindSelByName(sela1, "sel_urc", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, 1);
+ selaFindSelByName(sela1, "sel_llc", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, 1);
+ selaFindSelByName(sela1, "sel_lrc", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, 1);
+ selaDestroy(&sela1);
+ if (rp->display) {
+ pix = selaDisplayInPix(sela2, 21, 3, 10, 4);
+ pixDisplay(pix, 0, 0);
+ pixDestroy(&pix);
+ }
+ return sela2;
+}
diff --git a/leptonica/prog/findpattern1.c b/leptonica/prog/findpattern1.c
new file mode 100644
index 00000000..42395db7
--- /dev/null
+++ b/leptonica/prog/findpattern1.c
@@ -0,0 +1,145 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * findpattern1.c
+ *
+ * findpattern1 filein patternfile fileout
+ *
+ * This is setup with input parameters to generate a hit-miss
+ * Sel from 'patternfile' and use it on 'filein' at 300 ppi.
+ * For example, use char.tif of a "c" bitmap, taken from the
+ * the page image feyn.tif:
+ *
+ * findpattern1 feyn.tif char.tif /tmp/result.tif
+ *
+ * This shows a number of different outputs, including a magnified
+ * image of the Sel superimposed on the "c" bitmap.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* for pixGenerateSelWithRuns() */
+static const l_int32 NumHorLines = 11;
+static const l_int32 NumVertLines = 8;
+static const l_int32 MinRunlength = 1;
+
+ /* for pixDisplayHitMissSel() */
+static const l_uint32 HitColor = 0xff880000;
+static const l_uint32 MissColor = 0x00ff8800;
+
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout, *patternfile;
+l_int32 w, h, i, n;
+BOX *box, *boxe;
+BOXA *boxa1, *boxa2;
+PIX *pixs, *pixp, *pixpe, *pix1, *pix2, *pix3, *pix4, *pixhmt;
+PIXCMAP *cmap;
+SEL *sel_2h, *sel;
+static char mainName[] = "findpattern1";
+
+ if (argc != 4)
+ return ERROR_INT(" Syntax: findpattern1 filein patternfile fileout",
+ mainName, 1);
+ filein = argv[1];
+ patternfile = argv[2];
+ fileout = argv[3];
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/hmt");
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+ if ((pixp = pixRead(patternfile)) == NULL)
+ return ERROR_INT("pixp not made", mainName, 1);
+ pixGetDimensions(pixp, &w, &h, NULL);
+
+ /* Generate the hit-miss Sel with runs */
+ sel = pixGenerateSelWithRuns(pixp, NumHorLines, NumVertLines, 0,
+ MinRunlength, 7, 7, 0, 0, &pixpe);
+
+ /* Display the Sel two ways */
+ selWriteStream(stderr, sel);
+ pix1 = pixDisplayHitMissSel(pixpe, sel, 9, HitColor, MissColor);
+ pixDisplay(pix1, 200, 200);
+ pixWrite("/tmp/lept/hmt/pix1.png", pix1, IFF_PNG);
+
+ /* Use the Sel to find all instances in the page */
+ startTimer();
+ pixhmt = pixHMT(NULL, pixs, sel);
+ lept_stderr("Time to find patterns = %7.3f\n", stopTimer());
+
+ /* Small erosion to remove noise; typically not necessary if
+ * there are enough elements in the Sel */
+ sel_2h = selCreateBrick(1, 2, 0, 0, SEL_HIT);
+ pix2 = pixErode(NULL, pixhmt, sel_2h);
+
+ /* Display the result visually by placing the Sel at each
+ * location found */
+ pix3 = pixDilate(NULL, pix2, sel);
+ cmap = pixcmapCreate(1);
+ pixcmapAddColor(cmap, 255, 255, 255);
+ pixcmapAddColor(cmap, 255, 0, 0);
+ pixSetColormap(pix3, cmap);
+ pixWrite(fileout, pix3, IFF_PNG);
+
+ /* Display output with a red outline around each located pattern */
+ boxa1 = pixConnCompBB(pix2, 8);
+ n = boxaGetCount(boxa1);
+ boxa2 = boxaCreate(n);
+ pix4 = pixConvert1To2Cmap(pixs);
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxa1, i, L_COPY);
+ boxe = boxCreate(box->x - w / 2, box->y - h / 2, w + 4, h + 4);
+ boxaAddBox(boxa2, boxe, L_INSERT);
+ pixRenderBoxArb(pix4, boxe, 2, 255, 0, 0);
+ boxDestroy(&box);
+ }
+ pixWrite("/tmp/lept/hmt/outline.png", pix4, IFF_PNG);
+ boxaWriteStream(stderr, boxa2);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixp);
+ pixDestroy(&pixpe);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pixhmt);
+ selDestroy(&sel);
+ selDestroy(&sel_2h);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ return 0;
+}
+
diff --git a/leptonica/prog/findpattern2.c b/leptonica/prog/findpattern2.c
new file mode 100644
index 00000000..26c400bd
--- /dev/null
+++ b/leptonica/prog/findpattern2.c
@@ -0,0 +1,161 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * findpattern2.c
+ *
+ * We use pixGenerateSelRandom() to generate the sels.
+ *
+ * This is set up with input parameters to work on feyn.tif.
+ *
+ * (1) We extract a "e" bitmap, generate a hit-miss sel, and
+ * then produce several 4 bpp colormapped renditions,
+ * with the pattern either removed or highlighted.
+ *
+ * (2) We do the same with the word "Caltech".
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* for pixDisplayHitMissSel() */
+static const l_uint32 HitColor = 0x33aa4400;
+static const l_uint32 MissColor = 0xaa44bb00;
+
+
+int main(int argc,
+ char **argv)
+{
+BOX *box;
+PIX *pixs, *pixc, *pixp, *pixsel, *pixhmt;
+PIX *pixd1, *pixd2, *pixd3;
+SEL *selhm;
+static char mainName[] = "findpattern2";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: findpattern2", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/hmt");
+
+ /* -------------------------------------------- *
+ * Extract the pattern for a single character *
+ * ---------------------------------------------*/
+ pixs = pixRead("feyn.tif");
+ box = boxCreate(599, 1055, 18, 23);
+ pixc = pixClipRectangle(pixs, box, NULL);
+
+ /* Make a hit-miss sel */
+ selhm = pixGenerateSelRandom(pixc, 0.3, 0.2, 1, 6, 6, 0, 0, &pixp);
+
+ /* Display the sel */
+ pixsel = pixDisplayHitMissSel(pixp, selhm, 7, HitColor, MissColor);
+ pixDisplay(pixsel, 200, 200);
+ pixWrite("/tmp/lept/hmt/pixsel1.png", pixsel, IFF_PNG);
+
+ /* Use the Sel to find all instances in the page */
+ startTimer();
+ pixhmt = pixHMT(NULL, pixs, selhm);
+ lept_stderr("Time to find patterns = %7.3f\n", stopTimer());
+
+ /* Color each instance at full res */
+ pixd1 = pixDisplayMatchedPattern(pixs, pixp, pixhmt, selhm->cx,
+ selhm->cy, 0x0000ff00, 1.0, 5);
+ pixWrite("/tmp/lept/hmt/pixd11.png", pixd1, IFF_PNG);
+
+ /* Color each instance at 0.3 scale */
+ pixd2 = pixDisplayMatchedPattern(pixs, pixp, pixhmt, selhm->cx,
+ selhm->cy, 0x0000ff00, 0.5, 5);
+ pixWrite("/tmp/lept/hmt/junkpixd12.png", pixd2, IFF_PNG);
+
+ /* Remove each instance from the input image */
+ pixd3 = pixCopy(NULL, pixs);
+ pixRemoveMatchedPattern(pixd3, pixp, pixhmt, selhm->cx,
+ selhm->cy, 1);
+ pixWrite("/tmp/lept/hmt/pixr1.png", pixd3, IFF_PNG);
+
+ boxDestroy(&box);
+ selDestroy(&selhm);
+ pixDestroy(&pixc);
+ pixDestroy(&pixp);
+ pixDestroy(&pixsel);
+ pixDestroy(&pixhmt);
+ pixDestroy(&pixd1);
+ pixDestroy(&pixd2);
+ pixDestroy(&pixd3);
+
+
+ /* -------------------------------------------- *
+ * Extract the pattern for a word *
+ * ---------------------------------------------*/
+ box = boxCreate(208, 872, 130, 35);
+ pixc = pixClipRectangle(pixs, box, NULL);
+
+ /* Make a hit-miss sel */
+ selhm = pixGenerateSelRandom(pixc, 1.0, 0.05, 2, 6, 6, 0, 0, &pixp);
+
+ /* Display the sel */
+ pixsel = pixDisplayHitMissSel(pixp, selhm, 7, HitColor, MissColor);
+ pixDisplay(pixsel, 200, 200);
+ pixWrite("/tmp/lept/hmt/pixsel2.png", pixsel, IFF_PNG);
+
+ /* Use the Sel to find all instances in the page */
+ startTimer();
+ pixhmt = pixHMT(NULL, pixs, selhm);
+ lept_stderr("Time to find word patterns = %7.3f\n", stopTimer());
+
+ /* Color each instance at full res */
+ pixd1 = pixDisplayMatchedPattern(pixs, pixp, pixhmt, selhm->cx,
+ selhm->cy, 0x0000ff00, 1.0, 5);
+ pixWrite("/tmp/lept/hmt/pixd21.png", pixd1, IFF_PNG);
+
+ /* Color each instance at 0.3 scale */
+ pixd2 = pixDisplayMatchedPattern(pixs, pixp, pixhmt, selhm->cx,
+ selhm->cy, 0x0000ff00, 0.5, 5);
+ pixWrite("/tmp/lept/hmt/pixd22.png", pixd2, IFF_PNG);
+
+ /* Remove each instance from the input image */
+ pixd3 = pixCopy(NULL, pixs);
+ pixRemoveMatchedPattern(pixd3, pixp, pixhmt, selhm->cx,
+ selhm->cy, 1);
+ pixWrite("/tmp/lept/hmt/pixr2.png", pixd3, IFF_PNG);
+
+ selDestroy(&selhm);
+ boxDestroy(&box);
+ pixDestroy(&pixc);
+ pixDestroy(&pixp);
+ pixDestroy(&pixsel);
+ pixDestroy(&pixhmt);
+ pixDestroy(&pixd1);
+ pixDestroy(&pixd2);
+ pixDestroy(&pixd3);
+ pixDestroy(&pixs);
+ return 0;
+}
+
diff --git a/leptonica/prog/findpattern3.c b/leptonica/prog/findpattern3.c
new file mode 100644
index 00000000..cc641cfe
--- /dev/null
+++ b/leptonica/prog/findpattern3.c
@@ -0,0 +1,160 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * findpattern3.c
+ *
+ * We use pixGenerateSelBoundary() to generate the sels.
+ *
+ * This is set up with input parameters to work on feyn.tif.
+ *
+ * (1) We extract an "e" bitmap, generate a hit-miss sel, and
+ * then produce several 4 bpp colormapped renditions,
+ * with the pattern either removed or highlighted.
+ *
+ * (2) We do the same with the word "Caltech".
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* for pixDisplayHitMissSel() */
+static const l_uint32 HitColor = 0x33aa4400;
+static const l_uint32 MissColor = 0xaa44bb00;
+
+int main(int argc,
+ char **argv)
+{
+BOX *box;
+PIX *pixs, *pixc, *pixp, *pixsel, *pixhmt;
+PIX *pixd1, *pixd2, *pixd3;
+SEL *selhm;
+static char mainName[] = "findpattern3";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: findpattern3", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/hmt");
+
+ /* -------------------------------------------- *
+ * Extract the pattern for a single character *
+ * ---------------------------------------------*/
+ pixs = pixRead("feyn.tif");
+ box = boxCreate(599, 1055, 18, 23);
+ pixc = pixClipRectangle(pixs, box, NULL);
+
+ /* Make a hit-miss sel */
+ selhm = pixGenerateSelBoundary(pixc, 1, 2, 2, 2, 1, 1, 0, 0, &pixp);
+
+ /* Display the sel */
+ pixsel = pixDisplayHitMissSel(pixp, selhm, 7, HitColor, MissColor);
+ pixDisplay(pixsel, 200, 200);
+ pixWrite("/tmp/lept/hmt/pixsel1.png", pixsel, IFF_PNG);
+
+ /* Use the Sel to find all instances in the page */
+ startTimer();
+ pixhmt = pixHMT(NULL, pixs, selhm);
+ lept_stderr("Time to find patterns = %7.3f\n", stopTimer());
+
+ /* Color each instance at full res */
+ pixd1 = pixDisplayMatchedPattern(pixs, pixp, pixhmt, selhm->cx,
+ selhm->cy, 0x0000ff00, 1.0, 5);
+ pixWrite("/tmp/lept/hmt/pixd11.png", pixd1, IFF_PNG);
+
+ /* Color each instance at 0.3 scale */
+ pixd2 = pixDisplayMatchedPattern(pixs, pixp, pixhmt, selhm->cx,
+ selhm->cy, 0x0000ff00, 0.5, 5);
+ pixWrite("/tmp/lept/hmt/pixd12.png", pixd2, IFF_PNG);
+
+ /* Remove each instance from the input image */
+ pixd3 = pixCopy(NULL, pixs);
+ pixRemoveMatchedPattern(pixd3, pixp, pixhmt, selhm->cx,
+ selhm->cy, 1);
+ pixWrite("/tmp/lept/hmt/pixr1.png", pixd3, IFF_PNG);
+
+ boxDestroy(&box);
+ selDestroy(&selhm);
+ pixDestroy(&pixc);
+ pixDestroy(&pixp);
+ pixDestroy(&pixsel);
+ pixDestroy(&pixhmt);
+ pixDestroy(&pixd1);
+ pixDestroy(&pixd2);
+ pixDestroy(&pixd3);
+
+
+ /* -------------------------------------------- *
+ * Extract the pattern for a word *
+ * ---------------------------------------------*/
+ box = boxCreate(208, 872, 130, 35);
+ pixc = pixClipRectangle(pixs, box, NULL);
+
+ /* Make a hit-miss sel */
+ selhm = pixGenerateSelBoundary(pixc, 2, 2, 1, 4, 1, 1, 0, 0, &pixp);
+
+ /* Display the sel */
+ pixsel = pixDisplayHitMissSel(pixp, selhm, 7, HitColor, MissColor);
+ pixDisplay(pixsel, 200, 200);
+ pixWrite("/tmp/lept/hmt/pixsel2.png", pixsel, IFF_PNG);
+
+ /* Use the Sel to find all instances in the page */
+ startTimer();
+ pixhmt = pixHMT(NULL, pixs, selhm);
+ lept_stderr("Time to find word patterns = %7.3f\n", stopTimer());
+
+ /* Color each instance at full res */
+ pixd1 = pixDisplayMatchedPattern(pixs, pixp, pixhmt, selhm->cx,
+ selhm->cy, 0x0000ff00, 1.0, 5);
+ pixWrite("/tmp/lept/hmt/pixd21.png", pixd1, IFF_PNG);
+
+ /* Color each instance at 0.3 scale */
+ pixd2 = pixDisplayMatchedPattern(pixs, pixp, pixhmt, selhm->cx,
+ selhm->cy, 0x0000ff00, 0.5, 5);
+ pixWrite("/tmp/lept/hmt/pixd22.png", pixd2, IFF_PNG);
+
+ /* Remove each instance from the input image */
+ pixd3 = pixCopy(NULL, pixs);
+ pixRemoveMatchedPattern(pixd3, pixp, pixhmt, selhm->cx,
+ selhm->cy, 1);
+ pixWrite("/tmp/lept/hmt/pixr2.png", pixd3, IFF_PNG);
+
+ selDestroy(&selhm);
+ boxDestroy(&box);
+ pixDestroy(&pixc);
+ pixDestroy(&pixp);
+ pixDestroy(&pixsel);
+ pixDestroy(&pixhmt);
+ pixDestroy(&pixd1);
+ pixDestroy(&pixd2);
+ pixDestroy(&pixd3);
+ pixDestroy(&pixs);
+ return 0;
+}
+
diff --git a/leptonica/prog/findpattern_reg.c b/leptonica/prog/findpattern_reg.c
new file mode 100644
index 00000000..7b86bfe2
--- /dev/null
+++ b/leptonica/prog/findpattern_reg.c
@@ -0,0 +1,173 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * findpattern_reg.c
+ *
+ * This uses pixGenerateSelBoundary() to generate hit-miss Sels
+ * that are a good fit for two 1 bpp patterns:
+ * * a "T" in the banner name
+ * * the banner name ("Tribune")
+ * The sels are first displayed, showing the hit and miss elements
+ * in color.
+ *
+ * The sels are then used to identify and remove the components
+ * in a page image in which thay are found. We demonstrate
+ * the ability to find these components are reductions from 4 to 16x.
+ * (16x is extreme -- don't do this at home!) The results are displayed
+ * with the matched pattern either highlighted or removed.
+ *
+ * Some of these Sels are also made by livre_hmt.c for figures
+ * in the Document Image Applications chapter.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* for pixDisplayHitMissSel() */
+static const l_uint32 HitColor = 0x33aa4400;
+static const l_uint32 MissColor = 0xaa44bb00;
+
+ /* Patterns at full resolution */
+static const char *patname[2] = {
+ "tribune-word.png", /* patno = 0 */
+ "tribune-t.png"}; /* patno = 1 */
+
+l_int32 GeneratePattern(l_int32 patno, l_int32 red, L_REGPARAMS *rp);
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 patno, red;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ for (patno = 0; patno < 2; patno++) {
+ for (red = 4; red <= 16; red *= 2) {
+ if (patno == 1 && red == 16) continue;
+ GeneratePattern(patno, red, rp);
+ }
+ }
+
+ return regTestCleanup(rp);
+}
+
+
+l_int32
+GeneratePattern(l_int32 patno,
+ l_int32 red,
+ L_REGPARAMS *rp)
+{
+l_int32 width, cx, cy;
+PIX *pixs, *pixt, *pix, *pixr, *pixp, *pixsel, *pixhmt;
+PIX *pixc1, *pixc2, *pixc3, *pixd;
+PIXA *pixa;
+SEL *selhm;
+
+ PROCNAME("GeneratePattern");
+
+ if ((pixs = pixRead(patname[patno])) == NULL) {
+ rp->success = FALSE;
+ return ERROR_INT("pixs not made", procName, 1);
+ }
+
+ /* Make a hit-miss sel at specified reduction factor */
+ if (red == 4) {
+ pixt = pixReduceRankBinaryCascade(pixs, 4, 4, 0, 0);
+ selhm = pixGenerateSelBoundary(pixt, 2, 2, 20, 30, 1, 1, 0, 0, &pixp);
+ }
+ else if (red == 8) {
+ pixt = pixReduceRankBinaryCascade(pixs, 4, 4, 2, 0);
+ selhm = pixGenerateSelBoundary(pixt, 1, 2, 6, 12, 1, 1, 0, 0, &pixp);
+ }
+ else { /* red == 16 */
+ pixt = pixReduceRankBinaryCascade(pixs, 4, 4, 2, 2);
+ selhm = pixGenerateSelBoundary(pixt, 1, 1, 4, 8, 0, 0, 0, 0, &pixp);
+ }
+ pixDestroy(&pixt);
+
+ /* Display the sel */
+ pixsel = pixDisplayHitMissSel(pixp, selhm, 7, HitColor, MissColor);
+ pixa = pixaCreate(2);
+ pixaAddPix(pixa, pixs, L_CLONE);
+ pixaAddPix(pixa, pixsel, L_CLONE);
+ width = (patno == 0) ? 1200 : 400;
+ pixd = pixaDisplayTiledAndScaled(pixa, 32, width, 2, 0, 30, 2);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixDisplayWithTitle(pixd, 100, 100 + 100 * (3 * patno + red / 4),
+ NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+
+ /* Use the sel to find all instances in the page */
+ pix = pixRead("tribune-page-4x.png"); /* 4x reduced */
+ if (red == 4)
+ pixr = pixClone(pix);
+ else if (red == 8)
+ pixr = pixReduceRankBinaryCascade(pix, 2, 0, 0, 0);
+ else /* red == 16 */
+ pixr = pixReduceRankBinaryCascade(pix, 2, 2, 0, 0);
+ pixDestroy(&pix);
+
+ startTimer();
+ pixhmt = pixHMT(NULL, pixr, selhm);
+ lept_stderr("Time to find patterns = %7.3f\n", stopTimer());
+
+ /* Color each instance at full res */
+ selGetParameters(selhm, NULL, NULL, &cy, &cx);
+ pixc1 = pixDisplayMatchedPattern(pixr, pixp, pixhmt,
+ cx, cy, 0x0000ff00, 1.0, 5);
+ regTestWritePixAndCheck(rp, pixc1, IFF_PNG);
+ pixDisplayWithTitle(pixc1, 500, 100, NULL, rp->display);
+
+ /* Color each instance at 0.5 scale */
+ pixc2 = pixDisplayMatchedPattern(pixr, pixp, pixhmt,
+ cx, cy, 0x0000ff00, 0.5, 5);
+ regTestWritePixAndCheck(rp, pixc2, IFF_PNG);
+
+ /* Remove each instance from the input image */
+ pixc3 = pixCopy(NULL, pixr);
+ pixRemoveMatchedPattern(pixc3, pixp, pixhmt, cx, cy, 1);
+ regTestWritePixAndCheck(rp, pixc3, IFF_PNG);
+
+ selDestroy(&selhm);
+ pixDestroy(&pixp);
+ pixDestroy(&pixsel);
+ pixDestroy(&pixhmt);
+ pixDestroy(&pixc1);
+ pixDestroy(&pixc2);
+ pixDestroy(&pixc3);
+ pixDestroy(&pixd);
+ pixDestroy(&pixr);
+ pixDestroy(&pixs);
+ return 0;
+}
diff --git a/leptonica/prog/fish24.jpg b/leptonica/prog/fish24.jpg
new file mode 100644
index 00000000..eba97436
--- /dev/null
+++ b/leptonica/prog/fish24.jpg
Binary files differ
diff --git a/leptonica/prog/flipdetect_reg.c b/leptonica/prog/flipdetect_reg.c
new file mode 100644
index 00000000..226224a3
--- /dev/null
+++ b/leptonica/prog/flipdetect_reg.c
@@ -0,0 +1,119 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * flipdetect_reg
+ *
+ * - Tests the high-level text orientation interface
+ * - Tests 90 degree orientation of text and whether the text is
+ * mirror reversed.
+ * - Shows the typical 'confidence' outputs from functions in flipdetect.c.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, orient, rotation;
+l_float32 upconf, leftconf, conf;
+PIX *pix, *pixs, *pix1, *pix2;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pix = pixRead("feyn.tif");
+ pixs = pixScale(pix, 0.5, 0.5);
+ pixDestroy(&pix);
+
+ /* Test high-level interface */
+ lept_stderr("\nTest high-level detection/rotation\n");
+ pix1 = pixRotateOrth(pixs, 3);
+ pix2 = pixOrientCorrect(pix1, 0.0, 0.0, &upconf, &leftconf,
+ &rotation, 0);
+ if (rp->display)
+ lept_stderr("upconf = %7.3f, leftconf = %7.3f, rotation = %d\n",
+ upconf, leftconf, rotation);
+ regTestCompareValues(rp, upconf, 2.543, 0.1); /* 0 */
+ regTestCompareValues(rp, leftconf, 15.431, 0.1); /* 1 */
+ regTestCompareValues(rp, rotation, 90, 0.0); /* 2 */
+ regTestComparePix(rp, pixs, pix2); /* 3 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Test orientation detection */
+ pixa = pixaCreate(4);
+ pix1 = pixCopy(NULL, pixs);
+ lept_stderr("\nTest orient detection for 4 orientations\n");
+ pixOrientDetect(pix1, &upconf, &leftconf, 0, 0);
+ makeOrientDecision(upconf, leftconf, 0, 0, &orient, 1);
+ regTestCompareValues(rp, upconf, 15.431, 0.1); /* 4 */
+ regTestCompareValues(rp, orient, 1, 0.0); /* 5 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixRotate90(pix1, 1);
+ pix1 = pix2;
+ pixOrientDetect(pix1, &upconf, &leftconf, 0, 0);
+ makeOrientDecision(upconf, leftconf, 0, 0, &orient, 1);
+ regTestCompareValues(rp, leftconf, -15.702, 0.1); /* 6 */
+ regTestCompareValues(rp, orient, 4, 0.0); /* 7 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixRotate90(pix1, 1);
+ pix1 = pix2;
+ pixOrientDetect(pix1, &upconf, &leftconf, 0, 0);
+ makeOrientDecision(upconf, leftconf, 0, 0, &orient, 1);
+ regTestCompareValues(rp, upconf, -15.702, 0.1); /* 8 */
+ regTestCompareValues(rp, orient, 3, 0.0); /* 9 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixRotate90(pix1, 1);
+ pix1 = pix2;
+ pixOrientDetect(pix1, &upconf, &leftconf, 0, 0);
+ makeOrientDecision(upconf, leftconf, 0, 0, &orient, 1);
+ regTestCompareValues(rp, leftconf, 15.431, 0.1); /* 10 */
+ regTestCompareValues(rp, orient, 2, 0.0); /* 11 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ pix2 = pixaDisplayTiledInColumns(pixa, 2, 0.25, 20, 2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 12 */
+ pixDisplayWithTitle(pix2, 100, 100, NULL, rp->display);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa);
+
+ lept_stderr("\nTest mirror reverse detection\n");
+ pixMirrorDetect(pixs, &conf, 0, rp->display);
+ lept_stderr("conf = %5.3f; not mirror reversed\n", conf);
+ regTestCompareValues(rp, conf, 4.128, 0.1); /* 13 */
+
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/flipselgen.c.notused b/leptonica/prog/flipselgen.c.notused
new file mode 100644
index 00000000..f8a76774
--- /dev/null
+++ b/leptonica/prog/flipselgen.c.notused
@@ -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.
+ *====================================================================*/
+
+/*
+ * flipselgen.c
+ *
+ * NOTE
+ * ================================================================
+ * This code has been retired from the library, along with the code
+ * in flipdetectdwa.c that it generates. It is no longer compiled.
+ * ================================================================
+ *
+ * Results are two files:
+ * fmorphgen.3.c
+ * fmorphgenlow.3.c
+ * using INDEX = 3.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define INDEX 3
+#define DFLAG 1
+
+ /* Sels for pixPageFlipDetectDWA() */
+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";
+
+int main(int argc,
+ char **argv)
+{
+SEL *sel1, *sel2, *sel3, *sel4;
+SELA *sela;
+PIX *pix, *pixd;
+PIXA *pixa;
+static char mainName[] = "flipselgen";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: flipselgen", mainName, 1);
+
+ setLeptDebugOK(1);
+ sela = selaCreate(0);
+ sel1 = selCreateFromString(textsel1, 5, 6, "flipsel1");
+ sel2 = selCreateFromString(textsel2, 5, 6, "flipsel2");
+ sel3 = selCreateFromString(textsel3, 5, 6, "flipsel3");
+ sel4 = selCreateFromString(textsel4, 5, 6, "flipsel4");
+ selaAddSel(sela, sel1, NULL, 0);
+ selaAddSel(sela, sel2, NULL, 0);
+ selaAddSel(sela, sel3, NULL, 0);
+ selaAddSel(sela, sel4, NULL, 0);
+
+ pixa = pixaCreate(4);
+ pix = selDisplayInPix(sel1, 23, 2);
+ pixDisplayWithTitle(pix, 100, 100, "sel1", DFLAG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = selDisplayInPix(sel2, 23, 2);
+ pixDisplayWithTitle(pix, 275, 100, "sel2", DFLAG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = selDisplayInPix(sel3, 23, 2);
+ pixDisplayWithTitle(pix, 450, 100, "sel3", DFLAG);
+ pixaAddPix(pixa, pix, L_INSERT);
+ pix = selDisplayInPix(sel4, 23, 2);
+ pixDisplayWithTitle(pix, 625, 100, "sel4", DFLAG);
+ pixaAddPix(pixa, pix, L_INSERT);
+
+ pixd = pixaDisplayTiled(pixa, 800, 0, 15);
+ pixDisplayWithTitle(pixd, 100, 300, "allsels", DFLAG);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ if (fhmtautogen(sela, INDEX, NULL))
+ return ERROR_INT(" Generation failed", mainName, 1);
+
+ selaDestroy(&sela);
+ return 0;
+}
+
diff --git a/leptonica/prog/flipsels.txt b/leptonica/prog/flipsels.txt
new file mode 100644
index 00000000..eb71128c
--- /dev/null
+++ b/leptonica/prog/flipsels.txt
@@ -0,0 +1,35 @@
+# flipsels.txt
+#
+# Example file for reading Sels from file in
+# a simple format. These Sels are also defined
+# in flipdetect.c and compiled into strings there.
+#
+
+textsel1
+"x oo "
+"x oOo "
+"x o "
+"x "
+"xxxxxx"
+
+textsel2
+" oo x"
+" oOo x"
+" o x"
+" x"
+"xxxxxx"
+
+textsel3
+"xxxxxx"
+"x "
+"x o "
+"x oOo "
+"x oo "
+
+textsel4
+"xxxxxx"
+" x"
+" o x"
+" oOo x"
+" oo x"
+
diff --git a/leptonica/prog/fmorphauto_reg.c b/leptonica/prog/fmorphauto_reg.c
new file mode 100644
index 00000000..36d5967e
--- /dev/null
+++ b/leptonica/prog/fmorphauto_reg.c
@@ -0,0 +1,163 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Basic regression test for erosion & dilation: rasterops & dwa.
+ *
+ * Tests erosion and dilation from 58 structuring elements
+ * by comparing the full image rasterop results with the
+ * automatically generated dwa results.
+ *
+ * Results must be identical for all operations.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* defined in morph.c */
+LEPT_DLL extern l_int32 MORPH_BC;
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, nsels, same, xorcount;
+char *filein, *selname;
+PIX *pixs, *pixs1, *pixt1, *pixt2, *pixt3, *pixt4;
+SEL *sel;
+SELA *sela;
+static char mainName[] = "fmorphauto_reg";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: fmorphauto_reg filein", mainName, 1);
+ filein = argv[1];
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pix not made", mainName, 1);
+
+ sela = selaAddBasic(NULL);
+ nsels = selaGetCount(sela);
+ for (i = 0; i < nsels; i++)
+ {
+ sel = selaGetSel(sela, i);
+ selname = selGetName(sel);
+
+ /* --------- dilation ----------*/
+
+ pixt1 = pixDilate(NULL, pixs, sel);
+
+ pixs1 = pixAddBorder(pixs, 32, 0);
+ pixt2 = pixFMorphopGen_1(NULL, pixs1, L_MORPH_DILATE, selname);
+ pixt3 = pixRemoveBorder(pixt2, 32);
+
+ pixt4 = pixXor(NULL, pixt1, pixt3);
+ pixZero(pixt4, &same);
+
+ if (same == 1) {
+ lept_stderr("dilations are identical for sel %d (%s)\n",
+ i, selname);
+ } else {
+ lept_stderr("dilations differ for sel %d (%s)\n", i, selname);
+ pixCountPixels(pixt4, &xorcount, NULL);
+ lept_stderr("Number of pixels in XOR: %d\n", xorcount);
+ }
+
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixDestroy(&pixt3);
+ pixDestroy(&pixt4);
+ pixDestroy(&pixs1);
+
+ /* --------- erosion with asymmetric b.c ----------*/
+
+ resetMorphBoundaryCondition(ASYMMETRIC_MORPH_BC);
+ lept_stderr("MORPH_BC = %d ... ", MORPH_BC);
+ pixt1 = pixErode(NULL, pixs, sel);
+
+ if (MORPH_BC == ASYMMETRIC_MORPH_BC)
+ pixs1 = pixAddBorder(pixs, 32, 0); /* OFF border pixels */
+ else
+ pixs1 = pixAddBorder(pixs, 32, 1); /* ON border pixels */
+ pixt2 = pixFMorphopGen_1(NULL, pixs1, L_MORPH_ERODE, selname);
+ pixt3 = pixRemoveBorder(pixt2, 32);
+
+ pixt4 = pixXor(NULL, pixt1, pixt3);
+ pixZero(pixt4, &same);
+
+ if (same == 1) {
+ lept_stderr("erosions are identical for sel %d (%s)\n", i, selname);
+ } else {
+ lept_stderr("erosions differ for sel %d (%s)\n", i, selname);
+ pixCountPixels(pixt4, &xorcount, NULL);
+ lept_stderr("Number of pixels in XOR: %d\n", xorcount);
+ }
+
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixDestroy(&pixt3);
+ pixDestroy(&pixt4);
+ pixDestroy(&pixs1);
+
+ /* --------- erosion with symmetric b.c ----------*/
+
+ resetMorphBoundaryCondition(SYMMETRIC_MORPH_BC);
+ lept_stderr("MORPH_BC = %d ... ", MORPH_BC);
+ pixt1 = pixErode(NULL, pixs, sel);
+
+ if (MORPH_BC == ASYMMETRIC_MORPH_BC)
+ pixs1 = pixAddBorder(pixs, 32, 0); /* OFF border pixels */
+ else
+ pixs1 = pixAddBorder(pixs, 32, 1); /* ON border pixels */
+ pixt2 = pixFMorphopGen_1(NULL, pixs1, L_MORPH_ERODE, selname);
+ pixt3 = pixRemoveBorder(pixt2, 32);
+
+ pixt4 = pixXor(NULL, pixt1, pixt3);
+ pixZero(pixt4, &same);
+
+ if (same == 1) {
+ lept_stderr("erosions are identical for sel %d (%s)\n", i, selname);
+ } else {
+ lept_stderr("erosions differ for sel %d (%s)\n", i, selname);
+ pixCountPixels(pixt4, &xorcount, NULL);
+ lept_stderr("Number of pixels in XOR: %d\n", xorcount);
+ }
+
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixDestroy(&pixt3);
+ pixDestroy(&pixt4);
+ pixDestroy(&pixs1);
+ }
+
+ return 0;
+}
+
diff --git a/leptonica/prog/fmorphautogen.c b/leptonica/prog/fmorphautogen.c
new file mode 100644
index 00000000..9564f770
--- /dev/null
+++ b/leptonica/prog/fmorphautogen.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * fmorphautogen.c
+ *
+ * This program is used to generate the two files.
+ * If filename is not given, the files are:
+ * fmorphgen.<n>.c
+ * fmorphgenlow.<n>.c
+ * where <n> is the input index. Otherwise they are:
+ * <filename>.<n>.c
+ * <filename>low.<n>.c
+ * These two files, when compiled, implement dwa operations for
+ * all sels generated by selaAddBasic().
+ *
+ * The library files fmorphgen.1.c and fmorphgenlow.1.c
+ * were made using index = 1.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filename;
+l_int32 index;
+SELA *sela;
+static char mainName[] = "fmorphautogen";
+
+ if (argc != 2 && argc != 3)
+ return ERROR_INT(" Syntax: fmorphautogen index <filename>",
+ mainName, 1);
+ index = atoi(argv[1]);
+ filename = NULL;
+ if (argc == 3)
+ filename = argv[2];
+
+ setLeptDebugOK(1);
+ sela = selaAddBasic(NULL);
+ if (fmorphautogen(sela, index, filename))
+ return 1;
+
+ selaDestroy(&sela);
+ return 0;
+}
+
diff --git a/leptonica/prog/fonts/chars-10.pa b/leptonica/prog/fonts/chars-10.pa
new file mode 100644
index 00000000..fd3d93a7
--- /dev/null
+++ b/leptonica/prog/fonts/chars-10.pa
Binary files differ
diff --git a/leptonica/prog/fonts/chars-10.ps b/leptonica/prog/fonts/chars-10.ps
new file mode 100644
index 00000000..cee09b2a
--- /dev/null
+++ b/leptonica/prog/fonts/chars-10.ps
@@ -0,0 +1,21 @@
+%!PS
+% chars-10.ps
+
+/inch {72 mul} def
+/Palatino-Roman
+10 72 div inch
+selectfont
+
+% 25 chars in first row: 33-57
+0.3 inch 2.0 inch moveto
+3. 0. (!"#$%&'()*+,-./0123456789) ashow
+
+% 34 chars in second row: 58-91
+0.3 inch 1.4 inch moveto
+3. 0. (:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[) ashow
+
+% 34 chars in third row: 93-126
+0.3 inch 0.8 inch moveto
+3. 0. (]^_`abcdefghijklmnopqrstuvwxyz{|}~) ashow
+
+showpage
diff --git a/leptonica/prog/fonts/chars-10.tif b/leptonica/prog/fonts/chars-10.tif
new file mode 100644
index 00000000..a6e36520
--- /dev/null
+++ b/leptonica/prog/fonts/chars-10.tif
Binary files differ
diff --git a/leptonica/prog/fonts/chars-12.pa b/leptonica/prog/fonts/chars-12.pa
new file mode 100644
index 00000000..f7159865
--- /dev/null
+++ b/leptonica/prog/fonts/chars-12.pa
Binary files differ
diff --git a/leptonica/prog/fonts/chars-12.ps b/leptonica/prog/fonts/chars-12.ps
new file mode 100644
index 00000000..87b03b76
--- /dev/null
+++ b/leptonica/prog/fonts/chars-12.ps
@@ -0,0 +1,21 @@
+%!PS
+% chars-12.ps
+
+/inch {72 mul} def
+/Palatino-Roman
+12 72 div inch
+selectfont
+
+% 25 chars in first row: 33-57
+0.3 inch 2.0 inch moveto
+3. 0. (!"#$%&'()*+,-./0123456789) ashow
+
+% 34 chars in second row: 58-91
+0.3 inch 1.4 inch moveto
+3. 0. (:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[) ashow
+
+% 34 chars in third row: 93-126
+0.3 inch 0.8 inch moveto
+3. 0. (]^_`abcdefghijklmnopqrstuvwxyz{|}~) ashow
+
+showpage
diff --git a/leptonica/prog/fonts/chars-12.tif b/leptonica/prog/fonts/chars-12.tif
new file mode 100644
index 00000000..f53304f4
--- /dev/null
+++ b/leptonica/prog/fonts/chars-12.tif
Binary files differ
diff --git a/leptonica/prog/fonts/chars-14.pa b/leptonica/prog/fonts/chars-14.pa
new file mode 100644
index 00000000..1b0ede1e
--- /dev/null
+++ b/leptonica/prog/fonts/chars-14.pa
Binary files differ
diff --git a/leptonica/prog/fonts/chars-14.ps b/leptonica/prog/fonts/chars-14.ps
new file mode 100644
index 00000000..364fee0f
--- /dev/null
+++ b/leptonica/prog/fonts/chars-14.ps
@@ -0,0 +1,21 @@
+%!PS
+% chars-14.ps
+
+/inch {72 mul} def
+/Palatino-Roman
+14 72 div inch
+selectfont
+
+% 25 chars in first row: 33-57
+0.3 inch 2.0 inch moveto
+3. 0. (!"#$%&'()*+,-./0123456789) ashow
+
+% 34 chars in second row: 58-91
+0.3 inch 1.4 inch moveto
+3. 0. (:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[) ashow
+
+% 34 chars in third row: 93-126
+0.3 inch 0.8 inch moveto
+3. 0. (]^_`abcdefghijklmnopqrstuvwxyz{|}~) ashow
+
+showpage
diff --git a/leptonica/prog/fonts/chars-14.tif b/leptonica/prog/fonts/chars-14.tif
new file mode 100644
index 00000000..ff47cb5b
--- /dev/null
+++ b/leptonica/prog/fonts/chars-14.tif
Binary files differ
diff --git a/leptonica/prog/fonts/chars-16.pa b/leptonica/prog/fonts/chars-16.pa
new file mode 100644
index 00000000..32c9bd2f
--- /dev/null
+++ b/leptonica/prog/fonts/chars-16.pa
Binary files differ
diff --git a/leptonica/prog/fonts/chars-16.ps b/leptonica/prog/fonts/chars-16.ps
new file mode 100644
index 00000000..a2390247
--- /dev/null
+++ b/leptonica/prog/fonts/chars-16.ps
@@ -0,0 +1,21 @@
+%!PS
+% chars-16.ps
+
+/inch {72 mul} def
+/Palatino-Roman
+16 72 div inch
+selectfont
+
+% 25 chars in first row: 33-57
+0.3 inch 2.0 inch moveto
+3. 0. (!"#$%&'()*+,-./0123456789) ashow
+
+% 34 chars in second row: 58-91
+0.3 inch 1.4 inch moveto
+3. 0. (:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[) ashow
+
+% 34 chars in third row: 93-126
+0.3 inch 0.8 inch moveto
+3. 0. (]^_`abcdefghijklmnopqrstuvwxyz{|}~) ashow
+
+showpage
diff --git a/leptonica/prog/fonts/chars-16.tif b/leptonica/prog/fonts/chars-16.tif
new file mode 100644
index 00000000..4d6a5d2f
--- /dev/null
+++ b/leptonica/prog/fonts/chars-16.tif
Binary files differ
diff --git a/leptonica/prog/fonts/chars-18.pa b/leptonica/prog/fonts/chars-18.pa
new file mode 100644
index 00000000..c1941d4d
--- /dev/null
+++ b/leptonica/prog/fonts/chars-18.pa
Binary files differ
diff --git a/leptonica/prog/fonts/chars-18.ps b/leptonica/prog/fonts/chars-18.ps
new file mode 100644
index 00000000..1a6be2c6
--- /dev/null
+++ b/leptonica/prog/fonts/chars-18.ps
@@ -0,0 +1,21 @@
+%!PS
+% chars-18.ps
+
+/inch {72 mul} def
+/Palatino-Roman
+18 72 div inch
+selectfont
+
+% 25 chars in first row: 33-57
+0.3 inch 2.0 inch moveto
+3. 0. (!"#$%&'()*+,-./0123456789) ashow
+
+% 34 chars in second row: 58-91
+0.3 inch 1.4 inch moveto
+3. 0. (:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[) ashow
+
+% 34 chars in third row: 93-126
+0.3 inch 0.8 inch moveto
+3. 0. (]^_`abcdefghijklmnopqrstuvwxyz{|}~) ashow
+
+showpage
diff --git a/leptonica/prog/fonts/chars-18.tif b/leptonica/prog/fonts/chars-18.tif
new file mode 100644
index 00000000..3eb9d9d5
--- /dev/null
+++ b/leptonica/prog/fonts/chars-18.tif
Binary files differ
diff --git a/leptonica/prog/fonts/chars-20.pa b/leptonica/prog/fonts/chars-20.pa
new file mode 100644
index 00000000..63244baa
--- /dev/null
+++ b/leptonica/prog/fonts/chars-20.pa
Binary files differ
diff --git a/leptonica/prog/fonts/chars-20.ps b/leptonica/prog/fonts/chars-20.ps
new file mode 100644
index 00000000..926cd718
--- /dev/null
+++ b/leptonica/prog/fonts/chars-20.ps
@@ -0,0 +1,21 @@
+%!PS
+% chars-20.ps
+
+/inch {72 mul} def
+/Palatino-Roman
+20 72 div inch
+selectfont
+
+% 25 chars in first row: 33-57
+0.3 inch 2.0 inch moveto
+3. 0. (!"#$%&'()*+,-./0123456789) ashow
+
+% 34 chars in second row: 58-91
+0.3 inch 1.4 inch moveto
+3. 0. (:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[) ashow
+
+% 34 chars in third row: 93-126
+0.3 inch 0.8 inch moveto
+3. 0. (]^_`abcdefghijklmnopqrstuvwxyz{|}~) ashow
+
+showpage
diff --git a/leptonica/prog/fonts/chars-20.tif b/leptonica/prog/fonts/chars-20.tif
new file mode 100644
index 00000000..53841dd5
--- /dev/null
+++ b/leptonica/prog/fonts/chars-20.tif
Binary files differ
diff --git a/leptonica/prog/fonts/chars-4.pa b/leptonica/prog/fonts/chars-4.pa
new file mode 100644
index 00000000..e347faaa
--- /dev/null
+++ b/leptonica/prog/fonts/chars-4.pa
Binary files differ
diff --git a/leptonica/prog/fonts/chars-4.ps b/leptonica/prog/fonts/chars-4.ps
new file mode 100644
index 00000000..fe8ea03b
--- /dev/null
+++ b/leptonica/prog/fonts/chars-4.ps
@@ -0,0 +1,21 @@
+%!PS
+% chars-4.ps
+
+/inch {72 mul} def
+/Palatino-Roman
+4 72 div inch
+selectfont
+
+% 25 chars in first row: 33-57
+0.3 inch 2.0 inch moveto
+3. 0. (!"#$%&'()*+,-./0123456789) ashow
+
+% 34 chars in second row: 58-91
+0.3 inch 1.4 inch moveto
+3. 0. (:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[) ashow
+
+% 34 chars in third row: 93-126
+0.3 inch 0.8 inch moveto
+3. 0. (]^_`abcdefghijklmnopqrstuvwxyz{|}~) ashow
+
+showpage
diff --git a/leptonica/prog/fonts/chars-4.tif b/leptonica/prog/fonts/chars-4.tif
new file mode 100644
index 00000000..096caa61
--- /dev/null
+++ b/leptonica/prog/fonts/chars-4.tif
Binary files differ
diff --git a/leptonica/prog/fonts/chars-6.pa b/leptonica/prog/fonts/chars-6.pa
new file mode 100644
index 00000000..d720f517
--- /dev/null
+++ b/leptonica/prog/fonts/chars-6.pa
Binary files differ
diff --git a/leptonica/prog/fonts/chars-6.ps b/leptonica/prog/fonts/chars-6.ps
new file mode 100644
index 00000000..91d170a5
--- /dev/null
+++ b/leptonica/prog/fonts/chars-6.ps
@@ -0,0 +1,21 @@
+%!PS
+% chars-6.ps
+
+/inch {72 mul} def
+/Palatino-Roman
+6 72 div inch
+selectfont
+
+% 25 chars in first row: 33-57
+0.3 inch 2.0 inch moveto
+3. 0. (!"#$%&'()*+,-./0123456789) ashow
+
+% 34 chars in second row: 58-91
+0.3 inch 1.4 inch moveto
+3. 0. (:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[) ashow
+
+% 34 chars in third row: 93-126
+0.3 inch 0.8 inch moveto
+3. 0. (]^_`abcdefghijklmnopqrstuvwxyz{|}~) ashow
+
+showpage
diff --git a/leptonica/prog/fonts/chars-6.tif b/leptonica/prog/fonts/chars-6.tif
new file mode 100644
index 00000000..2839a445
--- /dev/null
+++ b/leptonica/prog/fonts/chars-6.tif
Binary files differ
diff --git a/leptonica/prog/fonts/chars-8.pa b/leptonica/prog/fonts/chars-8.pa
new file mode 100644
index 00000000..fee467f6
--- /dev/null
+++ b/leptonica/prog/fonts/chars-8.pa
Binary files differ
diff --git a/leptonica/prog/fonts/chars-8.ps b/leptonica/prog/fonts/chars-8.ps
new file mode 100644
index 00000000..a6a83b98
--- /dev/null
+++ b/leptonica/prog/fonts/chars-8.ps
@@ -0,0 +1,21 @@
+%!PS
+% chars-8.ps
+
+/inch {72 mul} def
+/Palatino-Roman
+8 72 div inch
+selectfont
+
+% 25 chars in first row: 33-57
+0.3 inch 2.0 inch moveto
+3. 0. (!"#$%&'()*+,-./0123456789) ashow
+
+% 34 chars in second row: 58-91
+0.3 inch 1.4 inch moveto
+3. 0. (:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[) ashow
+
+% 34 chars in third row: 93-126
+0.3 inch 0.8 inch moveto
+3. 0. (]^_`abcdefghijklmnopqrstuvwxyz{|}~) ashow
+
+showpage
diff --git a/leptonica/prog/fonts/chars-8.tif b/leptonica/prog/fonts/chars-8.tif
new file mode 100644
index 00000000..74f35f59
--- /dev/null
+++ b/leptonica/prog/fonts/chars-8.tif
Binary files differ
diff --git a/leptonica/prog/form1.tif b/leptonica/prog/form1.tif
new file mode 100644
index 00000000..0a98e205
--- /dev/null
+++ b/leptonica/prog/form1.tif
Binary files differ
diff --git a/leptonica/prog/form2.tif b/leptonica/prog/form2.tif
new file mode 100644
index 00000000..32893cc5
--- /dev/null
+++ b/leptonica/prog/form2.tif
Binary files differ
diff --git a/leptonica/prog/fpix1_reg.c b/leptonica/prog/fpix1_reg.c
new file mode 100644
index 00000000..1d106a8f
--- /dev/null
+++ b/leptonica/prog/fpix1_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * fpix1_reg.c
+ *
+ * Regression test for a number of functions in the FPix utility.
+ * FPix allows you to do floating point operations such as
+ * convolution, with conversions to and from Pix.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+static void MakePtasAffine(l_int32 i, PTA **pptas, PTA **pptad);
+static void MakePtas(l_int32 i, PTA **pptas, PTA **pptad);
+
+static const l_int32 xs1[] = { 300, 300, 1100, 300, 32};
+static const l_int32 ys1[] = {1200, 1200, 1200, 1250, 934};
+static const l_int32 xs2[] = {1200, 1200, 325, 1300, 487};
+static const l_int32 ys2[] = {1100, 1100, 1200, 1250, 934};
+static const l_int32 xs3[] = { 200, 200, 1200, 250, 32};
+static const l_int32 ys3[] = { 200, 200, 200, 300, 67};
+static const l_int32 xs4[] = {1200, 1200, 1100, 1250, 332};
+static const l_int32 ys4[] = { 400, 200, 200, 300, 57};
+
+static const l_int32 xd1[] = { 300, 300, 1150, 300, 32};
+static const l_int32 yd1[] = {1200, 1400, 1150, 1350, 934};
+static const l_int32 xd2[] = {1100, 1400, 320, 1300, 487};
+static const l_int32 yd2[] = {1000, 1500, 1300, 1200, 904};
+static const l_int32 xd3[] = { 250, 200, 1310, 300, 61};
+static const l_int32 yd3[] = { 200, 300, 250, 325, 83};
+static const l_int32 xd4[] = {1250, 1200, 1140, 1250, 412};
+static const l_int32 yd4[] = { 300, 300, 250, 350, 83};
+
+
+int main(int argc,
+ char **argv)
+{
+l_float32 sum, sumx, sumy, diff;
+L_DEWARP *dew;
+L_DEWARPA *dewa;
+FPIX *fpixs, *fpixs2, *fpixs3, *fpixs4, *fpixg, *fpixd;
+FPIX *fpix1, *fpix2;
+DPIX *dpix, *dpix2;
+L_KERNEL *kel, *kelx, *kely;
+PIX *pixs, *pixs2, *pixs3, *pixd, *pixg, *pixb, *pixn;
+PIX *pix0, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+PIXA *pixa;
+PTA *ptas, *ptad;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "fpix1_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Gaussian kernel */
+ pixa = pixaCreate(0);
+ kel = makeGaussianKernel(5, 5, 3.0, 4.0);
+ kernelGetSum(kel, &sum);
+ if (rp->display) lept_stderr("Sum for 2d gaussian kernel = %f\n", sum);
+ pix0 = kernelDisplayInPix(kel, 41, 2);
+ regTestWritePixAndCheck(rp, pix0, IFF_PNG); /* 0 */
+ pixaAddPix(pixa, pix0, L_INSERT);
+
+ /* Separable gaussian kernel */
+ makeGaussianKernelSep(5, 5, 3.0, 4.0, &kelx, &kely);
+ kernelGetSum(kelx, &sumx);
+ if (rp->display) lept_stderr("Sum for x gaussian kernel = %f\n", sumx);
+ kernelGetSum(kely, &sumy);
+ if (rp->display) lept_stderr("Sum for y gaussian kernel = %f\n", sumy);
+ if (rp->display) lept_stderr("Sum for x * y gaussian kernel = %f\n",
+ sumx * sumy);
+ pix0 = kernelDisplayInPix(kelx, 41, 2);
+ regTestWritePixAndCheck(rp, pix0, IFF_PNG); /* 1 */
+ pixaAddPix(pixa, pix0, L_INSERT);
+ pix0 = kernelDisplayInPix(kely, 41, 2);
+ regTestWritePixAndCheck(rp, pix0, IFF_PNG); /* 2 */
+ pixaAddPix(pixa, pix0, L_INSERT);
+ pix0 = pixaDisplayTiledInColumns(pixa, 4, 1.0, 20, 2);
+ regTestWritePixAndCheck(rp, pix0, IFF_PNG); /* 3 */
+ pixaDestroy(&pixa);
+ pixDestroy(&pix0);
+
+ /* Use pixRasterop() to generate source image */
+ pixa = pixaCreate(0);
+ pixs = pixRead("test8.jpg");
+ pixs2 = pixRead("karen8.jpg");
+ pixRasterop(pixs, 150, 125, 150, 100, PIX_SRC, pixs2, 75, 100);
+ regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 4 */
+
+ /* Convolution directly with pix */
+ pix1 = pixConvolve(pixs, kel, 8, 1);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 5 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixConvolveSep(pixs, kelx, kely, 8, 1);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 6 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ /* Convolution indirectly with fpix, using fpixRasterop()
+ * to generate the source image. */
+ fpixs = pixConvertToFPix(pixs, 3);
+ fpixs2 = pixConvertToFPix(pixs2, 3);
+ fpixRasterop(fpixs, 150, 125, 150, 100, fpixs2, 75, 100);
+ fpix1 = fpixConvolve(fpixs, kel, 1);
+ pix3 = fpixConvertToPix(fpix1, 8, L_CLIP_TO_ZERO, 1);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 7 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ fpix2 = fpixConvolveSep(fpixs, kelx, kely, 1);
+ pix4 = fpixConvertToPix(fpix2, 8, L_CLIP_TO_ZERO, 1);
+ regTestWritePixAndCheck(rp, pix4, IFF_JFIF_JPEG); /* 8 */
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDestroy(&pixs2);
+ fpixDestroy(&fpixs2);
+ fpixDestroy(&fpix1);
+ fpixDestroy(&fpix2);
+
+ /* Comparison of results */
+ if (rp->display) {
+ pixCompareGray(pix1, pix2, L_COMPARE_ABS_DIFF, 0, NULL,
+ &diff, NULL, NULL);
+ lept_stderr("Ave diff of pixConvolve and pixConvolveSep: %f\n", diff);
+ pixCompareGray(pix3, pix4, L_COMPARE_ABS_DIFF, 0, NULL,
+ &diff, NULL, NULL);
+ lept_stderr("Ave diff of fpixConvolve and fpixConvolveSep: %f\n", diff);
+ pixCompareGray(pix1, pix3, L_COMPARE_ABS_DIFF, 0, NULL,
+ &diff, NULL, NULL);
+ lept_stderr("Ave diff of pixConvolve and fpixConvolve: %f\n", diff);
+ }
+ pixCompareGray(pix2, pix4, L_COMPARE_ABS_DIFF, GPLOT_PNG, NULL,
+ &diff, NULL, NULL);
+ lept_stderr("Ave diff of pixConvolveSep and fpixConvolveSep: %f\n", diff);
+ pix5 = pixRead("/tmp/lept/comp/compare_gray0.png");
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 9 */
+ pixaAddPix(pixa, pix5, L_INSERT);
+ pix1 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10 */
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+
+ /* Test arithmetic operations; add in a fraction rotated by 180 */
+ pixa = pixaCreate(0);
+ pixs3 = pixRotate180(NULL, pixs);
+ regTestWritePixAndCheck(rp, pixs3, IFF_JFIF_JPEG); /* 11 */
+ pixaAddPix(pixa, pixs3, L_INSERT);
+ fpixs3 = pixConvertToFPix(pixs3, 3);
+ fpixd = fpixLinearCombination(NULL, fpixs, fpixs3, 20.0, 5.0);
+ fpixAddMultConstant(fpixd, 0.0, 23.174); /* multiply up in magnitude */
+ pixd = fpixDisplayMaxDynamicRange(fpixd); /* bring back to 8 bpp */
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 12 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ fpixDestroy(&fpixs3);
+ fpixDestroy(&fpixd);
+ pixDestroy(&pixs);
+ fpixDestroy(&fpixs);
+
+ /* Display results */
+ pixd = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 2);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 13 */
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ /* Test some more convolutions, with sampled output. First on pix */
+ pixa = pixaCreate(0);
+ pixs = pixRead("1555.007.jpg");
+ pixg = pixConvertTo8(pixs, 0);
+ l_setConvolveSampling(5, 5);
+ pix1 = pixConvolve(pixg, kel, 8, 1);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 14 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixConvolveSep(pixg, kelx, kely, 8, 1);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 15 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix3 = pixConvolveRGB(pixs, kel);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 16 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix4 = pixConvolveRGBSep(pixs, kelx, kely);
+ regTestWritePixAndCheck(rp, pix4, IFF_JFIF_JPEG); /* 17 */
+ pixaAddPix(pixa, pix4, L_INSERT);
+
+ /* Then on fpix */
+ fpixg = pixConvertToFPix(pixg, 1);
+ fpix1 = fpixConvolve(fpixg, kel, 1);
+ pix5 = fpixConvertToPix(fpix1, 8, L_CLIP_TO_ZERO, 0);
+ regTestWritePixAndCheck(rp, pix5, IFF_JFIF_JPEG); /* 18 */
+ pixaAddPix(pixa, pix5, L_INSERT);
+ fpix2 = fpixConvolveSep(fpixg, kelx, kely, 1);
+ pix6 = fpixConvertToPix(fpix2, 8, L_CLIP_TO_ZERO, 0);
+ regTestWritePixAndCheck(rp, pix6, IFF_JFIF_JPEG); /* 19 */
+ pixaAddPix(pixa, pix6, L_INSERT);
+ regTestCompareSimilarPix(rp, pix1, pix5, 2, 0.00, 0); /* 20 */
+ regTestCompareSimilarPix(rp, pix2, pix6, 2, 0.00, 0); /* 21 */
+ fpixDestroy(&fpixg);
+ fpixDestroy(&fpix1);
+ fpixDestroy(&fpix2);
+
+ pixd = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 2);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 22 */
+ pixDisplayWithTitle(pixd, 600, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pixg);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ /* Test extension (continued and slope).
+ * First, build a smooth vertical disparity array;
+ * then extend and show the contours. */
+ pixs = pixRead("cat.035.jpg");
+ pixn = pixBackgroundNormSimple(pixs, NULL, NULL);
+ pixg = pixConvertRGBToGray(pixn, 0.5, 0.3, 0.2);
+ pixb = pixThresholdToBinary(pixg, 130);
+ dewa = dewarpaCreate(1, 30, 1, 15, 0);
+ if ((dew = dewarpCreate(pixb, 35)) == NULL) {
+ rp->success = FALSE;
+ L_ERROR("dew not made; tests 21-28 skipped (failed)\n", "fpix1_reg");
+ return regTestCleanup(rp);
+ }
+ dewarpaInsertDewarp(dewa, dew);
+ dewarpBuildPageModel(dew, NULL); /* two invalid indices in ptaGetPt() */
+ dewarpPopulateFullRes(dew, NULL, 0, 0);
+ fpixs = dew->fullvdispar;
+ fpixs2 = fpixAddContinuedBorder(fpixs, 200, 200, 100, 300);
+ fpixs3 = fpixAddSlopeBorder(fpixs, 200, 200, 100, 300);
+ dpix = fpixConvertToDPix(fpixs3);
+ fpixs4 = dpixConvertToFPix(dpix);
+ pix1 = fpixRenderContours(fpixs, 2.0, 0.2);
+ pix2 = fpixRenderContours(fpixs2, 2.0, 0.2);
+ pix3 = fpixRenderContours(fpixs3, 2.0, 0.2);
+ pix4 = fpixRenderContours(fpixs4, 2.0, 0.2);
+ pix5 = pixRead("karen8.jpg");
+ dpix2 = pixConvertToDPix(pix5, 1);
+ pix6 = dpixConvertToPix(dpix2, 8, L_CLIP_TO_ZERO, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 23 */
+ pixDisplayWithTitle(pix1, 0, 100, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 24 */
+ pixDisplayWithTitle(pix2, 470, 100, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 25 */
+ pixDisplayWithTitle(pix3, 1035, 100, NULL, rp->display);
+ regTestComparePix(rp, pix3, pix4); /* 26 */
+ regTestComparePix(rp, pix5, pix6); /* 27 */
+ pixDestroy(&pixs);
+ pixDestroy(&pixn);
+ pixDestroy(&pixg);
+ pixDestroy(&pixb);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ fpixDestroy(&fpixs2);
+ fpixDestroy(&fpixs3);
+ fpixDestroy(&fpixs4);
+ dpixDestroy(&dpix);
+ dpixDestroy(&dpix2);
+
+ /* Test affine and projective transforms on fpix */
+ fpixWrite("/tmp/lept/regout/fpix1.fp", dew->fullvdispar);
+ fpix1 = fpixRead("/tmp/lept/regout/fpix1.fp");
+ pix1 = fpixAutoRenderContours(fpix1, 40);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 28 */
+ pixDisplayWithTitle(pix1, 0, 500, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ MakePtasAffine(1, &ptas, &ptad);
+ fpix2 = fpixAffinePta(fpix1, ptad, ptas, 200, 0.0);
+ pix2 = fpixAutoRenderContours(fpix2, 40);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 29 */
+ pixDisplayWithTitle(pix2, 400, 500, NULL, rp->display);
+ fpixDestroy(&fpix2);
+ pixDestroy(&pix2);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+
+ MakePtas(1, &ptas, &ptad);
+ fpix2 = fpixProjectivePta(fpix1, ptad, ptas, 200, 0.0);
+ pix3 = fpixAutoRenderContours(fpix2, 40);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 30 */
+ pixDisplayWithTitle(pix3, 400, 500, NULL, rp->display);
+ fpixDestroy(&fpix1);
+ fpixDestroy(&fpix2);
+ pixDestroy(&pix3);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ dewarpaDestroy(&dewa);
+
+ kernelDestroy(&kel);
+ kernelDestroy(&kelx);
+ kernelDestroy(&kely);
+ return regTestCleanup(rp);
+}
+
+
+static void
+MakePtas(l_int32 i,
+ PTA **pptas,
+ PTA **pptad)
+{
+PTA *ptas, *ptad;
+ ptas = ptaCreate(4);
+ ptaAddPt(ptas, xs1[i], ys1[i]);
+ ptaAddPt(ptas, xs2[i], ys2[i]);
+ ptaAddPt(ptas, xs3[i], ys3[i]);
+ ptaAddPt(ptas, xs4[i], ys4[i]);
+ ptad = ptaCreate(4);
+ ptaAddPt(ptad, xd1[i], yd1[i]);
+ ptaAddPt(ptad, xd2[i], yd2[i]);
+ ptaAddPt(ptad, xd3[i], yd3[i]);
+ ptaAddPt(ptad, xd4[i], yd4[i]);
+ *pptas = ptas;
+ *pptad = ptad;
+ return;
+}
+
+static void
+MakePtasAffine(l_int32 i,
+ PTA **pptas,
+ PTA **pptad)
+{
+PTA *ptas, *ptad;
+ ptas = ptaCreate(3);
+ ptaAddPt(ptas, xs1[i], ys1[i]);
+ ptaAddPt(ptas, xs2[i], ys2[i]);
+ ptaAddPt(ptas, xs3[i], ys3[i]);
+ ptad = ptaCreate(3);
+ ptaAddPt(ptad, xd1[i], yd1[i]);
+ ptaAddPt(ptad, xd2[i], yd2[i]);
+ ptaAddPt(ptad, xd3[i], yd3[i]);
+ *pptas = ptas;
+ *pptad = ptad;
+ return;
+}
diff --git a/leptonica/prog/fpix2_reg.c b/leptonica/prog/fpix2_reg.c
new file mode 100644
index 00000000..5ceafbcb
--- /dev/null
+++ b/leptonica/prog/fpix2_reg.c
@@ -0,0 +1,116 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Regression test for FPix:
+ * - rotation by multiples of 90 degrees
+ * - adding borders of various types
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+FPIX *fpix1, *fpix2, *fpix3, *fpix4;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Test orthogonal rotations */
+ pix1 = pixRead("marge.jpg");
+ pix2 = pixConvertTo8(pix1, 0);
+ fpix1 = pixConvertToFPix(pix2, 1);
+
+ fpix2 = fpixRotateOrth(fpix1, 1);
+ pix3 = fpixConvertToPix(fpix2, 8, L_CLIP_TO_ZERO, 0);
+ pix4 = pixRotateOrth(pix2, 1);
+ regTestComparePix(rp, pix3, pix4); /* 0 */
+ pixDisplayWithTitle(pix3, 100, 100, NULL, rp->display);
+
+ fpix3 = fpixRotateOrth(fpix1, 2);
+ pix5 = fpixConvertToPix(fpix3, 8, L_CLIP_TO_ZERO, 0);
+ pix6 = pixRotateOrth(pix2, 2);
+ regTestComparePix(rp, pix5, pix6); /* 1 */
+ pixDisplayWithTitle(pix5, 560, 100, NULL, rp->display);
+
+ fpix4 = fpixRotateOrth(fpix1, 3);
+ pix7 = fpixConvertToPix(fpix4, 8, L_CLIP_TO_ZERO, 0);
+ pix8 = pixRotateOrth(pix2, 3);
+ regTestComparePix(rp, pix7, pix8); /* 2 */
+ pixDisplayWithTitle(pix7, 1170, 100, NULL, rp->display);
+ pixDisplayWithTitle(pix2, 560, 580, NULL, rp->display);
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ fpixDestroy(&fpix1);
+ fpixDestroy(&fpix2);
+ fpixDestroy(&fpix3);
+ fpixDestroy(&fpix4);
+
+ /* Test adding various borders */
+ pix1 = pixRead("marge.jpg");
+ pix2 = pixConvertTo8(pix1, 0);
+ fpix1 = pixConvertToFPix(pix2, 1);
+
+ fpix2 = fpixAddMirroredBorder(fpix1, 21, 21, 25, 25);
+ pix3 = fpixConvertToPix(fpix2, 8, L_CLIP_TO_ZERO, 0);
+ pix4 = pixAddMirroredBorder(pix2, 21, 21, 25, 25);
+ regTestComparePix(rp, pix3, pix4); /* 3 */
+ pixDisplayWithTitle(pix3, 100, 1000, NULL, rp->display);
+
+ fpix3 = fpixAddContinuedBorder(fpix1, 21, 21, 25, 25);
+ pix5 = fpixConvertToPix(fpix3, 8, L_CLIP_TO_ZERO, 0);
+ pix6 = pixAddContinuedBorder(pix2, 21, 21, 25, 25);
+ regTestComparePix(rp, pix5, pix6); /* 4 */
+ pixDisplayWithTitle(pix5, 750, 1000, NULL, rp->display);
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ fpixDestroy(&fpix1);
+ fpixDestroy(&fpix2);
+ fpixDestroy(&fpix3);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/fpixcontours.c b/leptonica/prog/fpixcontours.c
new file mode 100644
index 00000000..9bac599a
--- /dev/null
+++ b/leptonica/prog/fpixcontours.c
@@ -0,0 +1,76 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * fpixcontours.c
+ *
+ * Generates and displays an fpix as a set of contours
+ *
+ * Syntax: fpixcontours filein [ncontours]
+ * Default for ncontours is 40.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const char *fileout = "/tmp/lept/fpix/fpixcontours.png";
+
+int main(int argc,
+ char **argv)
+{
+char *filein;
+l_int32 ncontours;
+FPIX *fpix;
+PIX *pix;
+static char mainName[] = "fpixcontours";
+
+ if (argc != 2 && argc != 3) {
+ lept_stderr("Syntax: fpixcontours filein [ncontours]\n");
+ return 1;
+ }
+ filein = argv[1];
+ if (argc == 2)
+ ncontours = 40;
+ else /* argc == 3 */
+ ncontours = atoi(argv[2]);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/fpix");
+
+ if ((fpix = fpixRead(filein)) == NULL)
+ return ERROR_INT(mainName, "fpix not read", 1);
+ pix = fpixAutoRenderContours(fpix, ncontours);
+ pixWrite(fileout, pix, IFF_PNG);
+ pixDisplay(pix, 100, 100);
+
+ pixDestroy(&pix);
+ fpixDestroy(&fpix);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/README.md b/leptonica/prog/fuzzing/README.md
new file mode 100644
index 00000000..4dcddee8
--- /dev/null
+++ b/leptonica/prog/fuzzing/README.md
@@ -0,0 +1,25 @@
+# Leptonica fuzzing
+
+This directory contains fuzzing tests for Leptonica.
+Each test is in a separate source file *_fuzzer.cc.
+
+Normally these fuzzing tests are run by [OSS-Fuzz](https://oss-fuzz.com/),
+but can also be run locally.
+
+## Local build instructions
+
+Local builds require the clang compiler.
+The example was tested with clang-6.0 on Debian GNU Linux.
+
+ ./configure CC=clang-6.0 CXX=clang++-6.0
+ make fuzzers CXX=clang++-6.0
+
+## Running local fuzzers
+
+Each local fuzzer can be run like in the following example.
+
+ # Show command line syntax.
+ ./barcode_fuzzer -help=1
+
+ # Run the fuzzer.
+ ./barcode_fuzzer
diff --git a/leptonica/prog/fuzzing/adaptmap_fuzzer.cc b/leptonica/prog/fuzzing/adaptmap_fuzzer.cc
new file mode 100644
index 00000000..29df2e5b
--- /dev/null
+++ b/leptonica/prog/fuzzing/adaptmap_fuzzer.cc
@@ -0,0 +1,75 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ PIX *pix1, *pix2, *pix3, *pix4, *pix5, *return_pix1, *payload_copy;
+
+ pix1 = pixRead("../test8.jpg");
+ payload_copy = pixCopy(NULL, pixs_payload);
+ pixBackgroundNormGrayArray(payload_copy, pix1, 10, 10, 10, 10, 256, 10, 10, &pix2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&payload_copy);
+
+ pix1 = pixRead("../test8.jpg");
+ payload_copy = pixCopy(NULL, pixs_payload);
+ pixBackgroundNormGrayArrayMorph(payload_copy, pix1, 6, 5, 256, &pix2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&payload_copy);
+
+ pix1 = pixRead("../test8.jpg");
+ payload_copy = pixCopy(NULL, pixs_payload);
+ return_pix1 = pixBackgroundNormMorph(payload_copy, pix1, 6, 5, 256);
+ pixDestroy(&pix1);
+ pixDestroy(&payload_copy);
+ pixDestroy(&return_pix1);
+
+ pix1 = pixRead("../test8.jpg");
+ pix2 = pixRead("../test8.jpg");
+ payload_copy = pixCopy(NULL, pixs_payload);
+ pixBackgroundNormRGBArrays(payload_copy, pix1, pix2, 10, 10, 10, 10, 130, 10, 10, &pix3, &pix4, &pix5);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&payload_copy);
+
+ pix1 = pixRead("../test8.jpg");
+ payload_copy = pixCopy(NULL, pixs_payload);
+ pixBackgroundNormRGBArraysMorph(payload_copy, pix1, 6, 33, 130, &pix2, &pix3, &pix4);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&payload_copy);
+
+ payload_copy = pixCopy(NULL, pixs_payload);
+ pixContrastNorm(payload_copy, payload_copy, 10, 10, 3, 0, 0);
+ pixDestroy(&payload_copy);
+
+ pix1 = pixRead("../test8.jpg");
+ payload_copy = pixCopy(NULL, pixs_payload);
+ return_pix1 = pixGlobalNormNoSatRGB(payload_copy, pix1, 3, 3, 3, 2, 0.9);
+ pixDestroy(&pix1);
+ pixDestroy(&payload_copy);
+ pixDestroy(&return_pix1);
+
+ payload_copy = pixCopy(NULL, pixs_payload);
+ pixThresholdSpreadNorm(payload_copy, L_SOBEL_EDGE, 10, 0, 0, 0.7, -25, 255, 10, &pix1, &pix2, &pix3);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&payload_copy);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/affine_fuzzer.cc b/leptonica/prog/fuzzing/affine_fuzzer.cc
new file mode 100644
index 00000000..ab57c3e1
--- /dev/null
+++ b/leptonica/prog/fuzzing/affine_fuzzer.cc
@@ -0,0 +1,40 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ PIX *pix1, *return_pix1;
+ PTA *ptas, *ptad;
+
+ ptas = ptaCreate(0);
+ ptad = ptaCreate(0);
+ return_pix1 = pixAffinePta(pixs_payload, ptad, ptas, L_BRING_IN_WHITE);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ pixDestroy(&return_pix1);
+
+ pix1 = pixRead("../test8.jpg");
+ ptas = ptaCreate(0);
+ ptad = ptaCreate(0);
+ return_pix1 = pixAffinePtaWithAlpha(pixs_payload, ptad, ptas, pix1, 0.9, 1);
+ pixDestroy(&pix1);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ pixDestroy(&return_pix1);
+
+ ptas = ptaCreate(0);
+ ptad = ptaCreate(0);
+ return_pix1 = pixAffineSequential(pixs_payload, ptad, ptas, 3, 3);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ pixDestroy(&return_pix1);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/barcode_fuzzer.cc b/leptonica/prog/fuzzing/barcode_fuzzer.cc
new file mode 100644
index 00000000..d4dc01ec
--- /dev/null
+++ b/leptonica/prog/fuzzing/barcode_fuzzer.cc
@@ -0,0 +1,22 @@
+#include "leptfuzz.h"
+#include "readbarcode.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ if(size<3) return 0;
+ PIX *pixs;
+ SARRAY *saw1, *sad1;
+
+ leptSetStdNullHandler();
+
+ pixs = pixReadMemSpix(data, size);
+ if(pixs == NULL) return 0;
+
+ sad1 = pixProcessBarcodes(pixs, L_BF_ANY, L_USE_WIDTHS, &saw1, 1);
+
+ pixDestroy(&pixs);
+ sarrayDestroy(&saw1);
+ sarrayDestroy(&sad1);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/barcode_fuzzer_seed_corpus.zip b/leptonica/prog/fuzzing/barcode_fuzzer_seed_corpus.zip
new file mode 100644
index 00000000..81199be2
--- /dev/null
+++ b/leptonica/prog/fuzzing/barcode_fuzzer_seed_corpus.zip
Binary files differ
diff --git a/leptonica/prog/fuzzing/baseline_fuzzer.cc b/leptonica/prog/fuzzing/baseline_fuzzer.cc
new file mode 100644
index 00000000..429f8bff
--- /dev/null
+++ b/leptonica/prog/fuzzing/baseline_fuzzer.cc
@@ -0,0 +1,24 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ PIX *pix1 = pixDeskewLocal(pixs_payload, 10, 0, 0, 0.0, 0.0, 0.0);
+ pixDestroy(&pix1);
+
+ PTA *pta;
+ PIXA *pixadb = pixaCreate(6);
+ NUMA *na = pixFindBaselines(pixs_payload, &pta, pixadb);
+ numaDestroy(&na);
+ ptaDestroy(&pta);
+ pixaDestroy(&pixadb);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/bilateral_fuzzer.cc b/leptonica/prog/fuzzing/bilateral_fuzzer.cc
new file mode 100644
index 00000000..66d535b0
--- /dev/null
+++ b/leptonica/prog/fuzzing/bilateral_fuzzer.cc
@@ -0,0 +1,25 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+ PIX *return_pix1, *pix_copy;
+
+ pix_copy = pixCopy(NULL, pixs_payload);
+ return_pix1 = pixBilateral(pix_copy, 5.0, 10.0, 10, 1);
+ pixDestroy(&pix_copy);
+ pixDestroy(&return_pix1);
+
+ pix_copy = pixCopy(NULL, pixs_payload);
+ return_pix1 = pixBlockBilateralExact(pixs_payload, 10.0, 1.0);
+ pixDestroy(&pix_copy);
+ pixDestroy(&return_pix1);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/bilinear_fuzzer.cc b/leptonica/prog/fuzzing/bilinear_fuzzer.cc
new file mode 100644
index 00000000..f4fc4269
--- /dev/null
+++ b/leptonica/prog/fuzzing/bilinear_fuzzer.cc
@@ -0,0 +1,37 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ PIX *pix1, *return_pix1, *pix_copy;
+ PTA *ptas, *ptad;
+
+ ptas = ptaCreate(0);
+ ptad = ptaCreate(0);
+ pix_copy = pixCopy(NULL, pixs_payload);
+ return_pix1 = pixBilinearPta(pix_copy, ptad, ptas, L_BRING_IN_WHITE);
+ pixDestroy(&pix_copy);
+ pixDestroy(&return_pix1);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+
+ pix1 = pixRead("../test8.jpg");
+ ptas = ptaCreate(0);
+ ptad = ptaCreate(0);
+ pix_copy = pixCopy(NULL, pixs_payload);
+ return_pix1 = pixBilinearPtaWithAlpha(pix_copy, ptad, ptas, pix1, 0.5, 2);
+ pixDestroy(&pix_copy);
+ pixDestroy(&pix1);
+ pixDestroy(&return_pix1);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/binarize_fuzzer.cc b/leptonica/prog/fuzzing/binarize_fuzzer.cc
new file mode 100644
index 00000000..50d5b04e
--- /dev/null
+++ b/leptonica/prog/fuzzing/binarize_fuzzer.cc
@@ -0,0 +1,55 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ PIX *pix1, *pix2, *return_pix, *pix_copy1;
+ l_int32 l_i;
+
+ pix1 = pixRead("../test8.jpg");
+ pix_copy1 = pixCopy(NULL, pixs_payload);
+ return_pix = pixMaskedThreshOnBackgroundNorm(pix_copy1, pix1,
+ 100, 100, 10, 10,
+ 10, 10, 0.1, &l_i);
+ pixDestroy(&pix1);
+ pixDestroy(&pix_copy1);
+ pixDestroy(&return_pix);
+
+ pix1 = pixRead("../test8.jpg");
+ pix_copy1 = pixCopy(NULL, pixs_payload);
+ return_pix = pixOtsuThreshOnBackgroundNorm(pix_copy1, pix1,
+ 100, 100, 10, 10,
+ 130, 30, 30, 0.1,
+ &l_i);
+ pixDestroy(&pix1);
+ pixDestroy(&pix_copy1);
+ pixDestroy(&return_pix);
+
+ pix_copy1 = pixCopy(NULL, pixs_payload);
+ pixSauvolaBinarizeTiled(pix_copy1, 8, 0.34, 1, 1, NULL, &pix1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix_copy1);
+
+ pix1 = pixRead("../test8.jpg");
+ pix_copy1 = pixCopy(NULL, pixs_payload);
+ pixThresholdByConnComp(pix_copy1, pix1, 10, 10, 10, 5.5, 5.5,
+ &l_i, &pix2, 1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix_copy1);
+
+ pix_copy1 = pixCopy(NULL, pixs_payload);
+ pixThresholdByHisto(pix_copy1, 2, 0, 0, &l_i, &pix1, &pix2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix_copy1);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/blend_fuzzer.cc b/leptonica/prog/fuzzing/blend_fuzzer.cc
new file mode 100644
index 00000000..17b1af7d
--- /dev/null
+++ b/leptonica/prog/fuzzing/blend_fuzzer.cc
@@ -0,0 +1,93 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ PIX *pix1, *pix2, *return_pix, *pix_copy;
+
+ for(int i=0; i<10; i++) {
+ pix1 = pixRead("../test8.jpg");
+ pix_copy = pixCopy(NULL, pixs_payload);
+ return_pix = pixBlend(pix_copy, pix1, i, i, i);
+ pixDestroy(&pix_copy);
+ pixDestroy(&pix1);
+ pixDestroy(&return_pix);
+
+ pix_copy = pixCopy(NULL, pixs_payload);
+ return_pix = pixBlend(pix_copy, pix_copy, i, i, i);
+ pixDestroy(&pix_copy);
+ pixDestroy(&return_pix);
+ }
+
+ pix_copy = pixCopy(NULL, pixs_payload);
+ return_pix = pixAddAlphaToBlend(pix_copy, 1.2, 1);
+ pixDestroy(&pix_copy);
+ pixDestroy(&return_pix);
+
+ pix1 = pixRead("../test8.jpg");
+ BOX *box1 = boxCreate(150, 130, 1500, 355);
+ pix_copy = pixCopy(NULL, pixs_payload);
+ pixBlendBackgroundToColor(pix_copy, pix1, box1, 123, 1.0, 5, 12);
+ pixDestroy(&pix1);
+ boxDestroy(&box1);
+ pixDestroy(&pix_copy);
+
+ pix1 = pixRead("../test8.jpg");
+ pix_copy = pixCopy(NULL, pixs_payload);
+ pixBlendCmap(pix_copy, pix1, 2, 3, 4);
+ pixDestroy(&pix1);
+ pixDestroy(&pix_copy);
+
+ pix1 = pixRead("../test8.jpg");
+ pix_copy = pixCopy(NULL, pixs_payload);
+ pixBlendColorByChannel(pix_copy, pix_copy, pix1, 200, 200, 0.7, 0.8, 0.9, 1, 5);
+ pixDestroy(&pix1);
+ pixDestroy(&pix_copy);
+
+ pix1 = pixRead("../test8.jpg");
+ pix_copy = pixCopy(NULL, pixs_payload);
+ pixBlendGrayAdapt(pix_copy, pix_copy, pix1, 2, 3, 0.8, 1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix_copy);
+
+ pix1 = pixRead("../test8.jpg");
+ pix_copy = pixCopy(NULL, pixs_payload);
+ pixBlendGrayInverse(pix_copy, pix_copy, pix1, 1, 2, 0.7);
+ pixDestroy(&pix1);
+ pixDestroy(&pix_copy);
+
+ pix1 = pixRead("../test8.jpg");
+ pix_copy = pixCopy(NULL, pixs_payload);
+ pixBlendHardLight(pix_copy, pix_copy, pix1, 1, 2, 0.8);
+ pixDestroy(&pix1);
+ pixDestroy(&pix_copy);
+
+ pix1 = pixRead("../test8.jpg");
+ pix_copy = pixCopy(NULL, pixs_payload);
+ return_pix = pixFadeWithGray(pix_copy, pix1, 1.0, L_BLEND_TO_WHITE);
+ pixDestroy(&pix1);
+ pixDestroy(&pix_copy);
+ pixDestroy(&return_pix);
+
+ pix_copy = pixCopy(NULL, pixs_payload);
+ pixLinearEdgeFade(pix_copy, L_FROM_LEFT, L_BLEND_TO_WHITE, 1.0, 0.8);
+ pixDestroy(&pix_copy);
+
+ pix_copy = pixCopy(NULL, pixs_payload);
+ pixMultiplyByColor(pix_copy, pix_copy, NULL, 2);
+ pixDestroy(&pix_copy);
+
+ pix_copy = pixCopy(NULL, pixs_payload);
+ return_pix = pixSetAlphaOverWhite(pix_copy);
+ pixDestroy(&pix_copy);
+ pixDestroy(&return_pix);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/boxfunc3_fuzzer.cc b/leptonica/prog/fuzzing/boxfunc3_fuzzer.cc
new file mode 100644
index 00000000..c43a2de0
--- /dev/null
+++ b/leptonica/prog/fuzzing/boxfunc3_fuzzer.cc
@@ -0,0 +1,107 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ BOXA *boxa_payload, *boxa1;
+ boxa_payload = boxaReadMem(data, size);
+ if(boxa_payload == NULL) return 0;
+
+ PIX *pixc, *pixd, *pix, *pixs;
+ PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
+ BOXAA *baa;
+ static const l_int32 WIDTH = 800;
+
+ //boxaaDisplay()
+ pix1 = pixRead("../test8.jpg");
+ if(pix1!=NULL) {
+ baa = boxaSort2d(boxa_payload, NULL, 6, 6, 5);
+ pix2 = boxaaDisplay(pix1, baa, 3, 1, 0xff000000,
+ 0x00ff0000, 0, 0);
+ boxaaDestroy(&baa);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+
+ //pixBlendBoxaRandom();
+ pixc = pixRead("../test8.jpg");
+ if(pixc!=NULL) {
+ pixd = pixBlendBoxaRandom(pixc, boxa_payload, 0.4);
+ pixDestroy(&pixc);
+ pixDestroy(&pixd);
+ }
+
+ //pixDrawBoxa();
+ pixc = pixRead("../test8.jpg");
+ if(pixc!=NULL) {
+ pixd = pixConvertTo1(pixc, 128);
+ pix1 = pixConvertTo8(pixd, FALSE);
+ pix2 = pixDrawBoxa(pix1, boxa_payload, 7, 0x40a0c000);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixc);
+ pixDestroy(&pixd);
+ }
+
+ //pixMaskConnComp();
+ pix1 = pixRead("../test8.jpg");
+ if(pix1!=NULL) {
+ boxa1 = boxaReadMem(data, size);
+ if(boxa1==NULL) {
+ pixDestroy(&pix1);
+ }else{
+ pix2 = pixScaleToSize(pix1, WIDTH, 0);
+ pix3 = pixConvertTo1(pix2, 100);
+ pix4 = pixExpandBinaryPower2(pix3, 2);
+ pix5 = pixGenerateHalftoneMask(pix4, NULL, NULL, NULL);
+ pix6 = pixMorphSequence(pix5, "c20.1 + c1.20", 0);
+ pix7 = pixMaskConnComp(pix6, 8, &boxa1);
+ boxaDestroy(&boxa1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ }
+ }
+
+ //pixPaintBoxa();
+ pix = pixRead("../test8.jpg");
+ if(pix!=NULL) {
+ boxa1 = boxaReadMem(data, size);
+ if(boxa1==NULL) {
+ pixDestroy(&pix);
+ }else{
+ pix1 = pixPaintBoxa(pix, boxa1, 0x60e0a000);
+ pixDestroy(&pix);
+ pixDestroy(&pix1);
+ boxaDestroy(&boxa1);
+ };
+
+ }
+
+
+ //pixPaintBoxaRandom();
+ pix = pixRead("../test8.jpg");
+ if(pix!=NULL) {
+ boxa1 = boxaReadMem(data, size);
+ if(boxa1==NULL) {
+ pixDestroy(&pix);
+ }else{
+ pixs = pixConvertTo1(pix, 128);
+ pix1 = pixPaintBoxaRandom(pixs, boxa1);
+ pixDestroy(&pix);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ boxaDestroy(&boxa1);
+ }
+ }
+
+ boxaDestroy(&boxa_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/boxfunc4_fuzzer.cc b/leptonica/prog/fuzzing/boxfunc4_fuzzer.cc
new file mode 100644
index 00000000..796ad93d
--- /dev/null
+++ b/leptonica/prog/fuzzing/boxfunc4_fuzzer.cc
@@ -0,0 +1,39 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ BOXA *boxa_payload, *boxa1;
+ boxa_payload = boxaReadMem(data, size);
+ if(boxa_payload == NULL) return 0;
+
+ PIX *pix1;
+ l_float32 fract;
+ l_int32 minx, miny, maxx, maxy, changed;
+
+ pix1 = boxaDisplayTiled(boxa_payload, NULL, 0, -1, 1500,
+ 2, 1.0, 0, 3, 2);
+ pixDestroy(&pix1);
+
+ boxaGetCoverage(boxa_payload, 0, 0, 0, &fract);
+
+ boxaLocationRange(boxa_payload, &minx, &miny, &maxx, &maxy);
+
+ boxa1 = boxaPermutePseudorandom(boxa_payload);
+ boxaDestroy(&boxa1);
+
+ boxaPermuteRandom(boxa_payload, boxa_payload);
+
+ boxa1 = boxaSelectByWHRatio(boxa_payload, 1,
+ L_SELECT_IF_LT, &changed);
+ boxaDestroy(&boxa1);
+
+ boxa1 = boxaSelectRange(boxa_payload, 0, -1, L_COPY);
+ boxaDestroy(&boxa1);
+
+ boxaDestroy(&boxa_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/boxfunc5_fuzzer.cc b/leptonica/prog/fuzzing/boxfunc5_fuzzer.cc
new file mode 100644
index 00000000..e85f306d
--- /dev/null
+++ b/leptonica/prog/fuzzing/boxfunc5_fuzzer.cc
@@ -0,0 +1,51 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ BOXA *boxa_payload, *boxa1;
+ boxa_payload = boxaReadMem(data, size);
+ if(boxa_payload == NULL) return 0;
+
+ l_float32 fvarp, fvarm, devw, devh;
+ l_float32 del_evenodd, rms_even, rms_odd, rms_all;
+ l_int32 isame;
+
+ boxa1 = boxaConstrainSize(boxa_payload, 0,
+ L_ADJUST_LEFT_AND_RIGHT,
+ 0, L_ADJUST_TOP_AND_BOT);
+ boxaDestroy(&boxa1);
+
+ boxa1 = boxaReconcileAllByMedian(boxa_payload,
+ L_ADJUST_LEFT_AND_RIGHT,
+ L_ADJUST_TOP_AND_BOT, 50,
+ 0, NULL);
+ boxaDestroy(&boxa1);
+
+ boxa1 = boxaReconcileEvenOddHeight(boxa_payload, L_ADJUST_TOP, 80,
+ L_ADJUST_CHOOSE_MIN, 1.05, 1);
+ boxaDestroy(&boxa1);
+
+ boxa1 = boxaReconcilePairWidth(boxa_payload, 2,
+ L_ADJUST_CHOOSE_MIN,
+ 0.5, NULL);
+ boxaDestroy(&boxa1);
+
+ boxaSizeConsistency1(boxa_payload, L_CHECK_HEIGHT,
+ 0.0, 0.0, &fvarp, &fvarm, &isame);
+
+ boxaSizeConsistency2(boxa_payload, &devw, &devh, 0);
+
+ boxaSizeVariation(boxa_payload, L_SELECT_WIDTH, &del_evenodd,
+ &rms_even, &rms_odd, &rms_all);
+
+ boxa1 = boxaSmoothSequenceMedian(boxa_payload, 10,
+ L_SUB_ON_LOC_DIFF,
+ 80, 20, 1);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/boxfunc_fuzzer.cc b/leptonica/prog/fuzzing/boxfunc_fuzzer.cc
new file mode 100644
index 00000000..7c3d1c11
--- /dev/null
+++ b/leptonica/prog/fuzzing/boxfunc_fuzzer.cc
@@ -0,0 +1,23 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+ BOXA *boxa1, *boxa2;
+ boxa1 = boxaReadMem(data, size);
+ if(boxa1==NULL) return 0;
+
+ boxa2 = boxaReconcileAllByMedian(boxa1, L_ADJUST_LEFT_AND_RIGHT,
+ L_ADJUST_TOP_AND_BOT, 50, 0, NULL);
+ if(boxa2!=NULL) boxaDestroy(&boxa2);
+
+ boxa2 = boxaReconcileAllByMedian(boxa1, L_ADJUST_SKIP,
+ L_ADJUST_TOP_AND_BOT, 50, 0, NULL);
+ if(boxa2!=NULL) boxaDestroy(&boxa2);
+ boxaDestroy(&boxa1);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/ccbord_fuzzer.cc b/leptonica/prog/fuzzing/ccbord_fuzzer.cc
new file mode 100644
index 00000000..f1b0e19a
--- /dev/null
+++ b/leptonica/prog/fuzzing/ccbord_fuzzer.cc
@@ -0,0 +1,27 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs, *pixc;
+ CCBORDA *ccba;
+
+ pixs = pixReadMemSpix(data, size);
+ if(pixs==NULL) return 0;
+
+ ccba = pixGetAllCCBorders(pixs);
+
+ ccbaStepChainsToPixCoords(ccba, CCB_GLOBAL_COORDS);
+ ccbaGenerateSPGlobalLocs(ccba, CCB_SAVE_TURNING_PTS);
+ pixc = ccbaDisplayImage2(ccba);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixc);
+ ccbaDestroy(&ccba);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/ccthin_fuzzer.cc b/leptonica/prog/fuzzing/ccthin_fuzzer.cc
new file mode 100644
index 00000000..08eb7e0a
--- /dev/null
+++ b/leptonica/prog/fuzzing/ccthin_fuzzer.cc
@@ -0,0 +1,28 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ PIXA *pixa, *return_pixa;
+ PIX *pixs;
+
+ leptSetStdNullHandler();
+ pixs = pixReadMemSpix(data, size);
+ if(pixs==NULL) return 0;
+
+ for(int i=0; i<10; i++) {
+ pixa = pixaReadMem(data, size);
+ return_pixa = pixaThinConnected(pixa, L_THIN_FG, i, i);
+ pixaDestroy(&pixa);
+ pixaDestroy(&return_pixa);
+
+ pixa = pixaReadMem(data, size);
+ return_pixa = pixaThinConnected(pixa, L_THIN_BG, i, i);
+ pixaDestroy(&pixa);
+ pixaDestroy(&return_pixa);
+ }
+
+ pixaDestroy(&return_pixa);
+ pixDestroy(&pixs);
+ return 0;
+} \ No newline at end of file
diff --git a/leptonica/prog/fuzzing/checkerboard_fuzzer.cc b/leptonica/prog/fuzzing/checkerboard_fuzzer.cc
new file mode 100644
index 00000000..468363af
--- /dev/null
+++ b/leptonica/prog/fuzzing/checkerboard_fuzzer.cc
@@ -0,0 +1,25 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+ PIX *pix1, *pix_copy;
+ PIXA *pixa1;
+ PTA *pta1;
+
+ pixa1 = pixaCreate(0);
+ pix_copy = pixCopy(NULL, pixs_payload);
+ pixFindCheckerboardCorners(pix_copy, 15, 3, 2, &pix1, &pta1, pixa1);
+ pixDestroy(&pix_copy);
+ pixaDestroy(&pixa1);
+ ptaDestroy(&pta1);
+ pixDestroy(&pix1);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/classapp_fuzzer.cc b/leptonica/prog/fuzzing/classapp_fuzzer.cc
new file mode 100644
index 00000000..61e8dc3e
--- /dev/null
+++ b/leptonica/prog/fuzzing/classapp_fuzzer.cc
@@ -0,0 +1,24 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ BOX *box1 = boxCreate(150, 130, 1500, 355);
+ BOXA *boxa1;
+ BOXAA *boxaa1;
+ PIX *pix_copy = pixCopy(NULL, pixs_payload);
+ pixFindWordAndCharacterBoxes(pix_copy, box1, 120, &boxa1, &boxaa1, NULL);
+ boxDestroy(&box1);
+ boxaDestroy(&boxa1);
+ boxaaDestroy(&boxaa1);
+ pixDestroy(&pix_copy);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/colorfill_fuzzer.cc b/leptonica/prog/fuzzing/colorfill_fuzzer.cc
new file mode 100644
index 00000000..9218e5ca
--- /dev/null
+++ b/leptonica/prog/fuzzing/colorfill_fuzzer.cc
@@ -0,0 +1,17 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ L_COLORFILL *cf = l_colorfillCreate(pixs_payload, 1, 1);
+ l_colorfillDestroy(&cf);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/colorquant_fuzzer.cc b/leptonica/prog/fuzzing/colorquant_fuzzer.cc
new file mode 100644
index 00000000..410ffa56
--- /dev/null
+++ b/leptonica/prog/fuzzing/colorquant_fuzzer.cc
@@ -0,0 +1,83 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ BOX *box;
+ PIX *pixs;
+ PIX *pix1, *pix2, *pix3, *pix4;
+ PIX *pix5, *pix52, *pix53, *pix54;
+ PIX *pix6, *pix7, *pix8, *pix9;
+ PIX *pix10, *pix11, *pix12;
+ PIX *return_pix;
+ PIXCMAP *cmap;
+
+ leptSetStdNullHandler();
+ pixs = pixReadMemSpix(data, size);
+ if(pixs==NULL) return 0;
+
+ pix1 = pixThresholdTo4bpp(pixs, 6, 1);
+ box = boxCreate(120, 30, 200, 200);
+ pixColorGray(pix1, box, L_PAINT_DARK, 220, 0, 0, 255);
+ boxDestroy(&box);
+
+ pix2 = pixScale(pix1, 1.5, 1.5);
+ cmap = pixGetColormap(pix1);
+ pix3 = pixOctcubeQuantFromCmap(pix2, cmap, 4,
+ 3, L_EUCLIDEAN_DISTANCE);
+
+ pix4 = pixConvertTo32(pix3);
+ pix5 = pixMedianCutQuant(pix4, 0);
+ pix52 = pixMedianCutQuant(pix4, 1);
+ pix53 = pixMedianCutQuant(pixs, 0);
+ pix54 = pixMedianCutQuant(pixs, 1);
+
+ pix6 = pixFewColorsMedianCutQuantMixed(pix4, 30, 30, 100, 0, 0, 0);
+
+ pix7 = pixDeskew(pixs, 0);
+ pixWriteImpliedFormat("/tmp/fuzzfile1", pix7, 0, 0);
+
+ pix8 = pixOctreeQuantByPopulation(pixs, 0, 0);
+ pix9 = pixFewColorsOctcubeQuantMixed(pix4, 3, 20, 244, 20, 0.05, 15);
+ pix10 = pixColorSegment(pixs, 50, 6, 6, 6, 0);
+
+ for(int i=128; i<257; i++){
+ pix11 = pixOctreeColorQuant(pixs, i, 0);
+ pixDestroy(&pix11);
+ pix11 = pixOctreeColorQuant(pixs, i, 1);
+ pixDestroy(&pix11);
+ }
+
+ pix12 = pixFixedOctcubeQuant256(pixs, 0);
+ pixDestroy(&pix12);
+ pix12 = pixFixedOctcubeQuant256(pixs, 1);
+ pixDestroy(&pix12);
+
+ for(int i1=0; i1<10; i1++){
+ for(int i2=0; i2<10; i2++){
+ return_pix = pixQuantFromCmap(pixs, pixGetColormap(pixs),
+ i1, i2, L_MANHATTAN_DISTANCE);
+ pixDestroy(&return_pix);
+
+ return_pix = pixQuantFromCmap(pixs, pixGetColormap(pixs),
+ i1, i2, L_EUCLIDEAN_DISTANCE);
+ pixDestroy(&return_pix);
+ }
+ }
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix52);
+ pixDestroy(&pix53);
+ pixDestroy(&pix54);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ pixDestroy(&pix9);
+ pixDestroy(&pix10);
+ pixDestroy(&pixs);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/compare_fuzzer.cc b/leptonica/prog/fuzzing/compare_fuzzer.cc
new file mode 100644
index 00000000..acc6a99d
--- /dev/null
+++ b/leptonica/prog/fuzzing/compare_fuzzer.cc
@@ -0,0 +1,49 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ l_float32 fract;
+ l_int32 psame, comptype;
+ PIX *pixs1, *pixs2, *pixs3, *pixs4, *pixs5, *pixs6, *pixs7, *pixs8;
+ PIX *pixd1, *pixd2;
+
+ pixs1 = pixReadMemSpix(data, size);
+ if(pixs1==NULL) return 0;
+
+ pixs2 = pixCopy(NULL, pixs1);
+ if(pixs2==NULL) {
+ pixDestroy(&pixs1);
+ return 0;
+ }
+
+ pixs3 = pixConvertTo8(pixs1, 0);
+ pixs4 = pixConvertTo8(pixs2, 0);
+
+ pixGetPerceptualDiff(pixs1, pixs2, 1, 3, 20, &fract, &pixs5, &pixs6);
+ pixGetPerceptualDiff(pixs3, pixs4, 1, 3, 20, &fract, &pixs7, &pixs8);
+
+ pixEqualWithAlpha(pixs1, pixs2, 1, &psame);
+ pixEqualWithAlpha(pixs3, pixs4, 1, &psame);
+
+ pixd1 = NULL;
+ pixCompareBinary(pixs1, pixs2, L_COMPARE_XOR, &fract, &pixd1);
+ pixDestroy(&pixd1);
+ pixd2 = NULL;
+ pixCompareBinary(pixs1, pixs2, L_COMPARE_SUBTRACT, &fract, &pixd2);
+ pixDestroy(&pixd2);
+
+ pixDestroy(&pixs1);
+ pixDestroy(&pixs2);
+ pixDestroy(&pixs3);
+ pixDestroy(&pixs4);
+ pixDestroy(&pixs5);
+ pixDestroy(&pixs6);
+ pixDestroy(&pixs7);
+ pixDestroy(&pixs8);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/dewarp_fuzzer.cc b/leptonica/prog/fuzzing/dewarp_fuzzer.cc
new file mode 100644
index 00000000..37098a9f
--- /dev/null
+++ b/leptonica/prog/fuzzing/dewarp_fuzzer.cc
@@ -0,0 +1,29 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ if(size<3) return 0;
+
+ PIX *pixs, *pixd;
+ L_DEWARPA *dewa1;
+ PIXAC *pixac;
+ SARRAY *sa;
+
+ leptSetStdNullHandler();
+
+ pixs = pixReadMemSpix(data, size);
+ if(pixs==NULL) return 0;
+
+ // Don't use debug, because it requires writing to /tmp
+ dewarpSinglePage(pixs, 0, 1, 1, 0, &pixd, NULL, 0);
+
+ pixac = pixacompReadMem(data, size);
+ dewa1 = dewarpaCreateFromPixacomp(pixac, 1, 0, 10, -1);
+
+ dewarpaDestroy(&dewa1);
+ pixacompDestroy(&pixac);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/edge_fuzzer.cc b/leptonica/prog/fuzzing/edge_fuzzer.cc
new file mode 100644
index 00000000..4e4c0944
--- /dev/null
+++ b/leptonica/prog/fuzzing/edge_fuzzer.cc
@@ -0,0 +1,18 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ l_float32 l_f1;
+ l_float32 l_f2;
+ l_float32 l_f3;
+ pixMeasureEdgeSmoothness(pixs_payload, 1, 1, 1, &l_f1, &l_f2, &l_f3, NULL);
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/enhance_fuzzer.cc b/leptonica/prog/fuzzing/enhance_fuzzer.cc
new file mode 100644
index 00000000..4fa7ce76
--- /dev/null
+++ b/leptonica/prog/fuzzing/enhance_fuzzer.cc
@@ -0,0 +1,101 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if (pixs_payload == NULL) return 0;
+
+ PIX *pix_pointer_payload, *return_pix, *pix2;
+ L_KERNEL *kel;
+ NUMA *na1, *na2, *na3;
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixContrastTRCMasked(NULL, pix_pointer_payload, NULL, 0.5);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixDarkenGray(NULL, pix_pointer_payload, 220, 10);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixEqualizeTRC(NULL, pix_pointer_payload, 220, 10);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixGammaTRCMasked(NULL, pix_pointer_payload, NULL,
+ 1.0, 100, 175);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixGammaTRCWithAlpha(NULL, pix_pointer_payload,
+ 0.5, 1.0, 100);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixHalfEdgeByBandpass(pix_pointer_payload, 2, 2, 4, 4);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ l_float32 sat;
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pixMeasureSaturation(pix_pointer_payload, 1, &sat);
+ pixDestroy(&pix_pointer_payload);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixModifyBrightness(NULL, pix_pointer_payload, 0.5);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixModifyHue(NULL, pix_pointer_payload, 0.01 + 0.05 * 1);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixModifySaturation(NULL, pix_pointer_payload,
+ -0.9 + 0.1 * 1);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixMosaicColorShiftRGB(pix_pointer_payload,
+ -0.1, 0.0, 0.0, 0.0999, 1);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixMultConstantColor(pix_pointer_payload, 0.7, 0.4, 1.3);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ kel = kernelCreate(3, 3);
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixMultMatrixColor( pix_pointer_payload, kel);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+ kernelDestroy(&kel);
+
+ na1 = numaGammaTRC(1.0, 0, 255);
+ na2 = numaGammaTRC(1.0, 0, 255);
+ na3 = numaGammaTRC(1.0, 0, 255);
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pix2 = pixMakeSymmetricMask(10, 10, 0.5, 0.5, L_USE_INNER);
+ pixTRCMapGeneral(pix_pointer_payload, pix2, na1, na2, na3);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&pix2);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/fhmtgen_fuzzer.cc b/leptonica/prog/fuzzing/fhmtgen_fuzzer.cc
new file mode 100644
index 00000000..16cef24a
--- /dev/null
+++ b/leptonica/prog/fuzzing/fhmtgen_fuzzer.cc
@@ -0,0 +1,33 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ l_int32 i, nsels;
+ char *selname;
+ SEL *sel;
+ SELA *sela;
+ PIX *pix_pointer_payload, *return_pix;
+
+ sela = selaAddHitMiss(NULL);
+ nsels = selaGetCount(sela);
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+
+ for (i = 0; i < nsels; i++) {
+ sel = selaGetSel(sela, i);
+ selname = selGetName(sel);
+ return_pix = pixHMTDwa_1(NULL, pix_pointer_payload, selname);
+ pixDestroy(&return_pix);
+ }
+
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&pixs_payload);
+ selaDestroy(&sela);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/finditalic_fuzzer.cc b/leptonica/prog/fuzzing/finditalic_fuzzer.cc
new file mode 100644
index 00000000..5a46572c
--- /dev/null
+++ b/leptonica/prog/fuzzing/finditalic_fuzzer.cc
@@ -0,0 +1,18 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ BOXA *boxa1;
+ pixItalicWords(pixs_payload, NULL, NULL, &boxa1, 1);
+
+ pixDestroy(&pixs_payload);
+ boxaDestroy(&boxa1);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/flipdetect_fuzzer.cc b/leptonica/prog/fuzzing/flipdetect_fuzzer.cc
new file mode 100644
index 00000000..e1bafeb2
--- /dev/null
+++ b/leptonica/prog/fuzzing/flipdetect_fuzzer.cc
@@ -0,0 +1,31 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ l_float32 minupconf, minratio, conf1, upconf1, leftconf1;
+ PIX *pix_pointer_payload, *return_pix;
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pixMirrorDetect(pix_pointer_payload, &conf1, 0, 1);
+ pixDestroy(&pix_pointer_payload);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixOrientCorrect(pix_pointer_payload, minupconf,
+ minratio, NULL, NULL, NULL, 1);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pixOrientDetect(pix_pointer_payload, &upconf1, &leftconf1, 0, 0);
+ pixDestroy(&pix_pointer_payload);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/fpix2_fuzzer.cc b/leptonica/prog/fuzzing/fpix2_fuzzer.cc
new file mode 100644
index 00000000..e666c589
--- /dev/null
+++ b/leptonica/prog/fuzzing/fpix2_fuzzer.cc
@@ -0,0 +1,111 @@
+#include "leptfuzz.h"
+
+//static void MakePtas(l_int32 i, PTA **pptas, PTA **pptad);
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *tmp_pixs = pixReadMemSpix(data, size);
+ if(tmp_pixs == NULL) return 0;
+
+ DPIX *dpix_payload = pixConvertToDPix(tmp_pixs, 1);
+ if(dpix_payload == NULL) {
+ pixDestroy(&tmp_pixs);
+ return 0;
+ }
+
+ FPIX *fpix_payload = dpixConvertToFPix(dpix_payload);
+ if(fpix_payload == NULL) {
+ pixDestroy(&tmp_pixs);
+ dpixDestroy(&dpix_payload);
+ return 0;
+ }
+
+ DPIX *dpix_copy1 = dpixCopy(dpix_payload);
+ dpixAddMultConstant(dpix_copy1, 1.0, 1.2);
+ dpixDestroy(&dpix_copy1);
+
+ DPIX *dpix_copy2 = dpixCopy(dpix_payload);
+ FPIX *fpixs1 = dpixConvertToFPix(dpix_copy2);
+ fpixDestroy(&fpixs1);
+ dpixDestroy(&dpix_copy2);
+
+ DPIX *dpix_copy3 = dpixCopy(dpix_payload);
+ PIX *pix1 = dpixConvertToPix(dpix_copy3, 8, L_CLIP_TO_ZERO, 0);
+ pixDestroy(&pix1);
+ dpixDestroy(&dpix_copy3);
+
+ l_float64 l_f1;
+ l_int32 l_i1;
+ l_int32 l_i2;
+ DPIX *dpix_copy4 = dpixCopy(dpix_payload);
+ dpixGetMax(dpix_copy4, &l_f1, &l_i1, &l_i2);
+ dpixDestroy(&dpix_copy4);
+
+ l_float64 l_f2;
+ l_int32 l_i3;
+ l_int32 l_i4;
+ DPIX *dpix_copy5 = dpixCopy(dpix_payload);
+ dpixGetMin(dpix_copy5, &l_f2, &l_i3, &l_i4);
+ dpixDestroy(&dpix_copy5);
+
+ DPIX *dpix1 = dpixCreate(300, 300);
+ DPIX *dpix2 = dpixCreate(300, 300);
+ DPIX *dpix_copy6 = dpixCopy(dpix_payload);
+ dpixLinearCombination(dpix_copy6, dpix_copy6, dpix2, 1.1, 1.2);
+ dpixDestroy(&dpix1);
+ dpixDestroy(&dpix2);
+ dpixDestroy(&dpix_copy6);
+
+ DPIX *dpix_copy7 = dpixCopy(dpix_payload);
+ DPIX *dpix3 = dpixScaleByInteger(dpix_copy7, 1);
+ dpixDestroy(&dpix3);
+ dpixDestroy(&dpix_copy7);
+
+ DPIX *dpix_copy8 = dpixCopy(dpix_payload);
+ dpixSetAllArbitrary(dpix_copy8, 1.1);
+ dpixDestroy(&dpix_copy8);
+
+ FPIX *fpix_copy1 = fpixCopy(fpix_payload);
+ FPIX *fpix2 = fpixAddContinuedBorder(fpix_copy1, 1, 1, 1, 1);
+ fpixDestroy(&fpix_copy1);
+ fpixDestroy(&fpix2);
+
+ PTA *pta1 = ptaCreate(0);
+ PTA *pta2 = ptaCreate(0);
+ FPIX *fpix_copy92 = fpixCopy(fpix_payload);
+ FPIX *fpix3 = fpixAffinePta(fpix_copy92, pta1, pta2, 1, 0);
+ fpixDestroy(&fpix3);
+ fpixDestroy(&fpix_copy92);
+ ptaDestroy(&pta1);
+ ptaDestroy(&pta2);
+
+ FPIX *fpix_copy2 = fpixCopy(fpix_payload);
+ DPIX *dpix_return1 = fpixConvertToDPix(fpix_copy2);
+ fpixDestroy(&fpix_copy2);
+ dpixDestroy(&dpix_return1);
+
+ FPIX *fpix5 = fpixCreate(300, 300);
+ FPIX *fpix_copy3 = fpixCopy(fpix_payload);
+ fpixLinearCombination(fpix_copy3, fpix_copy3, fpix5, 1.1, 1.1);
+ fpixDestroy(&fpix_copy3);
+ fpixDestroy(&fpix5);
+
+ PTA *ptas, *ptad;
+ ptas = ptaCreate(0);
+ ptad = ptaCreate(0);
+ FPIX *fpix_copy4 = fpixCopy(fpix_payload);
+ FPIX *fpix_return2 = fpixProjectivePta(fpix_copy4, ptas, ptad, 200, 0.0);
+ fpixDestroy(&fpix_return2);
+ fpixDestroy(&fpix_copy4);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+
+ pixDestroy(&tmp_pixs);
+ dpixDestroy(&dpix_payload);
+ fpixDestroy(&fpix_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/general_corpus.zip b/leptonica/prog/fuzzing/general_corpus.zip
new file mode 100644
index 00000000..61a08f0d
--- /dev/null
+++ b/leptonica/prog/fuzzing/general_corpus.zip
Binary files differ
diff --git a/leptonica/prog/fuzzing/graphics_fuzzer.cc b/leptonica/prog/fuzzing/graphics_fuzzer.cc
new file mode 100644
index 00000000..6ca746c6
--- /dev/null
+++ b/leptonica/prog/fuzzing/graphics_fuzzer.cc
@@ -0,0 +1,81 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ PTA *pta1 = ptaCreate(0);
+ PIX *pix_return1 = pixFillPolygon(pixs_payload, pta1, 2, 2);
+ ptaDestroy(&pta1);
+ pixDestroy(&pix_return1);
+
+ PTA *pta_return1 =pixGeneratePtaBoundary(pixs_payload, 1);
+ ptaDestroy(&pta_return1);
+
+ BOX *box1 = boxCreate(150, 130, 1500, 355);
+ pixRenderBox(pixs_payload, box1, 3, 200);
+ boxDestroy(&box1);
+
+ BOXA *boxa1;
+ boxa1 = boxaCreate(0);
+ pixRenderBoxa(pixs_payload, boxa1, 17, 200);
+ boxaDestroy(&boxa1);
+
+ boxa1 = boxaCreate(0);
+ pixRenderBoxaBlend(pixs_payload, boxa1, 17, 200, 1, 25, 0.4, 1);
+ boxaDestroy(&boxa1);
+
+ PIX *pix_return12 = pixRenderContours(pixs_payload, 2, 4, 1);
+ pixDestroy(&pix_return12);
+
+ pixRenderGridArb(pixs_payload, 1, 1, 1, 1, 1, 1);
+
+ BOX *box2 = boxCreate(150, 130, 1500, 355);
+ pixRenderHashBox(pixs_payload, box2, 2, 1, 1, 0, L_SET_PIXELS);
+ boxDestroy(&box2);
+
+ BOX *box3 = boxCreate(150, 130, 1500, 355);
+ pixRenderHashBoxBlend(pixs_payload, box3, 2, 1, L_HORIZONTAL_LINE, 0, 1, 1, 1, 1.0);
+ boxDestroy(&box3);
+
+ BOXA *boxa2;
+ boxa2 = boxaCreate(1);
+ pixRenderHashBoxa(pixs_payload, boxa2, 2, 1, L_HORIZONTAL_LINE, 0, L_SET_PIXELS);
+ boxaDestroy(&boxa2);
+
+ boxa1 = boxaCreate(1);
+ pixRenderHashBoxaArb(pixs_payload, boxa1, 2, 1, L_HORIZONTAL_LINE, 0, 1, 1, 1);
+ boxaDestroy(&boxa1);
+
+ PIX *pixs = pixRead("../test8.jpg");
+ pixRenderHashMaskArb(pixs_payload, pixs, 2, 2, 2, 1, L_HORIZONTAL_LINE, 0, 1, 1, 1);
+ pixDestroy(&pixs);
+
+ pixRenderLineBlend(pixs_payload, 30, 60, 440, 70, 5, 115, 200, 120, 0.3);
+
+ PIX *pixs_payload2 = pixCopy(NULL, pixs_payload);
+ NUMA *na2 = numaGammaTRC(1.7, 150, 255);
+ pixRenderPlotFromNumaGen(&pixs_payload2, na2, L_HORIZONTAL_LINE, 3, 1, 80, 1, 1);
+ numaDestroy(&na2);
+ pixDestroy(&pixs_payload2);
+
+ PTA *pta2 = ptaCreate(0);
+ pixRenderPolylineArb(pixs_payload, pta2, 1, 1, 1, 1, 0);
+ ptaDestroy(&pta2);
+
+ PTA *pta3 = ptaCreate(0);
+ pixRenderPolylineBlend(pixs_payload, pta3, 17, 25, 200, 1, 0.5, 1, 1);
+ ptaDestroy(&pta3);
+
+ NUMA *na1 = numaGammaTRC(1.7, 150, 255);
+ pixRenderPlotFromNuma(&pixs_payload, na1, L_HORIZONTAL_LINE, 3, 1, 80);
+ numaDestroy(&na1);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/graymorph_fuzzer.cc b/leptonica/prog/fuzzing/graymorph_fuzzer.cc
new file mode 100644
index 00000000..3115e956
--- /dev/null
+++ b/leptonica/prog/fuzzing/graymorph_fuzzer.cc
@@ -0,0 +1,36 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ PIX *pix_pointer_payload, *return_pix;
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixCloseGray3(pix_pointer_payload, 3, 1);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixDilateGray3(pix_pointer_payload, 3, 1);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixErodeGray3(pix_pointer_payload, 3, 1);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixOpenGray3(pix_pointer_payload, 3, 1);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/grayquant_fuzzer.cc b/leptonica/prog/fuzzing/grayquant_fuzzer.cc
new file mode 100644
index 00000000..90cc8d17
--- /dev/null
+++ b/leptonica/prog/fuzzing/grayquant_fuzzer.cc
@@ -0,0 +1,47 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ PIX *pix_pointer_payload, *return_pix;
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixDitherTo2bpp(pix_pointer_payload, 1);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixDitherToBinary(pix_pointer_payload);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixGenerateMaskByBand(pix_pointer_payload, 1, 2, 1, 1);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixGenerateMaskByBand32(pix_pointer_payload, 1, 1, 1, 0.0, 0.0);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixGenerateMaskByDiscr32(pix_pointer_payload, 10, 10, L_MANHATTAN_DISTANCE);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ const char *str = "45 75 115 185";
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixThresholdGrayArb(pix_pointer_payload, str, 8, 0, 0, 0);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/jpegiostub_fuzzer.cc b/leptonica/prog/fuzzing/jpegiostub_fuzzer.cc
new file mode 100644
index 00000000..08e05a2f
--- /dev/null
+++ b/leptonica/prog/fuzzing/jpegiostub_fuzzer.cc
@@ -0,0 +1,15 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ pixSetChromaSampling(pixs_payload, 0);
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/kernel_fuzzer.cc b/leptonica/prog/fuzzing/kernel_fuzzer.cc
new file mode 100644
index 00000000..127f834b
--- /dev/null
+++ b/leptonica/prog/fuzzing/kernel_fuzzer.cc
@@ -0,0 +1,16 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ L_KERNEL *kel1 = kernelCreateFromPix(pixs_payload, 2, 2);
+ pixDestroy(&pixs_payload);
+ kernelDestroy(&kel1);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/leptfuzz.h b/leptonica/prog/fuzzing/leptfuzz.h
new file mode 100644
index 00000000..c8f8de12
--- /dev/null
+++ b/leptonica/prog/fuzzing/leptfuzz.h
@@ -0,0 +1,16 @@
+/* Common include file for Leptonica fuzzers. */
+
+#ifndef LEPTFUZZ_H
+#define LEPTFUZZ_H
+
+#include "allheaders.h"
+
+static void send_to_devnull(const char *) {}
+
+/* Suppress Leptonica error messages during fuzzing. */
+static void leptSetStdNullHandler()
+{
+ leptSetStderrHandler(send_to_devnull);
+}
+
+#endif /* LEPTFUZZ_H */
diff --git a/leptonica/prog/fuzzing/mask_fuzzer.cc b/leptonica/prog/fuzzing/mask_fuzzer.cc
new file mode 100644
index 00000000..dc665a9d
--- /dev/null
+++ b/leptonica/prog/fuzzing/mask_fuzzer.cc
@@ -0,0 +1,42 @@
+#include "leptfuzz.h"
+
+PIX *
+MakeReplacementMask(PIX *pixs)
+{
+PIX *pix1, *pix2, *pix3, *pix4;
+
+ pix1 = pixMaskOverColorPixels(pixs, 95, 3);
+ pix2 = pixMorphSequence(pix1, "o15.15", 0);
+ pixSeedfillBinary(pix2, pix2, pix1, 8);
+ pix3 = pixMorphSequence(pix2, "c15.15 + d61.31", 0);
+ pix4 = pixRemoveBorderConnComps(pix3, 8);
+ pixXor(pix4, pix4, pix3);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ return pix4;
+}
+
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs, *pix1, *pix2;
+
+ pixs = pixReadMemSpix(data, size);
+ if(pixs==NULL) return 0;
+
+ pix1 = MakeReplacementMask(pixs);
+ pix2 = pixConvertTo8(pix1, FALSE);
+
+ pixPaintSelfThroughMask(pix2, pix1, 0, 0, L_HORIZ, 30, 50, 5, 10);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/maze_fuzzer.cc b/leptonica/prog/fuzzing/maze_fuzzer.cc
new file mode 100644
index 00000000..b3116ae2
--- /dev/null
+++ b/leptonica/prog/fuzzing/maze_fuzzer.cc
@@ -0,0 +1,24 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ PIX *ppixd;
+
+ PTA *pta = pixSearchBinaryMaze(pixs_payload, 1, 2, 3, 4, &ppixd);
+ pixDestroy(&ppixd);
+ ptaDestroy(&pta);
+
+ PTA *pta2 = pixSearchGrayMaze(pixs_payload, 1, 2, 3, 4, &ppixd);
+ pixDestroy(&ppixd);
+ ptaDestroy(&pta2);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/morph_fuzzer.cc b/leptonica/prog/fuzzing/morph_fuzzer.cc
new file mode 100644
index 00000000..f4055550
--- /dev/null
+++ b/leptonica/prog/fuzzing/morph_fuzzer.cc
@@ -0,0 +1,48 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ PIX *pix, *pix1, *pix_copy1, *pix_copy2, *pix_copy3, *pix_copy4;
+ BOX *box1;
+ SEL *sel;
+
+ pix = pixRead("../feyn-fract.tif");
+ box1 = boxCreate(507, 65, 60, 36);
+ pix1 = pixClipRectangle(pix, box1, NULL);
+ sel = selCreateFromPix(pix1, 6, 6, "plus_sign");
+ pix_copy1 = pixCopy(NULL, pixs_payload);
+ pixCloseGeneralized(pix_copy1, pix, sel);
+ boxDestroy(&box1);
+ pixDestroy(&pix_copy1);
+ pixDestroy(&pix1);
+
+ pix_copy2 = pixCopy(NULL, pixs_payload);
+ pixCloseSafe(pix_copy2, pix, sel);
+ pixDestroy(&pix_copy2);
+
+ pix_copy3 = pixCopy(NULL, pixs_payload);
+ pixOpenGeneralized(pix_copy3, pix, sel);
+ pixDestroy(&pix_copy3);
+ pixDestroy(&pix);
+ selDestroy(&sel);
+
+ for (l_int32 i = 0; i < 5; i++) {
+ if ((sel = selCreate (i, i, "sel_5dp")) == NULL)
+ continue;
+ char *selname = selGetName(sel);
+ pix_copy4 = pixCopy(NULL, pixs_payload);
+ pixMorphDwa_1(pix_copy4, pix_copy4, i, selname);
+ pixDestroy(&pix_copy4);
+ selDestroy(&sel);
+ }
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/morphapp_fuzzer.cc b/leptonica/prog/fuzzing/morphapp_fuzzer.cc
new file mode 100644
index 00000000..ec4cf2ff
--- /dev/null
+++ b/leptonica/prog/fuzzing/morphapp_fuzzer.cc
@@ -0,0 +1,65 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ Pix *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ Pix *pix1 = pixRead("../test8.jpg");
+ Pix *pix2 = pixRead("../test8.jpg");
+ Pix *pix_return1 = pixDisplayMatchedPattern(pixs_payload, pix1, pix2,
+ 1, 2, 3, 0.5, 1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix_return1);
+ pixDestroy(&pix2);
+
+ Pix *pix_return2 = pixFastTophat(pixs_payload, 2, 2, L_TOPHAT_WHITE);
+ pixDestroy(&pix_return2);
+
+ Pix *pix_return3 = pixHDome(pixs_payload, 1, 4);
+ pixDestroy(&pix_return3);
+
+ Sela *sela1 = selaCreate(0);
+ Pix *pix_return4 = pixIntersectionOfMorphOps(pixs_payload,
+ sela1, L_MORPH_DILATE);
+ selaDestroy(&sela1);
+ pixDestroy(&pix_return4);
+
+ Pix *pix_return5 = pixMorphGradient(pixs_payload, 5, 5, 1);
+ pixDestroy(&pix_return5);
+
+ pix1 = pixRead("../test8.jpg");
+ Boxa *boxa1;
+ const char *sequence = "sequence";
+ Pix *pix_return6 = pixMorphSequenceByRegion(pixs_payload, pix1,
+ sequence, 4, 1, 1, &boxa1);
+ boxaDestroy(&boxa1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix_return6);
+
+ pix1 = pixRead("../test8.jpg");
+ Pix *pix_return7 = pixMorphSequenceMasked(pixs_payload, pix1, sequence, 0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix_return7);
+
+ pix1 = pixCreate(300, 300, 32);
+ pix2 = pixCreate(300, 300, 32);
+ pixRemoveMatchedPattern(pixs_payload, pix1, pix2, 2, 2, 2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ Pix *pix = pixCreate(300, 300, 32);
+ Pix *pix_return8 = pixSeedfillMorph(pixs_payload, pix, 0, 4);
+ pixDestroy(&pix_return8);
+ pixDestroy(&pix);
+
+ Pix *pix_return9 = pixSelectiveConnCompFill(pixs_payload, 4, 1, 1);
+ pixDestroy(&pix_return9);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/oss-fuzz-build.sh b/leptonica/prog/fuzzing/oss-fuzz-build.sh
new file mode 100755
index 00000000..ef248627
--- /dev/null
+++ b/leptonica/prog/fuzzing/oss-fuzz-build.sh
@@ -0,0 +1,128 @@
+# libz
+pushd $SRC/zlib
+./configure --static --prefix="$WORK"
+make -j$(nproc) all
+make install
+popd
+
+# libzstd
+pushd $SRC/zstd
+make -j$(nproc) install PREFIX="$WORK"
+popd
+
+# libjbig
+pushd "$SRC/jbigkit"
+make clean
+make -j$(nproc) lib
+cp "$SRC"/jbigkit/libjbig/*.a "$WORK/lib/"
+cp "$SRC"/jbigkit/libjbig/*.h "$WORK/include/"
+popd
+
+# libjpeg-turbo
+pushd $SRC/libjpeg-turbo
+cmake . -DCMAKE_INSTALL_PREFIX="$WORK" -DENABLE_STATIC:bool=on
+make -j$(nproc)
+make install
+popd
+
+# libpng
+pushd $SRC/libpng
+cat scripts/pnglibconf.dfa | \
+ sed -e "s/option WARNING /option WARNING disabled/" \
+> scripts/pnglibconf.dfa.temp
+mv scripts/pnglibconf.dfa.temp scripts/pnglibconf.dfa
+autoreconf -f -i
+./configure \
+ --prefix="$WORK" \
+ --disable-shared \
+ --enable-static \
+ LDFLAGS="-L$WORK/lib" \
+ CPPFLAGS="-I$WORK/include"
+make -j$(nproc)
+make install
+popd
+
+# libwebp
+pushd $SRC/libwebp
+export WEBP_CFLAGS="$CFLAGS -DWEBP_MAX_IMAGE_SIZE=838860800" # 800MiB
+./autogen.sh
+./configure \
+ --enable-libwebpdemux \
+ --enable-libwebpmux \
+ --disable-shared \
+ --disable-jpeg \
+ --disable-tiff \
+ --disable-gif \
+ --disable-wic \
+ --disable-threading \
+ --prefix="$WORK" \
+ CFLAGS="$WEBP_CFLAGS"
+make clean
+make -j$(nproc)
+make install
+popd
+
+# libtiff
+pushd "$SRC/libtiff"
+cmake . -DCMAKE_INSTALL_PREFIX="$WORK" -DBUILD_SHARED_LIBS=off
+make clean
+make -j$(nproc)
+make install
+popd
+
+# leptonica
+export LEPTONICA_LIBS="$WORK/lib/libjbig.a $WORK/lib/libzstd.a $WORK/lib/libwebp.a $WORK/lib/libpng.a"
+./autogen.sh
+./configure \
+ --enable-static \
+ --disable-shared \
+ --with-libpng \
+ --with-zlib \
+ --with-jpeg \
+ --with-libwebp \
+ --with-libtiff \
+ --prefix="$WORK" \
+ LIBS="$LEPTONICA_LIBS" \
+ LDFLAGS="-L$WORK/lib" \
+ CPPFLAGS="-I$WORK/include -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION"
+make -j$(nproc)
+make install
+
+for f in $SRC/leptonica/prog/fuzzing/*_fuzzer.cc; do
+ fuzzer=$(basename "$f" _fuzzer.cc)
+ $CXX $CXXFLAGS -std=c++11 -I"$WORK/include" \
+ $SRC/leptonica/prog/fuzzing/${fuzzer}_fuzzer.cc -o $OUT/${fuzzer}_fuzzer \
+ -Isrc/ \
+ "$WORK/lib/liblept.a" \
+ "$WORK/lib/libtiff.a" \
+ "$WORK/lib/libwebp.a" \
+ "$WORK/lib/libpng.a" \
+ "$WORK/lib/libjpeg.a" \
+ "$WORK/lib/libjbig.a" \
+ "$WORK/lib/libzstd.a" \
+ "$WORK/lib/libz.a" \
+ $LIB_FUZZING_ENGINE
+done
+
+
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/adaptmap_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/affine_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/baseline_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/bilateral_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/bilinear_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/binarize_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/blend_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/checkerboard_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/classapp_fuzzer_seed_corpus.zip
+
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/pix_rotate_shear_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/enhance_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/colorquant_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/dewarp_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/pix_orient_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/pixconv_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/blend_fuzzer_seed_corpus.zip
+cp $SRC/leptonica/prog/fuzzing/general_corpus.zip $OUT/pixMirrorDetectDwa_fuzzer_seed_corpus.zip
+
+cp $SRC/leptonica/prog/fuzzing/pixa_recog_fuzzer_seed_corpus.zip $OUT/
+cp $SRC/leptonica/prog/fuzzing/barcode_fuzzer_seed_corpus.zip $OUT/
diff --git a/leptonica/prog/fuzzing/pageseg_fuzzer.cc b/leptonica/prog/fuzzing/pageseg_fuzzer.cc
new file mode 100644
index 00000000..93ccbefc
--- /dev/null
+++ b/leptonica/prog/fuzzing/pageseg_fuzzer.cc
@@ -0,0 +1,42 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ leptSetStdNullHandler();
+
+ l_int32 score;
+ PIX *pixs;
+ PIX *pix1, *pix2, *pix3, *pix4;
+ PIXA *pixa1, *pixa2;
+ PIXAC *pixac;
+ BOX *box;
+ BOXA *boxa;
+
+ pixs = pixReadMemSpix(data, size);
+ if(pixs==NULL) return 0;
+
+ pixa1 = pixaCreate(0);
+ pixDecideIfTable(pixs, NULL, L_PORTRAIT_MODE, &score, pixa1);
+ pixaDestroy(&pixa1);
+
+ pixa1 = pixaCreate(0);
+ pixGetRegionsBinary(pixs, &pix1, &pix2, &pix3, pixa1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa1);
+
+ pixac = pixacompReadMem(data, size);
+ box = pixFindPageForeground(pixs, 170, 70, 30, 0, pixac);
+ boxDestroy(&box);
+ pixacompDestroy(&pixac);
+
+ pixSplitIntoCharacters(pixs, 4, 4, &boxa, &pixa2, &pix4);
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa2);
+ pixDestroy(&pix4);
+
+ pixDestroy(&pixs);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/paintcmap_fuzzer.cc b/leptonica/prog/fuzzing/paintcmap_fuzzer.cc
new file mode 100644
index 00000000..54f9c62b
--- /dev/null
+++ b/leptonica/prog/fuzzing/paintcmap_fuzzer.cc
@@ -0,0 +1,27 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ BOX *box1;
+ PIX *pix_pointer_payload;
+
+ box1 = boxCreate(278, 35, 122, 50);
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pixSetSelectCmap(pix_pointer_payload, box1, 2, 255, 255, 100);
+ boxDestroy(&box1);
+ pixDestroy(&pix_pointer_payload);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pixSetSelectMaskedCmap(pix_pointer_payload, NULL, 1, 50, 0, 250, 249, 248);
+ pixDestroy(&pix_pointer_payload);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/pix1_fuzzer.cc b/leptonica/prog/fuzzing/pix1_fuzzer.cc
new file mode 100644
index 00000000..f987e3f8
--- /dev/null
+++ b/leptonica/prog/fuzzing/pix1_fuzzer.cc
@@ -0,0 +1,19 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ leptSetStdNullHandler();
+
+ l_uint32 *data2;
+ PIX *pixs;
+
+ pixs = pixReadMemSpix(data, size);
+ if(pixs==NULL) return 0;
+
+ data2 = pixExtractData(pixs);
+
+ lept_free(data2);
+ pixDestroy(&pixs);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/pix3_fuzzer.cc b/leptonica/prog/fuzzing/pix3_fuzzer.cc
new file mode 100644
index 00000000..fd4c6cbf
--- /dev/null
+++ b/leptonica/prog/fuzzing/pix3_fuzzer.cc
@@ -0,0 +1,133 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ PIX *pixs_payload = pixReadMemSpix(data, size);
+ if(pixs_payload == NULL) return 0;
+
+ BOX *box1;
+ PIX *pix_pointer_payload, *return_pix, *pix2;
+ NUMA *return_numa;
+ l_float32 l_f;
+ l_int32 l_i, l_i2;
+ l_uint32 l_ui;
+ BOXA *boxa1;
+
+ box1 = boxCreate(150, 130, 1500, 355);
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_numa = pixAbsDiffByColumn(pix_pointer_payload, box1);
+ pixDestroy(&pix_pointer_payload);
+ boxDestroy(&box1);
+ numaDestroy(&return_numa);
+
+ box1 = boxCreate(150, 130, 1500, 355);
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_numa = pixAbsDiffByRow(pix_pointer_payload, box1);
+ pixDestroy(&pix_pointer_payload);
+ boxDestroy(&box1);
+ numaDestroy(&return_numa);
+
+ box1 = boxCreate(150, 130, 1500, 355);
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pixAbsDiffInRect(pix_pointer_payload, box1, L_HORIZONTAL_LINE, &l_f);
+ pixDestroy(&pix_pointer_payload);
+ boxDestroy(&box1);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pixAbsDiffOnLine(pix_pointer_payload, 2, 2, 3, 3, &l_f);
+ pixDestroy(&pix_pointer_payload);
+
+ box1 = boxCreate(150, 130, 1500, 355);
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_numa = pixAverageByColumn(pix_pointer_payload, box1, L_BLACK_IS_MAX);
+ boxDestroy(&box1);
+ pixDestroy(&pix_pointer_payload);
+ numaDestroy(&return_numa);
+
+ box1 = boxCreate(150, 130, 1500, 355);
+ return_numa = pixAverageByRow(pix_pointer_payload, box1, L_WHITE_IS_MAX);
+ boxDestroy(&box1);
+ pixDestroy(&pix_pointer_payload);
+ numaDestroy(&return_numa);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pixAverageInRect(pix_pointer_payload, NULL, NULL, 0, 255, 1, &l_f);
+ pixDestroy(&pix_pointer_payload);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pixAverageInRectRGB(pix_pointer_payload, NULL, NULL, 10, &l_ui);
+ pixDestroy(&pix_pointer_payload);
+
+ boxa1 = boxaCreate(0);
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixCopyWithBoxa(pix_pointer_payload, boxa1, L_SET_WHITE);
+ pixDestroy(&pix_pointer_payload);
+ boxaDestroy(&boxa1);
+ pixDestroy(&return_pix);
+
+ for (int i = 0; i < 5; i++) {
+ box1 = boxCreate(150, 130, 1500, 355);
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pixCountArbInRect(pix_pointer_payload, box1, L_SET_WHITE, 2, &l_i);
+ pixDestroy(&pix_pointer_payload);
+ boxDestroy(&box1);
+ }
+
+ box1 = boxCreate(150, 130, 1500, 355);
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_numa = pixCountByRow(pix_pointer_payload, box1);
+ pixDestroy(&pix_pointer_payload);
+ boxDestroy(&box1);
+ numaDestroy(&return_numa);
+
+ box1 = boxCreate(150, 130, 1500, 355);
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pixCountPixelsInRect(pix_pointer_payload, box1, &l_i, &l_i2);
+ boxDestroy(&box1);
+ pixDestroy(&pix_pointer_payload);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixMakeArbMaskFromRGB(pix_pointer_payload, -0.5, -0.5, 1.0, 20);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ for (int i = 0; i < 5; i++) {
+ return_pix = pixMakeMaskFromVal(pix_pointer_payload, i);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+ }
+
+ pix2 = pixRead("../test8.jpg");
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ pixPaintSelfThroughMask(pix_pointer_payload, pix2, 0, 0, L_HORIZ, 30, 50, 5, 10);
+ pixDestroy(&pix2);
+ pixDestroy(&pix_pointer_payload);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_pix = pixSetUnderTransparency(pix_pointer_payload, 0, 0);
+ pixDestroy(&pix_pointer_payload);
+ pixDestroy(&return_pix);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_numa = pixVarianceByColumn(pix2, NULL);
+ pixDestroy(&pix_pointer_payload);
+ numaDestroy(&return_numa);
+
+ pix_pointer_payload = pixCopy(NULL, pixs_payload);
+ return_numa = pixVarianceByRow(pix_pointer_payload, NULL);
+ pixDestroy(&pix_pointer_payload);
+ numaDestroy(&return_numa);
+
+ box1 = boxCreate(150, 130, 1500, 355);
+ pixVarianceInRect(pix_pointer_payload, box1, &l_f);
+ boxDestroy(&box1);
+ pixDestroy(&pix_pointer_payload);
+
+ pixDestroy(&pixs_payload);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/pix4_fuzzer.cc b/leptonica/prog/fuzzing/pix4_fuzzer.cc
new file mode 100644
index 00000000..c8d23259
--- /dev/null
+++ b/leptonica/prog/fuzzing/pix4_fuzzer.cc
@@ -0,0 +1,109 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ leptSetStdNullHandler();
+
+ PIX *pixs;
+ BOX *box;
+
+ pixs = pixReadMemSpix(data, size);
+ if(pixs==NULL) return 0;
+
+ PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+ NUMA *na1, *na2, *na3, *na4, *na5, *na6;
+ NUMAA *naa1;
+
+ pix1 = pixConvertTo8(pixs, FALSE);
+ box = boxCreate(120, 30, 200, 200);
+ na1 = pixGetGrayHistogramInRect(pix1, box, 1);
+ numaDestroy(&na1);
+ boxDestroy(&box);
+ pixDestroy(&pix1);
+
+ naa1 = pixGetGrayHistogramTiled(pixs, 1, 1, 1);
+ numaaDestroy(&naa1);
+
+ pix1 = pixConvertTo8(pixs, FALSE);
+ na1 = pixGetCmapHistogramMasked(pix1, NULL, 1, 1, 1);
+ numaDestroy(&na1);
+ pixDestroy(&pix1);
+
+ pix1 = pixConvertTo8(pixs, FALSE);
+ box = boxCreate(120, 30, 200, 200);
+ na1 = pixGetCmapHistogramInRect(pix1, box, 1);
+ numaDestroy(&na1);
+ boxDestroy(&box);
+ pixDestroy(&pix1);
+
+ l_int32 ncolors;
+ pixCountRGBColors(pixs, 1, &ncolors);
+
+ l_uint32 pval;
+ pix1 = pixConvertTo8(pixs, FALSE);
+ pixGetPixelAverage(pix1, NULL, 10, 10, 1, &pval);
+ pixDestroy(&pix1);
+
+ pix1 = pixConvertTo8(pixs, FALSE);
+ l_uint32 pval2;
+ pixGetPixelStats(pix1, 1, L_STANDARD_DEVIATION, &pval2);
+ pixDestroy(&pix1);
+
+ pix1 = pixConvertTo8(pixs, FALSE);
+ if(pix1!=NULL){
+ pix2 = pixConvert8To32(pix1);
+ pixGetAverageTiledRGB(pix2, 2, 2, L_MEAN_ABSVAL, &pix3, &pix4, &pix5);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ }
+
+ pixRowStats(pixs, NULL, &na1, &na2, &na3, &na4, &na5, &na6);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+ numaDestroy(&na5);
+ numaDestroy(&na6);
+
+ pixColumnStats(pixs, NULL, &na1, &na2, &na3, &na4, &na5, &na6);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+ numaDestroy(&na5);
+ numaDestroy(&na6);
+
+ static const l_int32 nbins = 10;
+ l_int32 minval, maxval;
+ l_uint32 *gau32;
+ pix1 = pixScaleBySampling(pixs, 0.2, 0.2);
+ pixGetBinnedComponentRange(pix1, nbins, 2, L_SELECT_GREEN,
+ &minval, &maxval, &gau32, 0);
+ pixDestroy(&pix1);
+ lept_free(gau32);
+
+ PIX *pixd = pixSeedspread(pixs, 4);
+ PIX *pixc = pixConvertTo32(pixd);
+ PIX *pixr = pixRankBinByStrip(pixc, L_SCAN_HORIZONTAL, 1,
+ 10, L_SELECT_MAX);
+ pixDestroy(&pixd);
+ pixDestroy(&pixc);
+ pixDestroy(&pixr);
+
+ PIXA *pixa = pixaReadMem(data, size);
+ pix1 = pixaGetAlignedStats(pixa, L_MEAN_ABSVAL, 2, 2);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+
+ l_int32 thresh, fgval, bgval;
+ pix1 = pixConvertTo8(pixs, 0);
+ pixSplitDistributionFgBg(pix1, 1.5, 1, &thresh, &fgval, &bgval, &pix2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixs);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/pixMirrorDetectDwa_fuzzer.cc b/leptonica/prog/fuzzing/pixMirrorDetectDwa_fuzzer.cc
new file mode 100644
index 00000000..83c6ce73
--- /dev/null
+++ b/leptonica/prog/fuzzing/pixMirrorDetectDwa_fuzzer.cc
@@ -0,0 +1,20 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ if(size<3) return 0;
+
+ leptSetStdNullHandler();
+
+ l_float32 conf;
+ PIX *pixs;
+
+ pixs = pixReadMemSpix(data, size);
+ if(pixs==NULL) return 0;
+
+ pixMirrorDetectDwa(pixs, &conf, 0, 0);
+
+ pixDestroy(&pixs);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/pix_orient_fuzzer.cc b/leptonica/prog/fuzzing/pix_orient_fuzzer.cc
new file mode 100644
index 00000000..13adeffc
--- /dev/null
+++ b/leptonica/prog/fuzzing/pix_orient_fuzzer.cc
@@ -0,0 +1,19 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ if(size<3) return 0;
+ l_int32 rotation;
+ l_float32 upconf1, leftconf1;
+ PIX *pix1, *pix2;
+
+ leptSetStdNullHandler();
+
+ pix1 = pixReadMemSpix(data,size);
+ if(pix1==NULL) return 0;
+ pix2 = pixOrientCorrect(pix1, 1.0, 1.0, &upconf1, &leftconf1, &rotation, 0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/pix_rotate_shear_fuzzer.cc b/leptonica/prog/fuzzing/pix_rotate_shear_fuzzer.cc
new file mode 100644
index 00000000..9ae2569c
--- /dev/null
+++ b/leptonica/prog/fuzzing/pix_rotate_shear_fuzzer.cc
@@ -0,0 +1,67 @@
+// The fuzzer takes as input a buffer of bytes. The buffer is read in as:
+// <angle>, <x_center>, <y_center>, and the remaining bytes will be read
+// in as a <pix>. The image is then rotated by angle around the center. All
+// inputs should not result in undefined behavior.
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include "leptfuzz.h"
+
+// Set to true only for debugging; always false for production
+static const bool DebugOutput = false;
+
+namespace {
+
+// Reads the front bytes of a data buffer containing `size` bytes as an int16_t,
+// and advances the buffer forward [if there is sufficient capacity]. If there
+// is insufficient capacity, this returns 0 and does not modify size or data.
+int16_t ReadInt16(const uint8_t** data, size_t* size) {
+ int16_t result = 0;
+ if (*size >= sizeof(result)) {
+ memcpy(&result, *data, sizeof(result));
+ *data += sizeof(result);
+ *size -= sizeof(result);
+ }
+ return result;
+}
+
+} // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ const int16_t angle = ReadInt16(&data, &size);
+ const int16_t x_center = ReadInt16(&data, &size);
+ const int16_t y_center = ReadInt16(&data, &size);
+
+ leptSetStdNullHandler();
+
+ // Don't do pnm format (which can cause timeouts) or
+ // jpeg format (which can have uninitialized variables.
+ // The format checker requires at least 12 bytes.
+ if (size < 12) return EXIT_SUCCESS;
+ int format;
+ findFileFormatBuffer(data, &format);
+ if (format == IFF_PNM || format == IFF_JFIF_JPEG) return EXIT_SUCCESS;
+
+ Pix* pix = pixReadMem(reinterpret_cast<const unsigned char*>(data), size);
+ if (pix == nullptr) {
+ return EXIT_SUCCESS;
+ }
+
+ // Never in production
+ if (DebugOutput) {
+ L_INFO("w = %d, h = %d, d = %d\n", "fuzzer",
+ pixGetWidth(pix), pixGetHeight(pix), pixGetDepth(pix));
+ }
+
+ constexpr float deg2rad = M_PI / 180.;
+ Pix* pix_rotated = pixRotateShear(pix, x_center, y_center, deg2rad * angle,
+ L_BRING_IN_WHITE);
+ if (pix_rotated) {
+ pixDestroy(&pix_rotated);
+ }
+
+ pixDestroy(&pix);
+ return EXIT_SUCCESS;
+}
diff --git a/leptonica/prog/fuzzing/pixa_recog_fuzzer.cc b/leptonica/prog/fuzzing/pixa_recog_fuzzer.cc
new file mode 100644
index 00000000..7ca69369
--- /dev/null
+++ b/leptonica/prog/fuzzing/pixa_recog_fuzzer.cc
@@ -0,0 +1,49 @@
+#include "leptfuzz.h"
+#include <sys/types.h>
+#include <unistd.h>
+
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ if(size<10) return 0;
+
+ leptSetStdNullHandler();
+
+ char filename[256];
+ sprintf(filename, "/tmp/libfuzzer.pa");
+ FILE *fp = fopen(filename, "wb");
+ if (!fp) return 0;
+ fwrite(data, size, 1, fp);
+ fclose(fp);
+
+ PIXA *pixa1, *pixa2, *pixa3, *pixa4, *pixa5;
+ L_RECOG *recog1, *recog2;
+ PIX *pix1, *pix2, *pix3, *pix4;
+
+ pixa1 = pixaRead(filename);
+
+ recog1 = recogCreateFromPixa(pixa1, 0, 40, 1, 128, 1);
+
+ pixa2 = recogTrainFromBoot(recog1, pixa1, 0.75, 128, 1);
+
+ pixa3 = pixaRemoveOutliers1(pixa1, 0.8, 4, 3, &pix1, &pix2);
+ pixa4 = pixaRemoveOutliers2(pixa1, 0.8, 4, &pix3, &pix4);
+
+ recog2 = recogCreateFromPixa(pixa1, 4, 40, 1, 128, 1);
+ recogIdentifyMultiple(recog2, pix2, 0, 0, NULL, &pixa5, NULL, 1);
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ recogDestroy(&recog1);
+ recogDestroy(&recog2);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ pixaDestroy(&pixa4);
+ pixaDestroy(&pixa5);
+ unlink(filename);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/pixa_recog_fuzzer_seed_corpus.zip b/leptonica/prog/fuzzing/pixa_recog_fuzzer_seed_corpus.zip
new file mode 100644
index 00000000..b3eabdd8
--- /dev/null
+++ b/leptonica/prog/fuzzing/pixa_recog_fuzzer_seed_corpus.zip
Binary files differ
diff --git a/leptonica/prog/fuzzing/pixconv_fuzzer.cc b/leptonica/prog/fuzzing/pixconv_fuzzer.cc
new file mode 100644
index 00000000..0dc21491
--- /dev/null
+++ b/leptonica/prog/fuzzing/pixconv_fuzzer.cc
@@ -0,0 +1,23 @@
+#include "leptfuzz.h"
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ if(size<5) return 0;
+ PIX *pixs, *pix1, *pix2, *pix3;
+
+ leptSetStdNullHandler();
+
+ pixs = pixReadMemSpix(data, size);
+ if(pixs==NULL) return 0;
+
+ pix1 = pixConvertGrayToFalseColor(pixs, 1.0);
+ pix2 = pixThreshold8(pixs, 1, 0, 0);
+ pixQuantizeIfFewColors(pixs, 8, 0, 1, &pix3);
+
+ pixDestroy(&pixs);
+ if (pix1!=NULL) pixDestroy(&pix1);
+ if (pix2!=NULL) pixDestroy(&pix2);
+ if (pix3!=NULL) pixDestroy(&pix3);
+ return 0;
+}
diff --git a/leptonica/prog/fuzzing/recog_basic_fuzzer.cc b/leptonica/prog/fuzzing/recog_basic_fuzzer.cc
new file mode 100644
index 00000000..78fcbca7
--- /dev/null
+++ b/leptonica/prog/fuzzing/recog_basic_fuzzer.cc
@@ -0,0 +1,25 @@
+#include "leptfuzz.h"
+#include <sys/types.h>
+#include <unistd.h>
+
+extern "C" int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ leptSetStdNullHandler();
+
+ L_RECOG *recog;
+ char filename[256];
+ sprintf(filename, "/tmp/libfuzzer.%d", getppid());
+
+ FILE *fp = fopen(filename, "wb");
+ if (!fp)
+ return 0;
+ fwrite(data, size, 1, fp);
+ fclose(fp);
+
+ recog = recogRead(filename);
+
+ recogDestroy(&recog);
+ unlink(filename);
+ return 0;
+}
diff --git a/leptonica/prog/gammatest.c b/leptonica/prog/gammatest.c
new file mode 100644
index 00000000..7c8e2ec6
--- /dev/null
+++ b/leptonica/prog/gammatest.c
@@ -0,0 +1,98 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * gammatest.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+#define MINVAL 30
+#define MAXVAL 210
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+char buf[512];
+l_int32 iplot, same;
+l_float32 gam;
+l_float64 gamma[] = {.5, 1.0, 1.5, 2.0, 2.5, -1.0};
+GPLOT *gplot;
+NUMA *na, *nax;
+PIX *pixs, *pixd;
+static char mainName[] = "gammatest";
+
+ if (argc != 4)
+ return ERROR_INT(" Syntax: gammatest filein gam fileout", mainName, 1);
+ filein = argv[1];
+ gam = atof(argv[2]);
+ fileout = argv[3];
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/gamma");
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ startTimer();
+ pixd = pixGammaTRC(NULL, pixs, gam, MINVAL, MAXVAL);
+ lept_stderr("Time for gamma: %7.3f sec\n", stopTimer());
+ pixGammaTRC(pixs, pixs, gam, MINVAL, MAXVAL);
+ pixEqual(pixs, pixd, &same);
+ if (!same)
+ lept_stderr("Error in pixGammaTRC!\n");
+ pixWrite(fileout, pixs, IFF_JFIF_JPEG);
+ pixDestroy(&pixs);
+
+ na = numaGammaTRC(gam, MINVAL, MAXVAL);
+ gplotSimple1(na, GPLOT_PNG, "/tmp/lept/gamma/trc", "gamma trc");
+ l_fileDisplay("/tmp/lept/gamma/trc.png", 100, 100, 1.0);
+ numaDestroy(&na);
+
+ /* Plot gamma TRC maps */
+ gplot = gplotCreate("/tmp/lept/gamma/corr", GPLOT_PNG,
+ "Mapping function for gamma correction",
+ "value in", "value out");
+ nax = numaMakeSequence(0.0, 1.0, 256);
+ for (iplot = 0; gamma[iplot] >= 0.0; iplot++) {
+ na = numaGammaTRC(gamma[iplot], 30, 215);
+ snprintf(buf, sizeof(buf), "gamma = %3.1f", gamma[iplot]);
+ gplotAddPlot(gplot, nax, na, GPLOT_LINES, buf);
+ numaDestroy(&na);
+ }
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ l_fileDisplay("/tmp/lept/gamma/corr.png", 100, 100, 1.0);
+ numaDestroy(&nax);
+ return 0;
+}
+
diff --git a/leptonica/prog/genfonts_reg.c b/leptonica/prog/genfonts_reg.c
new file mode 100644
index 00000000..8b5b7cb5
--- /dev/null
+++ b/leptonica/prog/genfonts_reg.c
@@ -0,0 +1,173 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * genfonts_reg.c
+ *
+ * This regtest is for the generation of bitmap font characters that
+ * are presently used for annotating images.
+ *
+ * The tiff images of bitmaps fonts, which are used as input
+ * to this generator, are supplied in Leptonica in the prog/fonts
+ * directory. The tiff images were generated from the PostScript files
+ * in that directory, using the shell script prog/ps2tiff. If you
+ * want to generate other fonts, modify the PostScript files
+ * and use ps2tiff. ps2tiff uses GhostScript.
+ *
+ * The input tiff images are stored either as files in prog/fonts/,
+ * or as compiled C strings in bmfdata.h. Each image stores 94 of the
+ * 95 printable characters, all in one of 9 sizes (ranging from 4 to 20
+ * points). These are programmatically split into individual characters,
+ * and the baselines are computed for each character. Baselines are
+ * required to properly render them.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+#include "bmfdata.h"
+
+#define NFONTS 9
+
+const l_int32 sizes[] = {4, 6, 8, 10, 12, 14, 16, 18, 20};
+
+int main(int argc,
+ char **argv)
+{
+char buf[512];
+char *pathname, *datastr, *formstr;
+l_uint8 *data1, *data2;
+l_int32 i, bl1, bl2, bl3, sbytes, formbytes, fontsize, rbytes;
+size_t nbytes;
+PIX *pix1, *pix2, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "genfonts_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* ------------ Generate pixa char bitmap files from file ----------- */
+ lept_rmdir("lept/filefonts");
+ lept_mkdir("lept/filefonts");
+ for (i = 0; i < 9; i++) {
+ pixaSaveFont("fonts", "/tmp/lept/filefonts", sizes[i]);
+ pathname = pathJoin("/tmp/lept/filefonts", outputfonts[i]);
+ pixa = pixaRead(pathname);
+ if (rp->display) {
+ lept_stderr("Found %d chars in font size %d\n",
+ pixaGetCount(pixa), sizes[i]);
+ }
+ pixd = pixaDisplayTiled(pixa, 1500, 0, 15);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 0 - 8 */
+ if (i == 2) pixDisplayWithTitle(pixd, 100, 0, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ lept_free(pathname);
+ }
+ lept_rmdir("lept/filefonts");
+
+ /* ---------- Generate pixa char bitmap files from string --------- */
+ lept_rmdir("lept/strfonts");
+ lept_mkdir("lept/strfonts");
+ for (i = 0; i < 9; i++) {
+ pixaSaveFont(NULL, "/tmp/lept/strfonts", sizes[i]);
+ pathname = pathJoin("/tmp/lept/strfonts", outputfonts[i]);
+ pixa = pixaRead(pathname);
+ if (rp->display) {
+ lept_stderr("Found %d chars in font size %d\n",
+ pixaGetCount(pixa), sizes[i]);
+ }
+ pixd = pixaDisplayTiled(pixa, 1500, 0, 15);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 9 - 17 */
+ if (i == 2) pixDisplayWithTitle(pixd, 100, 150, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ lept_free(pathname);
+ }
+
+ /* ----- Use pixaGetFont() and write the result out -----*/
+ lept_rmdir("lept/pafonts");
+ lept_mkdir("lept/pafonts");
+ for (i = 0; i < 9; i++) {
+ pixa = pixaGetFont("/tmp/lept/strfonts", sizes[i], &bl1, &bl2, &bl3);
+ lept_stderr("Baselines are at: %d, %d, %d\n", bl1, bl2, bl3);
+ snprintf(buf, sizeof(buf), "/tmp/lept/pafonts/chars-%d.pa", sizes[i]);
+ pixaWrite(buf, pixa);
+ if (i == 2) {
+ pixd = pixaDisplayTiled(pixa, 1500, 0, 15);
+ pixDisplayWithTitle(pixd, 100, 300, NULL, rp->display);
+ pixDestroy(&pixd);
+ }
+ pixaDestroy(&pixa);
+ }
+ lept_rmdir("lept/pafonts");
+
+ /* ------- Generate 4/3 encoded ascii strings from tiff files ------ */
+ lept_rmdir("lept/encfonts");
+ lept_mkdir("lept/encfonts");
+ for (i = 0; i < 9; i++) {
+ fontsize = 2 * i + 4;
+ pathname = pathJoin("fonts", inputfonts[i]);
+ data1 = l_binaryRead(pathname, &nbytes);
+ datastr = encodeBase64(data1, nbytes, &sbytes);
+ if (rp->display)
+ lept_stderr("nbytes = %lu, sbytes = %d\n",
+ (unsigned long)nbytes, sbytes);
+ formstr = reformatPacked64(datastr, sbytes, 4, 72, 1, &formbytes);
+ snprintf(buf, sizeof(buf), "/tmp/lept/encfonts/formstr_%d.txt",
+ fontsize);
+ l_binaryWrite(buf, "w", formstr, formbytes);
+ regTestCheckFile(rp, buf); /* 18-26 */
+ if (i == 8)
+ pix1 = pixReadMem(data1, nbytes); /* original */
+ lept_free(data1);
+
+ data2 = decodeBase64(datastr, sbytes, &rbytes);
+ snprintf(buf, sizeof(buf), "/tmp/lept/encfonts/image_%d.tif", fontsize);
+ l_binaryWrite(buf, "w", data2, rbytes);
+ if (i == 8) {
+ pix2 = pixReadMem(data2, rbytes); /* encode/decode */
+ regTestComparePix(rp, pix1, pix2); /* 27 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ lept_free(data2);
+
+ lept_free(pathname);
+ lept_free(datastr);
+ lept_free(formstr);
+ }
+
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/german.png b/leptonica/prog/german.png
new file mode 100644
index 00000000..ad1c1c5b
--- /dev/null
+++ b/leptonica/prog/german.png
Binary files differ
diff --git a/leptonica/prog/gifio_reg.c b/leptonica/prog/gifio_reg.c
new file mode 100644
index 00000000..768bafcb
--- /dev/null
+++ b/leptonica/prog/gifio_reg.c
@@ -0,0 +1,232 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This is the Leptonica regression test for lossless read/write
+ * I/O in gif format.
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ *
+ * This tests reading and writing of images in gif format for
+ * varioius depths.
+ *
+ * The relative times for writing of gif and png are interesting.
+ *
+ * For 1 bpp:
+ *
+ * png writing is about 2x faster than gif writing, using giflib.
+ *
+ * For 32 bpp, using a 1 Mpix rgb image:
+ *
+ * png: Lossless: 1.16 sec (2.0 MB output file)
+ * Lossy: 0.43 sec, composed of:
+ * 0.22 sec (octree quant with dithering)
+ * 0.21 sec (to compress and write out)
+ *
+ * gif: Lossy: 0.34 sec, composed of:
+ * 0.22 sec (octree quant with dithering)
+ * 0.12 sec (to compress and write out)
+ * (note: no lossless mode; gif can't write out rgb)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+#if HAVE_LIBGIF || HAVE_LIBUNGIF
+#include "gif_lib.h"
+#endif /* HAVE_LIBGIF || HAVE_LIBUNGIF */
+
+#define FILE_1BPP "feyn.tif"
+#define FILE_2BPP "weasel2.4g.png"
+#define FILE_4BPP "weasel4.16c.png"
+#define FILE_8BPP_1 "dreyfus8.png"
+#define FILE_8BPP_2 "weasel8.240c.png"
+#define FILE_8BPP_3 "test8.jpg"
+#define FILE_16BPP "test16.tif"
+#define FILE_32BPP "marge.jpg"
+
+static void test_gif(const char *fname, PIXA *pixa, L_REGPARAMS *rp);
+static l_int32 test_mem_gif(const char *fname, l_int32 index);
+
+int main(int argc,
+ char **argv)
+{
+char buf[64];
+l_int32 success;
+PIX *pix;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+#if !HAVE_LIBGIF && !HAVE_LIBUNGIF
+ lept_stderr("gifio is not enabled\n"
+ "libgif or libungif are required for gifio_reg\n"
+ "See environ.h: #define HAVE_LIBGIF or HAVE_LIBUNGIF 1\n"
+ "See prog/Makefile: link in -lgif or -lungif\n\n");
+ regTestCleanup(rp);
+ return 0;
+#endif /* abort */
+
+ /* 5.1+ and not 5.1.2 */
+ snprintf(buf, sizeof(buf), "%s_reg", rp->testname);
+#if (GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0))
+ L_ERROR("Require giflib-5.1 or later.\n", buf);
+ return 1;
+#endif /* < 5.1 */
+#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.3 or later.\n", buf);
+ return 1;
+#endif /* 5.1.2 */
+
+ /* Set up for display output */
+ pixa = (rp->display) ? pixaCreate(0) : NULL;
+
+ lept_rmdir("lept/gif");
+ lept_mkdir("lept/gif");
+
+ /* ------------ Part 1: Test lossless r/w to file ------------*/
+ test_gif(FILE_1BPP, pixa, rp);
+ test_gif(FILE_2BPP, pixa, rp);
+ test_gif(FILE_4BPP, pixa, rp);
+ test_gif(FILE_8BPP_1, pixa, rp);
+ test_gif(FILE_8BPP_2, pixa, rp);
+ test_gif(FILE_8BPP_3, pixa, rp);
+ test_gif(FILE_16BPP, pixa, rp);
+ test_gif(FILE_32BPP, pixa, rp);
+ if (rp->success)
+ lept_stderr("\n ****** Success on lossless r/w to file *****\n\n");
+ else
+ lept_stderr("\n ***** Failure on at least one r/w to file ****\n\n");
+
+ if (rp->display) {
+ pix = pixaDisplayTiledAndScaled(pixa, 32, 450, 3, 0, 20, 2);
+ pixWrite("/tmp/lept/gif/giftest.jpg", pix, IFF_JFIF_JPEG);
+ pixDisplay(pix, 100, 100);
+ pixDestroy(&pix);
+ pixaDestroy(&pixa);
+ }
+
+ /* ------------ Part 2: Test lossless r/w to memory ------------ */
+ success = TRUE;
+ if (test_mem_gif(FILE_1BPP, 0)) success = FALSE;
+ if (test_mem_gif(FILE_2BPP, 1)) success = FALSE;
+ if (test_mem_gif(FILE_4BPP, 2)) success = FALSE;
+ if (test_mem_gif(FILE_8BPP_1, 3)) success = FALSE;
+ if (test_mem_gif(FILE_8BPP_2, 4)) success = FALSE;
+ if (test_mem_gif(FILE_8BPP_3, 5)) success = FALSE;
+ if (test_mem_gif(FILE_16BPP, 6)) success = FALSE;
+ if (test_mem_gif(FILE_32BPP, 7)) success = FALSE;
+ if (success)
+ lept_stderr("\n ****** Success on lossless r/w to memory *****\n\n");
+ else
+ lept_stderr("\n **** Failure on at least one r/w to memory ****\n\n");
+
+ /* Success only if all tests are passed */
+ if (rp->success == TRUE) rp->success = success;
+
+ return regTestCleanup(rp);
+}
+
+
+static void
+test_gif(const char *fname,
+ PIXA *pixa,
+ L_REGPARAMS *rp)
+{
+char buf[256];
+l_int32 same;
+PIX *pixs, *pix1, *pix2;
+
+ pixs = pixRead(fname);
+ snprintf(buf, sizeof(buf), "/tmp/lept/gif/gifio-a.%d.gif", rp->index + 1);
+ pixWrite(buf, pixs, IFF_GIF);
+ pix1 = pixRead(buf);
+ snprintf(buf, sizeof(buf), "/tmp/lept/gif/gifio-b.%d.gif", rp->index + 1);
+ pixWrite(buf, pix1, IFF_GIF);
+ pix2 = pixRead(buf);
+ regTestWritePixAndCheck(rp, pix2, IFF_GIF);
+ pixEqual(pixs, pix2, &same);
+
+ if (!same && rp->index < 6) {
+ lept_stderr("Error for %s\n", fname);
+ rp->success = FALSE;
+ }
+ if (rp->display) {
+ lept_stderr(" depth: pixs = %d, pix1 = %d\n", pixGetDepth(pixs),
+ pixGetDepth(pix1));
+ pixaAddPix(pixa, pix2, L_CLONE);
+ }
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return;
+}
+
+
+ /* Returns 1 on error */
+static l_int32
+test_mem_gif(const char *fname,
+ l_int32 index)
+{
+l_uint8 *data = NULL;
+l_int32 same;
+size_t size = 0;
+PIX *pixs;
+PIX *pixd = NULL;
+
+ if ((pixs = pixRead(fname)) == NULL) {
+ lept_stderr("Failure to read gif file: %s\n", fname);
+ return 1;
+ }
+ if (pixWriteMem(&data, &size, pixs, IFF_GIF)) {
+ lept_stderr("Mem gif write fail on image %d\n", index);
+ return 1;
+ }
+ if ((pixd = pixReadMem(data, size)) == NULL) {
+ lept_stderr("Mem gif read fail on image %d\n", index);
+ lept_free(data);
+ return 1;
+ }
+
+ pixEqual(pixs, pixd, &same);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ lept_free(data);
+ if (!same && index < 6) {
+ lept_stderr("Mem gif write/read fail for file %s\n", fname);
+ return 1;
+ }
+ else
+ return 0;
+}
diff --git a/leptonica/prog/google-searchbox.png b/leptonica/prog/google-searchbox.png
new file mode 100644
index 00000000..c9c549c4
--- /dev/null
+++ b/leptonica/prog/google-searchbox.png
Binary files differ
diff --git a/leptonica/prog/gplotdata.example b/leptonica/prog/gplotdata.example
new file mode 100644
index 00000000..4e477b7a
--- /dev/null
+++ b/leptonica/prog/gplotdata.example
@@ -0,0 +1,82 @@
+%
+% gplotdata.example
+%
+% This is an example data file to be used as input for gplot
+% from file.
+%
+% All lines starting with '%' are comments and are ignored
+% All blank lines are ignored
+% Use '&' at beginning of a line as a control character:
+% The first '&' signals that the data is to start
+% Subsequent '&' are separators between data for different plots;
+% this way, we can have multiple plots on the same output graph
+%
+% All plot info lines before the "&" that signals start of
+% data are optional
+%
+% If they exist, the first three must be:
+% title
+% xaxis label
+% yaxis label
+% These default to null strings.
+%
+% Optionally after that, the individual plots can
+% be labeled and the drawing method specified.
+% This is done by specifying:
+% number of plots
+% title for plot 1
+% drawing style for plot 1
+% title for plot 2
+% drawing style for plot 2
+% ...
+%
+% The defaults are to have the plots untitled and to
+% use LINES for the plot. The drawing styles allowed
+% are in the set {LINES, POINTS, IMPULSES, LINESPOINTS, DOTS}.
+%
+
+this is the title
+this is the xaxis label
+this is the yaxis label
+4
+% (this is a comment line)
+plotto uno
+LINES
+% (this is a comment line)
+plotto due
+POINTS
+% (this is a comment line)
+plotto tre
+LINESPOINTS
+% (this is a comment line)
+plotto quattro
+DOTS
+&&&&&&&&
+1. 4
+2,7
+3 11
+b(4) = 19
+(5., 31)
+6.2 40
+7.3 , 70
+&&&&&&&&
+&&&&&&&& oops, I put in an extra one
+1 7
+2.1 15
+3.3 22
+4.5, 36
+5.9 62
+&&&&&&&&
+0.5 4
+1.5 10
+2.5 25
+3.5 35
+4.5 47.2
+5.5, 68
+&&&&&&&&
+0.2 5
+1.2 12.3
+2.3 27.9
+3.6 42.8
+4.8 61.9
+5.1 68.3
diff --git a/leptonica/prog/graphicstest.c b/leptonica/prog/graphicstest.c
new file mode 100644
index 00000000..7ac6349d
--- /dev/null
+++ b/leptonica/prog/graphicstest.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * graphicstest.c
+ *
+ * e.g.: graphicstest fish24.jpg junkout
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 d;
+BOX *box1, *box2, *box3, *box4;
+BOXA *boxa;
+PIX *pixs, *pix1;
+PTA *pta;
+static char mainName[] = "graphicstest";
+
+ if (argc != 3)
+ return ERROR_INT(" Syntax: graphicstest filein fileout", mainName, 1);
+ filein = argv[1];
+ fileout = argv[2];
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT(" Syntax: pixs not made", mainName, 1);
+ d = pixGetDepth(pixs);
+ if (d <= 8)
+ pix1 = pixConvertTo32(pixs);
+ else
+ pix1 = pixClone(pixs);
+
+ /* Paint on RGB */
+ pixRenderLineArb(pix1, 450, 20, 850, 320, 5, 200, 50, 125);
+ pixRenderLineArb(pix1, 30, 40, 440, 40, 5, 100, 200, 25);
+ pixRenderLineBlend(pix1, 30, 60, 440, 70, 5, 115, 200, 120, 0.3);
+ pixRenderLineBlend(pix1, 30, 600, 440, 670, 9, 215, 115, 30, 0.5);
+ pixRenderLineBlend(pix1, 130, 700, 540, 770, 9, 255, 255, 250, 0.4);
+ pixRenderLineBlend(pix1, 130, 800, 540, 870, 9, 0, 0, 0, 0.4);
+ box1 = boxCreate(70, 80, 300, 245);
+ box2 = boxCreate(470, 180, 150, 205);
+ box3 = boxCreate(520, 220, 160, 220);
+ box4 = boxCreate(570, 260, 160, 220);
+ boxa = boxaCreate(3);
+ boxaAddBox(boxa, box2, L_INSERT);
+ boxaAddBox(boxa, box3, L_INSERT);
+ boxaAddBox(boxa, box4, L_INSERT);
+ pixRenderBoxArb(pix1, box1, 3, 200, 200, 25);
+ pixRenderBoxaBlend(pix1, boxa, 17, 200, 200, 25, 0.4, 1);
+ pta = ptaCreate(5);
+ ptaAddPt(pta, 250, 300);
+ ptaAddPt(pta, 350, 450);
+ ptaAddPt(pta, 400, 600);
+ ptaAddPt(pta, 212, 512);
+ ptaAddPt(pta, 180, 375);
+ pixRenderPolylineBlend(pix1, pta, 17, 25, 200, 200, 0.5, 1, 1);
+ pixWrite(fileout, pix1, IFF_JFIF_JPEG);
+ pixDisplay(pix1, 200, 200);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ boxDestroy(&box1);
+ boxaDestroy(&boxa);
+ ptaDestroy(&pta);
+ pixDestroy(&pixs);
+ return 0;
+}
+
+
diff --git a/leptonica/prog/gray-alpha.tif b/leptonica/prog/gray-alpha.tif
new file mode 100644
index 00000000..199d30a0
--- /dev/null
+++ b/leptonica/prog/gray-alpha.tif
Binary files differ
diff --git a/leptonica/prog/grayfill_reg.c b/leptonica/prog/grayfill_reg.c
new file mode 100644
index 00000000..a8a99b94
--- /dev/null
+++ b/leptonica/prog/grayfill_reg.c
@@ -0,0 +1,207 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * grayfill_reg.c
+ *
+ * Regression test for gray filling operations
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void PixTestEqual(L_REGPARAMS *rp, PIX *pixs1, PIX *pixs2, PIX *pixm,
+ l_int32 set, l_int32 connectivity);
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j;
+PIX *pixm, *pixmi, *pixs1, *pixs1_8, *pix1;
+PIX *pixs2, *pixs2_8, *pixs3, *pixs3_8;
+PIX *pixb1, *pixb2, *pixb3, *pixmin, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Mask */
+ pixm = pixCreate(200, 200, 8);
+ for (i = 0; i < 200; i++)
+ for (j = 0; j < 200; j++)
+ pixSetPixel(pixm, j, i, 20 + L_ABS((100 - i) * (100 - j)) / 50);
+ pixmi = pixInvert(NULL, pixm);
+
+ /* Seed1 */
+ pixs1 = pixCreate(200, 200, 8);
+ for (i = 99; i <= 101; i++)
+ for (j = 99; j <= 101; j++)
+ pixSetPixel(pixs1, j, i, 50 - i/100 - j/100);
+ pixs1_8 = pixCopy(NULL, pixs1);
+
+ /* Seed2 */
+ pixs2 = pixCreate(200, 200, 8);
+ for (i = 99; i <= 101; i++)
+ for (j = 99; j <= 101; j++)
+ pixSetPixel(pixs2, j, i, 205 - i/100 - j/100);
+ pixs2_8 = pixCopy(NULL, pixs2);
+
+ /* Inverse grayscale fill */
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixm, L_COPY);
+ regTestWritePixAndCheck(rp, pixm, IFF_PNG); /* 0 */
+ pixaAddPix(pixa, pixs1, L_COPY);
+ regTestWritePixAndCheck(rp, pixs1, IFF_PNG); /* 1 */
+ pixSeedfillGrayInv(pixs1, pixm, 4);
+ pixSeedfillGrayInv(pixs1_8, pixm, 8);
+ pixaAddPix(pixa, pixs1, L_COPY);
+ regTestWritePixAndCheck(rp, pixs1, IFF_PNG); /* 2 */
+ pixaAddPix(pixa, pixs1_8, L_COPY);
+ regTestWritePixAndCheck(rp, pixs1_8, IFF_PNG); /* 3 */
+ pixb1 = pixThresholdToBinary(pixs1, 20);
+ pixaAddPix(pixa, pixb1, L_COPY);
+ regTestWritePixAndCheck(rp, pixb1, IFF_PNG); /* 4 */
+ pixCombineMasked(pixs1, pixm, pixb1);
+ pixaAddPix(pixa, pixs1, L_COPY);
+ regTestWritePixAndCheck(rp, pixs1, IFF_PNG); /* 5 */
+ pix1 = pixaDisplayTiledInColumns(pixa, 6, 1.0, 15, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pix1, 100, 0, "inverse gray fill", rp->display);
+ pixDestroy(&pixs1);
+ pixDestroy(&pixs1_8);
+ pixDestroy(&pixb1);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ /* Standard grayscale fill */
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixmi, L_COPY);
+ regTestWritePixAndCheck(rp, pixmi, IFF_PNG); /* 7 */
+ pixaAddPix(pixa, pixs2, L_COPY);
+ regTestWritePixAndCheck(rp, pixs2, IFF_PNG); /* 8 */
+ pixSeedfillGray(pixs2, pixmi, 4);
+ pixSeedfillGray(pixs2_8, pixmi, 8);
+ pixaAddPix(pixa, pixs2, L_COPY);
+ regTestWritePixAndCheck(rp, pixs2, IFF_PNG); /* 9 */
+ pixaAddPix(pixa, pixs2_8, L_COPY);
+ regTestWritePixAndCheck(rp, pixs2_8, IFF_PNG); /* 10 */
+ pixb2 = pixThresholdToBinary(pixs2, 205);
+ regTestWritePixAndCheck(rp, pixb2, IFF_PNG); /* 11 */
+ pixaAddPix(pixa, pixb2, L_INSERT);
+ pix1 = pixaDisplayTiledInColumns(pixa, 5, 1.0, 15, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 12 */
+ pixDisplayWithTitle(pix1, 100, 200, "standard gray fill", rp->display);
+ pixDestroy(&pixs2);
+ pixDestroy(&pixs2_8);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ /* Basin fill from minima as seed */
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixm, L_COPY);
+ regTestWritePixAndCheck(rp, pixm, IFF_PNG); /* 13 */
+ pixLocalExtrema(pixm, 0, 0, &pixmin, NULL);
+ pixaAddPix(pixa, pixmin, L_COPY);
+ regTestWritePixAndCheck(rp, pixmin, IFF_PNG); /* 14 */
+ pixs3 = pixSeedfillGrayBasin(pixmin, pixm, 30, 4);
+ pixs3_8 = pixSeedfillGrayBasin(pixmin, pixm, 30, 8);
+ pixaAddPix(pixa, pixs3, L_INSERT);
+ regTestWritePixAndCheck(rp, pixs3, IFF_PNG); /* 15 */
+ pixaAddPix(pixa, pixs3_8, L_INSERT);
+ regTestWritePixAndCheck(rp, pixs3_8, IFF_PNG); /* 15 */
+ pixb3 = pixThresholdToBinary(pixs3, 60);
+ regTestWritePixAndCheck(rp, pixb3, IFF_PNG); /* 17 */
+ pixaAddPix(pixa, pixb3, L_INSERT);
+ pix1 = pixaDisplayTiledInColumns(pixa, 5, 1.0, 15, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 18 */
+ pixDisplayWithTitle(pix1, 100, 400, "gray fill form seed", rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ /* Compare hybrid and iterative gray seedfills */
+ pixs1 = pixCopy(NULL, pixm);
+ pixs2 = pixCopy(NULL, pixm);
+ pixAddConstantGray(pixs1, -30);
+ pixAddConstantGray(pixs2, 60);
+
+ PixTestEqual(rp, pixs1, pixs2, pixm, 1, 4); /* 19 - 22 */
+ PixTestEqual(rp, pixs1, pixs2, pixm, 2, 8); /* 23 - 26 */
+ PixTestEqual(rp, pixs2, pixs1, pixm, 3, 4); /* 27 - 30 */
+ PixTestEqual(rp, pixs2, pixs1, pixm, 4, 8); /* 31 - 34 */
+ pixDestroy(&pixs1);
+ pixDestroy(&pixs2);
+
+ pixDestroy(&pixm);
+ pixDestroy(&pixmi);
+ pixDestroy(&pixmin);
+ return regTestCleanup(rp);;
+}
+
+void
+PixTestEqual(L_REGPARAMS *rp,
+ PIX *pixs1,
+ PIX *pixs2,
+ PIX *pixm,
+ l_int32 set,
+ l_int32 connectivity)
+{
+PIX *pixc11, *pixc12, *pixc21, *pixc22, *pixmi;
+
+ pixmi = pixInvert(NULL, pixm);
+ pixc11 = pixCopy(NULL, pixs1);
+ pixc12 = pixCopy(NULL, pixs1);
+ pixc21 = pixCopy(NULL, pixs2);
+ pixc22 = pixCopy(NULL, pixs2);
+
+ /* Test inverse seed filling */
+ pixSeedfillGrayInv(pixc11, pixm, connectivity);
+ regTestWritePixAndCheck(rp, pixc11, IFF_PNG); /* '1' */
+ pixSeedfillGrayInvSimple(pixc12, pixm, connectivity);
+ regTestComparePix(rp, pixc11, pixc12); /* '2' */
+
+ /* Test seed filling */
+ pixSeedfillGray(pixc21, pixm, connectivity);
+ regTestWritePixAndCheck(rp, pixc21, IFF_PNG); /* '3' */
+ pixSeedfillGraySimple(pixc22, pixm, connectivity);
+ regTestComparePix(rp, pixc21, pixc22); /* '4' */
+
+ /* Display the filling results */
+/* pixDisplay(pixc11, 220 * (set - 1), 100);
+ pixDisplay(pixc21, 220 * (set - 1), 320); */
+
+ pixDestroy(&pixmi);
+ pixDestroy(&pixc11);
+ pixDestroy(&pixc12);
+ pixDestroy(&pixc21);
+ pixDestroy(&pixc22);
+ return;
+}
+
+
diff --git a/leptonica/prog/graymorph1_reg.c b/leptonica/prog/graymorph1_reg.c
new file mode 100644
index 00000000..8d50f5d7
--- /dev/null
+++ b/leptonica/prog/graymorph1_reg.c
@@ -0,0 +1,332 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * graymorph1_reg.c
+ *
+ * (1) Tests the interpreter for grayscale morphology, as
+ * given in morphseq.c
+ *
+ * (2) Tests composite operations: tophat and hdome
+ *
+ * (3) Tests duality for grayscale erode/dilate, open/close,
+ * and black/white tophat
+ *
+ * (4) Demonstrates closing plus white tophat. Note that this
+ * combination of operations can be quite useful.
+ *
+ * (5) Demonstrates a method of doing contrast enhancement
+ * by taking 3 * pixs and subtracting from this the
+ * closing and opening of pixs. Do this both with the
+ * basic pix accumulation functions and with the cleaner
+ * Pixacc wrapper. Verify the results are equivalent.
+ *
+ * (6) Playing around: extract the feynman diagrams from
+ * the stamp, using the tophat.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define WSIZE 7
+#define HSIZE 7
+
+int main(int argc,
+ char **argv)
+{
+char seq[512];
+l_int32 w, h;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXA *pixa;
+PIXACC *pacc;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("aneurisms8.jpg");
+ pixa = pixaCreate(0);
+
+ /* =========================================================== */
+
+ /* -------- Test gray morph, including interpreter ------------ */
+ pix1 = pixDilateGray(pixs, WSIZE, HSIZE);
+ snprintf(seq, sizeof(seq), "D%d.%d", WSIZE, HSIZE);
+ pix2 = pixGrayMorphSequence(pixs, seq, 0, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ regTestComparePix(rp, pix1, pix2); /* 1 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+
+ pix1 = pixErodeGray(pixs, WSIZE, HSIZE);
+ snprintf(seq, sizeof(seq), "E%d.%d", WSIZE, HSIZE);
+ pix2 = pixGrayMorphSequence(pixs, seq, 0, 100);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ regTestComparePix(rp, pix1, pix2); /* 3 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+
+ pix1 = pixOpenGray(pixs, WSIZE, HSIZE);
+ snprintf(seq, sizeof(seq), "O%d.%d", WSIZE, HSIZE);
+ pix2 = pixGrayMorphSequence(pixs, seq, 0, 200);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 4 */
+ regTestComparePix(rp, pix1, pix2); /* 5 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+
+ pix1 = pixCloseGray(pixs, WSIZE, HSIZE);
+ snprintf(seq, sizeof(seq), "C%d.%d", WSIZE, HSIZE);
+ pix2 = pixGrayMorphSequence(pixs, seq, 0, 300);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 6 */
+ regTestComparePix(rp, pix1, pix2); /* 7 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+
+ pix1 = pixTophat(pixs, WSIZE, HSIZE, L_TOPHAT_WHITE);
+ snprintf(seq, sizeof(seq), "Tw%d.%d", WSIZE, HSIZE);
+ pix2 = pixGrayMorphSequence(pixs, seq, 0, 400);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 8 */
+ regTestComparePix(rp, pix1, pix2); /* 9 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+
+ pix1 = pixTophat(pixs, WSIZE, HSIZE, L_TOPHAT_BLACK);
+ snprintf(seq, sizeof(seq), "Tb%d.%d", WSIZE, HSIZE);
+ pix2 = pixGrayMorphSequence(pixs, seq, 0, 500);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10 */
+ regTestComparePix(rp, pix1, pix2); /* 11 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+
+ /* ------------- Test erode/dilate duality -------------- */
+ pix1 = pixDilateGray(pixs, WSIZE, HSIZE);
+ pix2 = pixInvert(NULL, pixs);
+ pix3 = pixErodeGray(pix2, WSIZE, HSIZE);
+ pixInvert(pix3, pix3);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 12 */
+ regTestComparePix(rp, pix1, pix3); /* 13 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* ------------- Test open/close duality -------------- */
+ pix1 = pixOpenGray(pixs, WSIZE, HSIZE);
+ pix2 = pixInvert(NULL, pixs);
+ pix3 = pixCloseGray(pix2, WSIZE, HSIZE);
+ pixInvert(pix3, pix3);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 14 */
+ regTestComparePix(rp, pix1, pix3); /* 15 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* ------------- Test tophat duality -------------- */
+ pix1 = pixTophat(pixs, WSIZE, HSIZE, L_TOPHAT_WHITE);
+ pix2 = pixInvert(NULL, pixs);
+ pix3 = pixTophat(pix2, WSIZE, HSIZE, L_TOPHAT_BLACK);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 16 */
+ regTestComparePix(rp, pix1, pix3); /* 17 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ pix1 = pixGrayMorphSequence(pixs, "Tw9.5", 0, 100);
+ pix2 = pixInvert(NULL, pixs);
+ pix3 = pixGrayMorphSequence(pix2, "Tb9.5", 0, 300);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 18 */
+ regTestComparePix(rp, pix1, pix3); /* 19 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+
+ /* ------------- Test opening/closing for large sels -------------- */
+ pix1 = pixGrayMorphSequence(pixs,
+ "C9.9 + C19.19 + C29.29 + C39.39 + C49.49", 0, 100);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 20 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixGrayMorphSequence(pixs,
+ "O9.9 + O19.19 + O29.29 + O39.39 + O49.49", 0, 400);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 21 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 4, 1.0, 20, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 22 */
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+
+ /* =========================================================== */
+
+ pixa = pixaCreate(0);
+ /* ---------- Closing plus white tophat result ------------ *
+ * Parameters: wsize, hsize = 9, 29 *
+ * ---------------------------------------------------------*/
+ pix1 = pixCloseGray(pixs, 9, 9);
+ pix2 = pixTophat(pix1, 9, 9, L_TOPHAT_WHITE);
+ pix3 = pixGrayMorphSequence(pixs, "C9.9 + TW9.9", 0, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 23 */
+ regTestComparePix(rp, pix2, pix3); /* 24 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixMaxDynamicRange(pix2, L_LINEAR_SCALE);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 25 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ pix1 = pixCloseGray(pixs, 29, 29);
+ pix2 = pixTophat(pix1, 29, 29, L_TOPHAT_WHITE);
+ pix3 = pixGrayMorphSequence(pixs, "C29.29 + Tw29.29", 0, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 26 */
+ regTestComparePix(rp, pix2, pix3); /* 27 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixMaxDynamicRange(pix2, L_LINEAR_SCALE);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 28 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* --------- hdome with parameter height = 100 ------------*/
+ pix1 = pixHDome(pixs, 100, 4);
+ pix2 = pixMaxDynamicRange(pix1, L_LINEAR_SCALE);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 29 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 30 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ /* ----- Contrast enhancement with morph parameters 9, 9 -------*/
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pix1 = pixInitAccumulate(w, h, 0x8000);
+ pixAccumulate(pix1, pixs, L_ARITH_ADD);
+ pixMultConstAccumulate(pix1, 3., 0x8000);
+ pix2 = pixOpenGray(pixs, 9, 9);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 31 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixAccumulate(pix1, pix2, L_ARITH_SUBTRACT);
+
+ pix2 = pixCloseGray(pixs, 9, 9);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 32 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixAccumulate(pix1, pix2, L_ARITH_SUBTRACT);
+ pix2 = pixFinalAccumulate(pix1, 0x8000, 8);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 33 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pix1);
+
+ /* Do the same thing with the Pixacc */
+ pacc = pixaccCreate(w, h, 1);
+ pixaccAdd(pacc, pixs);
+ pixaccMultConst(pacc, 3.);
+ pix1 = pixOpenGray(pixs, 9, 9);
+ pixaccSubtract(pacc, pix1);
+ pixDestroy(&pix1);
+ pix1 = pixCloseGray(pixs, 9, 9);
+ pixaccSubtract(pacc, pix1);
+ pixDestroy(&pix1);
+ pix1 = pixaccFinal(pacc, 8);
+ pixaccDestroy(&pacc);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 34 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 35 */
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 4, 1.0, 20, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 36 */
+ pixDisplayWithTitle(pix1, 1100, 0, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+ pixDestroy(&pixs);
+
+ /* =========================================================== */
+
+ pixa = pixaCreate(0);
+
+ /* ---- Tophat result on feynman stamp, to extract diagrams ----- */
+ pixs = pixRead("feynman-stamp.jpg");
+ pixGetDimensions(pixs, &w, &h, NULL);
+
+ /* Make output image to hold five intermediate images */
+ pix1 = pixCreate(5 * w + 18, h + 6, 32); /* composite output image */
+ pixSetAllArbitrary(pix1, 0x0000ff00); /* set to blue */
+
+ /* Paste in the input image */
+ pix2 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+ pixRasterop(pix1, 3, 3, w, h, PIX_SRC, pix2, 0, 0); /* 1st one */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 37 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ /* Paste in the grayscale version */
+ cmap = pixGetColormap(pixs);
+ if (cmap)
+ pix2 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+ else
+ pix2 = pixConvertRGBToGray(pixs, 0.33, 0.34, 0.33);
+ pix3 = pixConvertTo32(pix2); /* 8 --> 32 bpp */
+ pixRasterop(pix1, w + 6, 3, w, h, PIX_SRC, pix3, 0, 0); /* 2nd one */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 38 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+
+ /* Paste in a log dynamic range scaled version of the white tophat */
+ pix3 = pixTophat(pix2, 3, 3, L_TOPHAT_WHITE);
+ pix4 = pixMaxDynamicRange(pix3, L_LOG_SCALE);
+ pix5 = pixConvertTo32(pix4);
+ pixRasterop(pix1, 2 * w + 9, 3, w, h, PIX_SRC, pix5, 0, 0); /* 3rd */
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 39 */
+ pixaAddPix(pixa, pix5, L_INSERT);
+ pixDestroy(&pix2);
+ pixDestroy(&pix4);
+
+ /* Stretch the range and threshold to binary; paste it in */
+ pix2 = pixGammaTRC(NULL, pix3, 1.0, 0, 80);
+ pix4 = pixThresholdToBinary(pix2, 70);
+ pix5 = pixConvertTo32(pix4);
+ pixRasterop(pix1, 3 * w + 12, 3, w, h, PIX_SRC, pix5, 0, 0); /* 4th */
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 40 */
+ pixaAddPix(pixa, pix5, L_INSERT);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Invert; this is the final result */
+ pixInvert(pix4, pix4);
+ pix5 = pixConvertTo32(pix4);
+ pixRasterop(pix1, 4 * w + 15, 3, w, h, PIX_SRC, pix5, 0, 0); /* 5th */
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 41 */
+ pixaAddPix(pixa, pix5, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix4);
+
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1700, 1.0, 0, 20, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 42 */
+ pixDisplayWithTitle(pix1, 0, 800, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+ pixDestroy(&pixs);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/graymorph2_reg.c b/leptonica/prog/graymorph2_reg.c
new file mode 100644
index 00000000..3748a508
--- /dev/null
+++ b/leptonica/prog/graymorph2_reg.c
@@ -0,0 +1,153 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * graymorph2_reg.c
+ *
+ * Compares graymorph results with special (3x1, 1x3, 3x3) cases
+ * against the general case. Require exact equality.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pix1, *pix2, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("test8.jpg");
+
+ /* Dilation */
+ pixa = pixaCreate(0);
+ pix1 = pixDilateGray3(pixs, 3, 1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixDilateGray(pixs, 3, 1);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 0 */
+
+ pix1 = pixDilateGray3(pixs, 1, 3);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixDilateGray(pixs, 1, 3);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 1 */
+
+ pix1 = pixDilateGray3(pixs, 3, 3);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixDilateGray(pixs, 3, 3);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 2 */
+
+ pixd = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 2);
+ pixDisplayWithTitle(pixd, 0, 100, "Dilation", rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ /* Erosion */
+ pixa = pixaCreate(0);
+ pix1 = pixErodeGray3(pixs, 3, 1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixErodeGray(pixs, 3, 1);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 3 */
+
+ pix1 = pixErodeGray3(pixs, 1, 3);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixErodeGray(pixs, 1, 3);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 4 */
+
+ pix1 = pixErodeGray3(pixs, 3, 3);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixErodeGray(pixs, 3, 3);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 5 */
+
+ pixd = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 2);
+ pixDisplayWithTitle(pixd, 250, 100, "Erosion", rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ /* Opening */
+ pixa = pixaCreate(0);
+ pix1 = pixOpenGray3(pixs, 3, 1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixOpenGray(pixs, 3, 1);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 6 */
+
+ pix1 = pixOpenGray3(pixs, 1, 3);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixOpenGray(pixs, 1, 3);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 7 */
+
+ pix1 = pixOpenGray3(pixs, 3, 3);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixOpenGray(pixs, 3, 3);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 8 */
+
+ pixd = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 2);
+ pixDisplayWithTitle(pixd, 500, 100, "Opening", rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ /* Closing */
+ pixa = pixaCreate(0);
+ pix1 = pixCloseGray3(pixs, 3, 1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixCloseGray(pixs, 3, 1);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 9 */
+
+ pix1 = pixCloseGray3(pixs, 1, 3);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixCloseGray(pixs, 1, 3);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 10 */
+
+ pix1 = pixCloseGray3(pixs, 3, 3);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixCloseGray(pixs, 3, 3);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 11 */
+
+ pixd = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 2);
+ pixDisplayWithTitle(pixd, 750, 100, "Closing", rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/graymorphtest.c b/leptonica/prog/graymorphtest.c
new file mode 100644
index 00000000..9ad3b84d
--- /dev/null
+++ b/leptonica/prog/graymorphtest.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * graymorphtest.c
+ *
+ * Implements basic grayscale morphology; tests speed
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 wsize, hsize, w, h, d;
+PIX *pixs, *pixd;
+static char mainName[] = "graymorphtest";
+
+ if (argc != 5)
+ return ERROR_INT(" Syntax: graymorphtest filein wsize hsize fileout",
+ mainName, 1);
+ filein = argv[1];
+ wsize = atoi(argv[2]);
+ hsize = atoi(argv[3]);
+ fileout = argv[4];
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pix not made", mainName, 1);
+ pixGetDimensions(pixs, &w, &h, &d);
+ if (d != 8)
+ return ERROR_INT("pix not 8 bpp", mainName, 1);
+
+ /* ---------- Choose an operation ---------- */
+#if 1
+ pixd = pixDilateGray(pixs, wsize, hsize);
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixd);
+#elif 0
+ pixd = pixErodeGray(pixs, wsize, hsize);
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixd);
+#elif 0
+ pixd = pixOpenGray(pixs, wsize, hsize);
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixd);
+#elif 0
+ pixd = pixCloseGray(pixs, wsize, hsize);
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixd);
+#elif 0
+ pixd = pixTophat(pixs, wsize, hsize, TOPHAT_WHITE);
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixd);
+#elif 0
+ pixd = pixTophat(pixs, wsize, hsize, TOPHAT_BLACK);
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixd);
+#endif
+
+
+ /* ---------- Speed ---------- */
+#if 0
+ startTimer();
+ pixd = pixCloseGray(pixs, wsize, hsize);
+ lept_stderr(" Speed is %6.2f MPix/sec\n",
+ (l_float32)(4 * w * h) / (1000000. * stopTimer()));
+ pixWrite(fileout, pixd, IFF_PNG);
+#endif
+
+ pixDestroy(&pixs);
+ return 0;
+}
+
+
diff --git a/leptonica/prog/grayquant_reg.c b/leptonica/prog/grayquant_reg.c
new file mode 100644
index 00000000..85c79ed7
--- /dev/null
+++ b/leptonica/prog/grayquant_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * grayquant_reg.c
+ *
+ * Tests gray thresholding to 1, 2 and 4 bpp, with and without colormaps
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 THRESHOLD = 130;
+ /* nlevels for 4 bpp output; anything between 2 and 16 is allowed */
+static const l_int32 NLEVELS = 4;
+
+
+int main(int argc,
+ char **argv)
+{
+const char *str;
+l_int32 index, w, h;
+BOX *box;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+PIXA *pixa;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* ------------------------------------------------------------- */
+
+ pixs = pixRead("test8.jpg");
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixs, L_INSERT);
+
+ /* threshold to 1 bpp */
+ pix1 = pixThresholdToBinary(pixs, THRESHOLD);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+
+ /* dither to 2 bpp, with and without colormap */
+ pix1 = pixDitherTo2bpp(pixs, 1);
+ pix2 = pixDitherTo2bpp(pixs, 0);
+ pix3 = pixConvertGrayToColormap(pix2);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 2 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3 */
+ if (rp->display) pixcmapWriteStream(stderr, pixGetColormap(pix3));
+ regTestComparePix(rp, pix1, pix3); /* 4 */
+
+ /* threshold to 2 bpp, with and without colormap */
+ pix1 = pixThresholdTo2bpp(pixs, 4, 1);
+ pix2 = pixThresholdTo2bpp(pixs, 4, 0);
+ pix3 = pixConvertGrayToColormap(pix2);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 5 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 6 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 7 */
+ if (rp->display) pixcmapWriteStream(stderr, pixGetColormap(pix3));
+ regTestComparePix(rp, pix1, pix3); /* 8 */
+
+ pix1 = pixThresholdTo2bpp(pixs, 3, 1);
+ pix2 = pixThresholdTo2bpp(pixs, 3, 0);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 9 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 10 */
+
+ /* threshold to 4 bpp, with and without colormap */
+ pix1 = pixThresholdTo4bpp(pixs, 9, 1);
+ pix2 = pixThresholdTo4bpp(pixs, 9, 0);
+ pix3 = pixConvertGrayToColormap(pix2);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 11 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 12 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 13 */
+ if (rp->display) pixcmapWriteStream(stderr, pixGetColormap(pix3));
+
+ /* threshold on 8 bpp, with and without colormap */
+ pix1 = pixThresholdOn8bpp(pixs, 9, 1);
+ pix2 = pixThresholdOn8bpp(pixs, 9, 0);
+ pix3 = pixConvertGrayToColormap(pix2);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 14 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 15 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 16 */
+ if (rp->display) pixcmapWriteStream(stderr, pixGetColormap(pix3));
+ regTestComparePix(rp, pix1, pix3); /* 17 */
+
+ /* Optional display */
+ if (rp->display) {
+ lept_mkdir("lept/gquant");
+ pix1 = pixaDisplayTiled(pixa, 2000, 0, 20);
+ pixDisplay(pix1, 100, 100);
+ pixWrite("/tmp/lept/gquant/mosaic1.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa);
+
+ /* ------------------------------------------------------------- */
+
+ pixs = pixRead("test8.jpg");
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixs, L_INSERT);
+
+ /* Highlight 2 bpp with colormap */
+ pix1 = pixThresholdTo2bpp(pixs, 3, 1);
+ pixaAddPix(pixa, pix1, L_COPY);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 18 */
+ cmap = pixGetColormap(pix1);
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ box = boxCreate(278, 35, 122, 50);
+ pixSetSelectCmap(pix1, box, 2, 255, 255, 100);
+ boxDestroy(&box);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 19 */
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+
+ /* Test pixThreshold8() */
+ pix1 = pixThreshold8(pixs, 1, 2, 1); /* cmap */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 20 */
+
+ pix1 = pixThreshold8(pixs, 1, 2, 0); /* no cmap */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 21 */
+
+ pix1 = pixThreshold8(pixs, 2, 3, 1); /* highlight one box */
+ box = boxCreate(278, 35, 122, 50);
+ pixSetSelectCmap(pix1, box, 2, 255, 255, 100);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 22 */
+ cmap = pixGetColormap(pix1);
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ boxDestroy(&box);
+
+
+ pix1 = pixThreshold8(pixs, 2, 4, 0); /* no cmap */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 23 */
+
+ pix1 = pixThreshold8(pixs, 4, 6, 1); /* highlight one box */
+ box = boxCreate(278, 35, 122, 50);
+ pixSetSelectCmap(pix1, box, 5, 255, 255, 100);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 24 */
+ cmap = pixGetColormap(pix1);
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ boxDestroy(&box);
+
+ pix1 = pixThreshold8(pixs, 4, 6, 0); /* no cmap */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 25 */
+
+ /* Highlight 4 bpp with 2 colormap entries */
+ /* Note: We use 5 levels (0-4) for gray. */
+ /* 5 & 6 are used for highlight color. */
+ pix1 = pixThresholdTo4bpp(pixs, 5, 1);
+ cmap = pixGetColormap(pix1);
+ pixcmapGetIndex(cmap, 255, 255, 255, &index);
+ box = boxCreate(278, 35, 122, 50);
+ pixSetSelectCmap(pix1, box, index, 255, 255, 100); /* use index 5 */
+ pixaAddPix(pixa, pix1, L_COPY);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 26 */
+ boxDestroy(&box);
+ box = boxCreate(4, 6, 157, 33);
+ pixSetSelectCmap(pix1, box, index, 100, 255, 255); /* use index 6 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 27 */
+ boxDestroy(&box);
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+
+ /* Optional display */
+ if (rp->display) {
+ pix1 = pixaDisplayTiled(pixa, 2000, 0, 20);
+ pixDisplay(pix1, 200, 100);
+ pixWrite("/tmp/lept/gquant/mosaic2.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa);
+
+ /* ------------------------------------------------------------- */
+
+ pixs = pixRead("feyn.tif");
+ pixa = pixaCreate(0);
+
+ /* Comparison 8 bpp jpeg with 2 bpp (highlight) */
+ pix1 = pixScaleToGray4(pixs);
+ pix2 = pixReduceRankBinaryCascade(pixs, 2, 2, 0, 0);
+ pix3 = pixThresholdTo2bpp(pix1, 3, 1);
+ box = boxCreate(175, 208, 228, 88);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 28 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 29 */
+ pixSetSelectCmap(pix3, box, 2, 255, 255, 100);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 30 */
+ cmap = pixGetColormap(pix3);
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ boxDestroy(&box);
+
+ /* Thresholding to 4 bpp (highlight); use pix1 from above */
+ pix2 = pixThresholdTo4bpp(pix1, NLEVELS, 1);
+ box = boxCreate(175, 208, 228, 83);
+ pixSetSelectCmap(pix2, box, NLEVELS - 1, 255, 255, 100);
+ boxDestroy(&box);
+ box = boxCreate(232, 298, 110, 25);
+ pixSetSelectCmap(pix2, box, NLEVELS - 1, 100, 255, 255);
+ boxDestroy(&box);
+ box = boxCreate(21, 698, 246, 82);
+ pixSetSelectCmap(pix2, box, NLEVELS - 1, 225, 100, 255);
+ boxDestroy(&box);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 31 */
+ cmap = pixGetColormap(pix2);
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ pix3 = pixReduceRankBinaryCascade(pixs, 2, 2, 0, 0);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 32 */
+
+ /* Thresholding to 4 bpp at 2, 3, 4, 5 and 6 levels */
+ box = boxCreate(25, 202, 136, 37);
+ pix2 = pixClipRectangle(pix1, box, NULL);
+ pix3 = pixScale(pix2, 6., 6.);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 33 */
+ pixGetDimensions(pix3, &w, &h, NULL);
+ pix4 = pixCreate(w, 6 * h, 8);
+ pixRasterop(pix4, 0, 0, w, h, PIX_SRC, pix3, 0, 0);
+ pixDestroy(&pix2);
+ boxDestroy(&box);
+
+ pix5 = pixThresholdTo4bpp(pix3, 6, 1);
+ pix6 = pixRemoveColormap(pix5, REMOVE_CMAP_TO_GRAYSCALE);
+ pixRasterop(pix4, 0, h, w, h, PIX_SRC, pix6, 0, 0);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 34 */
+ pixDestroy(&pix6);
+
+ pix5 = pixThresholdTo4bpp(pix3, 5, 1);
+ pix6 = pixRemoveColormap(pix5, REMOVE_CMAP_TO_GRAYSCALE);
+ pixRasterop(pix4, 0, 2 * h, w, h, PIX_SRC, pix6, 0, 0);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 35 */
+ pixDestroy(&pix6);
+
+ pix5 = pixThresholdTo4bpp(pix3, 4, 1);
+ pix6 = pixRemoveColormap(pix5, REMOVE_CMAP_TO_GRAYSCALE);
+ pixRasterop(pix4, 0, 3 * h, w, h, PIX_SRC, pix6, 0, 0);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 36 */
+ pixDestroy(&pix6);
+
+ pix5 = pixThresholdTo4bpp(pix3, 3, 1);
+ pix6 = pixRemoveColormap(pix5, REMOVE_CMAP_TO_GRAYSCALE);
+ pixRasterop(pix4, 0, 4 * h, w, h, PIX_SRC, pix6, 0, 0);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 37 */
+ pixDestroy(&pix6);
+
+ pix5 = pixThresholdTo4bpp(pix3, 2, 1);
+ pix6 = pixRemoveColormap(pix5, REMOVE_CMAP_TO_GRAYSCALE);
+ pixRasterop(pix4, 0, 5 * h, w, h, PIX_SRC, pix6, 0, 0);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 38 */
+ pixDestroy(&pix6);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 39 */
+
+ /* Optional display */
+ if (rp->display) {
+ pix1 = pixaDisplayTiled(pixa, 2000, 0, 20);
+ pixDisplay(pix1, 300, 100);
+ pixWrite("/tmp/lept/gquant/mosaic3.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs);
+
+ /* ------------------------------------------------------------- */
+
+ /* Thresholding with fixed and arbitrary bin boundaries */
+ pixs = pixRead("stampede2.jpg");
+ pixa = pixaCreate(0);
+
+ pixaAddPix(pixa, pixs, L_INSERT);
+ regTestWritePixAndCheck(rp, pixs, IFF_PNG); /* 40 */
+ pix1 = pixThresholdTo4bpp(pixs, 5, 1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 41 */
+ pix1 = pixThresholdTo4bpp(pixs, 7, 1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 42 */
+ cmap = pixGetColormap(pix1);
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ pix1 = pixThresholdTo4bpp(pixs, 11, 1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 43 */
+
+ str = "45 75 115 185";
+ pix1 = pixThresholdGrayArb(pixs, str, 8, 0, 0, 0);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 44 */
+ str = "38 65 85 115 160 210";
+ pix1 = pixThresholdGrayArb(pixs, str, 8, 0, 1, 1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 45 */
+ cmap = pixGetColormap(pix1);
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ str = "38 60 75 90 110 130 155 185 208 239";
+ pix1 = pixThresholdGrayArb(pixs, str, 8, 0, 0, 0);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 46 */
+ str = "45 75 115 185";
+ pix1 = pixThresholdGrayArb(pixs, str, 0, 1, 0, 1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 47 */
+ str = "38 65 85 115 160 210";
+ pix1 = pixThresholdGrayArb(pixs, str, 0, 1, 0, 1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 48 */
+ cmap = pixGetColormap(pix1);
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ str = "38 60 75 90 110 130 155 185 208 239";
+ pix1 = pixThresholdGrayArb(pixs, str, 4, 1, 0, 1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 49 */
+
+ /* Optional display */
+ if (rp->display) {
+ pix1 = pixaDisplayTiled(pixa, 2000, 0, 20);
+ pixDisplay(pix1, 400, 100);
+ pixWrite("/tmp/lept/gquant/mosaic4.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa);
+
+ if (rp->display) {
+ /* Upscale 2x and threshold to 1 bpp */
+ pixs = pixRead("test8.jpg");
+ startTimer();
+ pix1 = pixScaleGray2xLIThresh(pixs, THRESHOLD);
+ lept_stderr(" time for scale/dither = %7.3f sec\n", stopTimer());
+ pixWrite("/tmp/lept/gquant/upscale1.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 0, 500);
+ pixDestroy(&pix1);
+
+ /* Upscale 4x and threshold to 1 bpp */
+ startTimer();
+ pix1 = pixScaleGray4xLIThresh(pixs, THRESHOLD);
+ lept_stderr(" time for scale/dither = %7.3f sec\n", stopTimer());
+ pixWrite("/tmp/lept/gquant/upscale2.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 700, 500);
+ pixDestroy(&pix1);
+ pixDestroy(&pixs);
+ }
+
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/graytext.png b/leptonica/prog/graytext.png
new file mode 100644
index 00000000..0839d7c9
--- /dev/null
+++ b/leptonica/prog/graytext.png
Binary files differ
diff --git a/leptonica/prog/greencover.jpg b/leptonica/prog/greencover.jpg
new file mode 100644
index 00000000..dd320d48
--- /dev/null
+++ b/leptonica/prog/greencover.jpg
Binary files differ
diff --git a/leptonica/prog/hardlight1_1.jpg b/leptonica/prog/hardlight1_1.jpg
new file mode 100644
index 00000000..8c355404
--- /dev/null
+++ b/leptonica/prog/hardlight1_1.jpg
Binary files differ
diff --git a/leptonica/prog/hardlight1_2.jpg b/leptonica/prog/hardlight1_2.jpg
new file mode 100644
index 00000000..68086611
--- /dev/null
+++ b/leptonica/prog/hardlight1_2.jpg
Binary files differ
diff --git a/leptonica/prog/hardlight2_1.jpg b/leptonica/prog/hardlight2_1.jpg
new file mode 100644
index 00000000..8692dcbc
--- /dev/null
+++ b/leptonica/prog/hardlight2_1.jpg
Binary files differ
diff --git a/leptonica/prog/hardlight2_2.jpg b/leptonica/prog/hardlight2_2.jpg
new file mode 100644
index 00000000..41dabea6
--- /dev/null
+++ b/leptonica/prog/hardlight2_2.jpg
Binary files differ
diff --git a/leptonica/prog/hardlight_reg.c b/leptonica/prog/hardlight_reg.c
new file mode 100644
index 00000000..6b48e900
--- /dev/null
+++ b/leptonica/prog/hardlight_reg.c
@@ -0,0 +1,140 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * hardlight_reg.c
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static void TestHardlight(const char *file1, const char *file2,
+ L_REGPARAMS *rp);
+
+int main(int argc,
+ char **argv)
+{
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ TestHardlight("hardlight1_1.jpg", "hardlight1_2.jpg", rp);
+ TestHardlight("hardlight2_1.jpg", "hardlight2_2.jpg", rp);
+ return regTestCleanup(rp);
+}
+
+void
+TestHardlight(const char *file1,
+ const char *file2,
+ L_REGPARAMS *rp)
+{
+PIX *pixs1, *pixs2, *pix1, *pix2, *pixd;
+PIXA *pixa;
+PIXAA *paa;
+
+ /* Read in images */
+ pixs1 = pixRead(file1);
+ pixs2 = pixRead(file2);
+ paa = pixaaCreate(0);
+
+ /* ---------- Test not-in-place; no colormaps ----------- */
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixs1, L_COPY);
+ pixaAddPix(pixa, pixs2, L_COPY);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ pixd = pixBlendHardLight(NULL, pixs1, pixs2, 0, 0, 1.0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixd, L_INSERT);
+
+ pix2 = pixConvertTo32(pixs2);
+ pixd = pixBlendHardLight(NULL, pixs1, pix2, 0, 0, 1.0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pix2);
+
+ pixd = pixBlendHardLight(NULL, pixs2, pixs1, 0, 0, 1.0);
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+
+ /* ---------- Test not-in-place; colormaps ----------- */
+ pixa = pixaCreate(0);
+ pix1 = pixMedianCutQuant(pixs1, 0);
+ if (pixGetDepth(pixs2) == 8)
+ pix2 = pixConvertGrayToColormap8(pixs2, 8);
+ else
+ pix2 = pixMedianCutQuant(pixs2, 0);
+ pixaAddPix(pixa, pix1, L_COPY);
+ pixaAddPix(pixa, pix2, L_COPY);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+
+ pixa = pixaCreate(0);
+ pixd = pixBlendHardLight(NULL, pix1, pixs2, 0, 0, 1.0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixaAddPix(pixa, pixd, L_INSERT);
+
+ pixd = pixBlendHardLight(NULL, pix1, pix2, 0, 0, 1.0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixaAddPix(pixa, pixd, L_INSERT);
+
+ pixd = pixBlendHardLight(NULL, pix2, pix1, 0, 0, 1.0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* ---------- Test in-place; no colormaps ----------- */
+ pixa = pixaCreate(0);
+ pixBlendHardLight(pixs1, pixs1, pixs2, 0, 0, 1.0);
+ regTestWritePixAndCheck(rp, pixs1, IFF_PNG);
+ pixaAddPix(pixa, pixs1, L_INSERT);
+
+ pixs1 = pixRead(file1);
+ pix2 = pixConvertTo32(pixs2);
+ pixBlendHardLight(pixs1, pixs1, pix2, 0, 0, 1.0);
+ regTestWritePixAndCheck(rp, pixs1, IFF_PNG);
+ pixaAddPix(pixa, pixs1, L_INSERT);
+ pixDestroy(&pix2);
+
+ pixs1 = pixRead(file1);
+ pixBlendHardLight(pixs2, pixs2, pixs1, 0, 0, 1.0);
+ regTestWritePixAndCheck(rp, pixs2, IFF_PNG);
+ pixaAddPix(pixa, pixs2, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ pixDestroy(&pixs1);
+
+ pixd = pixaaDisplayByPixa(paa, 4, 1.0, 20, 20, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaaDestroy(&paa);
+}
diff --git a/leptonica/prog/harmoniam-11.tif b/leptonica/prog/harmoniam-11.tif
new file mode 100644
index 00000000..427f1360
--- /dev/null
+++ b/leptonica/prog/harmoniam-11.tif
Binary files differ
diff --git a/leptonica/prog/harmoniam100-11.png b/leptonica/prog/harmoniam100-11.png
new file mode 100644
index 00000000..ba3f575e
--- /dev/null
+++ b/leptonica/prog/harmoniam100-11.png
Binary files differ
diff --git a/leptonica/prog/hash_reg.c b/leptonica/prog/hash_reg.c
new file mode 100644
index 00000000..589bb561
--- /dev/null
+++ b/leptonica/prog/hash_reg.c
@@ -0,0 +1,471 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * hash_reg.c
+ *
+ * Tests hashing functions for strings and points, and the use of them with:
+ * * sets (underlying rbtree implementation for sorting)
+ * * hash maps (underlying dnaHash implementation for accessing)
+ *
+ * For sets, it's important to use good 64-bit hashes to ensure that
+ * collisions are very rare. With solid randomization, you expect
+ * that a collision is likely with 2^32 or more hashed entities.
+ * The probability of a collision goes as n^2, so with 10M entities,
+ * the collision probabililty is about 10^-5.
+ *
+ * For the dna hashing, a faster but weaker hash function is used.
+ * The hash should do a reasonable job of randomizing the lower order
+ * bits corresponding to the prime number used with the mod function
+ * for assigning to buckets. (To the extent that those bits are not
+ * randomized, the calculation will run slower because bucket
+ * occupancy will not be random, but the result will still be exact.)
+ * Hash collisions in the key are allowed because the dna in
+ * the selected bucket stores integers into arrays (of pts or strings,
+ * for example), and not keys. The input point or string is hashed to
+ * a bucket (a dna), which is then traversed, and each stored value
+ * (an index) is used check if the point or string is in the associated
+ * array at that location.
+ *
+ * Also tests similar functions directly (without hashing the number) for dna.
+ * This will allow handling of both float64 and large integers that are
+ * accurately represented by float64.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static SARRAY *BuildShortStrings(l_int32 nchars, l_int32 add_dups);
+static PTA *BuildPointSet(l_int32 w, l_int32 h, l_int32 add_dups);
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 ncolors, c1, c2, c3, s1;
+L_ASET *set;
+L_DNA *da1, *da2, *da3, *da4, *da5, *da6, *da7, *da8, *dav, *dac;
+L_DNAHASH *dahash;
+GPLOT *gplot;
+NUMA *nav, *nac;
+PTA *pta1, *pta2, *pta3;
+SARRAY *sa1, *sa2, *sa3, *sa4;
+PIX *pix1;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/hash");
+
+ /* Test string hashing with aset */
+ sa1 = BuildShortStrings(3, 0);
+ sa2 = BuildShortStrings(3, 1);
+ c1 = sarrayGetCount(sa1);
+ c2 = sarrayGetCount(sa2);
+ regTestCompareValues(rp, 18278, c1, 0); /* 0 */
+ regTestCompareValues(rp, 20982, c2, 0); /* 1 */
+ if (rp->display) {
+ lept_stderr("Set results with string hashing:\n");
+ lept_stderr(" size with unique strings: %d\n", c1);
+ lept_stderr(" size with dups: %d\n", c2);
+ }
+ startTimer();
+ set = l_asetCreateFromSarray(sa2);
+ s1 = l_asetSize(set);
+ regTestCompareValues(rp, 18278, s1, 0); /* 2 */
+ if (rp->display) {
+ lept_stderr(" time to make set: %5.3f sec\n", stopTimer());
+ lept_stderr(" size of set without dups: %d\n", s1);
+ }
+ l_asetDestroy(&set);
+ startTimer();
+ sa3 = sarrayRemoveDupsByAset(sa2);
+ c1 = sarrayGetCount(sa3);
+ regTestCompareValues(rp, 18278, c1, 0); /* 3 */
+ if (rp->display) {
+ lept_stderr(" time to remove dups: %5.3f sec\n", stopTimer());
+ lept_stderr(" size without dups = %d\n", c1);
+ }
+ startTimer();
+ sa4 = sarrayIntersectionByAset(sa1, sa2);
+ c1 = sarrayGetCount(sa4);
+ regTestCompareValues(rp, 18278, c1, 0); /* 4 */
+ if (rp->display) {
+ lept_stderr(" time to intersect: %5.3f sec\n", stopTimer());
+ lept_stderr(" intersection size = %d\n", c1);
+ }
+ sarrayDestroy(&sa3);
+ sarrayDestroy(&sa4);
+
+ /* Test sarray set operations with dna hash.
+ * We use the same hash function as is used with aset. */
+ if (rp->display) {
+ lept_stderr("\nDna hash results for sarray:\n");
+ lept_stderr(" size with unique strings: %d\n", sarrayGetCount(sa1));
+ lept_stderr(" size with dups: %d\n", sarrayGetCount(sa2));
+ }
+ startTimer();
+ dahash = l_dnaHashCreateFromSarray(sa2);
+ s1 = l_dnaHashGetTotalCount(dahash);
+ regTestCompareValues(rp, 20982, s1, 0); /* 5 */
+ if (rp->display) {
+ lept_stderr(" time to make hashmap: %5.3f sec\n", stopTimer());
+ lept_stderr(" entries in hashmap with dups: %d\n", s1);
+ }
+ l_dnaHashDestroy(&dahash);
+ startTimer();
+ sarrayRemoveDupsByHash(sa2, &sa3, NULL);
+ c1 = sarrayGetCount(sa3);
+ regTestCompareValues(rp, 18278, c1, 0); /* 6 */
+ if (rp->display) {
+ lept_stderr(" time to remove dups: %5.3f sec\n", stopTimer());
+ lept_stderr(" size without dups = %d\n", c1);
+ }
+ startTimer();
+ sa4 = sarrayIntersectionByHash(sa1, sa2);
+ c1 = sarrayGetCount(sa4);
+ regTestCompareValues(rp, 18278, c1, 0); /* 7 */
+ if (rp->display) {
+ lept_stderr(" time to intersect: %5.3f sec\n", stopTimer());
+ lept_stderr(" intersection size = %d\n", c1);
+ }
+ sarrayDestroy(&sa3);
+ sarrayDestroy(&sa4);
+ sarrayDestroy(&sa1);
+ sarrayDestroy(&sa2);
+
+ /* Test point hashing with aset.
+ * Enter all points within a 300 x 300 image in pta1, and include
+ * 18,000 duplicates in pta2. With this pt hashing function,
+ * there are no hash collisions among any of the 400 million pixel
+ * locations in a 20000 x 20000 image. */
+ pta1 = BuildPointSet(300, 300, 0);
+ pta2 = BuildPointSet(300, 300, 1);
+ c1 = ptaGetCount(pta1);
+ c2 = ptaGetCount(pta2);
+ regTestCompareValues(rp, 90000, c1, 0); /* 8 */
+ regTestCompareValues(rp, 108000, c2, 0); /* 9 */
+ if (rp->display) {
+ lept_stderr("\nSet results for pta:\n");
+ lept_stderr(" pta1 size with unique points: %d\n", c1);
+ lept_stderr(" pta2 size with dups: %d\n", c2);
+ }
+ startTimer();
+ pta3 = ptaRemoveDupsByAset(pta2);
+ c1 = ptaGetCount(pta3);
+ regTestCompareValues(rp, 90000, c1, 0); /* 10 */
+ if (rp->display) {
+ lept_stderr(" Time to remove dups: %5.3f sec\n", stopTimer());
+ lept_stderr(" size without dups = %d\n", ptaGetCount(pta3));
+ }
+ ptaDestroy(&pta3);
+
+ startTimer();
+ pta3 = ptaIntersectionByAset(pta1, pta2);
+ c1 = ptaGetCount(pta3);
+ regTestCompareValues(rp, 90000, c1, 0); /* 11 */
+ if (rp->display) {
+ lept_stderr(" Time to intersect: %5.3f sec\n", stopTimer());
+ lept_stderr(" intersection size = %d\n", c1);
+ }
+ ptaDestroy(&pta1);
+ ptaDestroy(&pta2);
+ ptaDestroy(&pta3);
+
+ /* Test pta set operations with dna hash, using the same pt hashing
+ * function. Although there are no collisions in 20K x 20K images,
+ * the dna hash implementation works properly even if there are some. */
+ pta1 = BuildPointSet(400, 400, 0);
+ pta2 = BuildPointSet(400, 400, 1);
+ c1 = ptaGetCount(pta1);
+ c2 = ptaGetCount(pta2);
+ regTestCompareValues(rp, 160000, c1, 0); /* 12 */
+ regTestCompareValues(rp, 192000, c2, 0); /* 13 */
+ if (rp->display) {
+ lept_stderr("\nDna hash results for pta:\n");
+ lept_stderr(" pta1 size with unique points: %d\n", c1);
+ lept_stderr(" pta2 size with dups: %d\n", c2);
+ }
+ startTimer();
+ ptaRemoveDupsByHash(pta2, &pta3, NULL);
+ c1 = ptaGetCount(pta3);
+ regTestCompareValues(rp, 160000, c1, 0); /* 14 */
+ if (rp->display) {
+ lept_stderr(" Time to remove dups: %5.3f sec\n", stopTimer());
+ lept_stderr(" size without dups = %d\n", c1);
+ }
+ ptaDestroy(&pta3);
+
+ startTimer();
+ pta3 = ptaIntersectionByHash(pta1, pta2);
+ c1 = ptaGetCount(pta3);
+ regTestCompareValues(rp, 160000, c1, 0); /* 15 */
+ if (rp->display) {
+ lept_stderr(" Time to intersect: %5.3f sec\n", stopTimer());
+ lept_stderr(" intersection size = %d\n", c1);
+ }
+ ptaDestroy(&pta1);
+ ptaDestroy(&pta2);
+ ptaDestroy(&pta3);
+
+ /* Test dna set and histo operations using dna hash */
+ da1 = l_dnaMakeSequence(0.0, 0.125, 8000);
+ da2 = l_dnaMakeSequence(300.0, 0.125, 8000);
+ da3 = l_dnaMakeSequence(600.0, 0.125, 8000);
+ da4 = l_dnaMakeSequence(900.0, 0.125, 8000);
+ da5 = l_dnaMakeSequence(1200.0, 0.125, 8000);
+ l_dnaJoin(da1, da2, 0, -1);
+ l_dnaJoin(da1, da3, 0, -1);
+ l_dnaJoin(da1, da4, 0, -1);
+ l_dnaJoin(da1, da5, 0, -1);
+ l_dnaRemoveDupsByHash(da1, &da6, &dahash);
+ l_dnaHashDestroy(&dahash);
+ c1 = l_dnaGetCount(da1);
+ c2 = l_dnaGetCount(da6);
+ regTestCompareValues(rp, 40000, c1, 0); /* 16 */
+ regTestCompareValues(rp, 17600, c2, 0); /* 17 */
+ if (rp->display) {
+ lept_stderr("\nDna hash results for dna:\n");
+ lept_stderr(" dna size with dups = %d\n", c1);
+ lept_stderr(" dna size of unique numbers = %d\n", c2);
+ }
+ l_dnaMakeHistoByHash(da1, &dahash, &dav, &dac);
+ nav = l_dnaConvertToNuma(dav);
+ nac = l_dnaConvertToNuma(dac);
+ c1 = l_dnaGetCount(dac);
+ regTestCompareValues(rp, 17600, c1, 0); /* 18 */
+ if (rp->display)
+ lept_stderr(" dna number of histo points = %d\n", c1);
+
+ /* Plot it twice */
+ pix1 = gplotGeneralPix2(nav, nac, GPLOT_IMPULSES, "/tmp/lept/hash/histo",
+ "Histo", NULL, NULL);
+ gplot = gplotSimpleXY1(nav, nac, GPLOT_IMPULSES, GPLOT_PNG,
+ "/tmp/lept/hash/histo", "Histo");
+ if (rp->display) {
+ pixDisplay(pix1, 700, 100);
+ l_fileDisplay("/tmp/lept/hash/histo.png", 700, 600, 1.0);
+ }
+ pixDestroy(&pix1);
+ gplotDestroy(&gplot);
+
+ da7 = l_dnaIntersectionByHash(da2, da3);
+ c1 = l_dnaGetCount(da2);
+ c2 = l_dnaGetCount(da3);
+ c3 = l_dnaGetCount(da7);
+ regTestCompareValues(rp, 8000, c1, 0); /* 19 */
+ regTestCompareValues(rp, 8000, c2, 0); /* 20 */
+ regTestCompareValues(rp, 5600, c3, 0); /* 21 */
+ if (rp->display) {
+ lept_stderr(" dna num points: da2 = %d, da3 = %d\n", c1, c2);
+ lept_stderr(" dna num da2/da3 intersection points = %d\n", c3);
+ }
+ l_dnaDestroy(&da1);
+ l_dnaDestroy(&da2);
+ l_dnaDestroy(&da3);
+ l_dnaDestroy(&da4);
+ l_dnaDestroy(&da5);
+ l_dnaDestroy(&da6);
+ l_dnaDestroy(&da7);
+ l_dnaDestroy(&dac);
+ l_dnaDestroy(&dav);
+ l_dnaHashDestroy(&dahash);
+ numaDestroy(&nav);
+ numaDestroy(&nac);
+
+ /* Test pixel counting operations with hashmap and ordered map */
+ pix1 = pixRead("wet-day.jpg");
+ pixCountRGBColorsByHash(pix1, &c1);
+ pixCountRGBColors(pix1, 1, &c2);
+ regTestCompareValues(rp, 42427, c1, 0); /* 22 */
+ regTestCompareValues(rp, 42427, c2, 0); /* 23 */
+ if (rp->display) {
+ lept_stderr("\nColor count using dna hash: %d\n", c1);
+ lept_stderr("Color count using amap: %d\n", c2);
+ }
+ pixDestroy(&pix1);
+
+ /* Test aset operations on dna. */
+ da1 = l_dnaMakeSequence(0, 3, 10000);
+ da2 = l_dnaMakeSequence(0, 5, 10000);
+ da3 = l_dnaMakeSequence(0, 7, 10000);
+ l_dnaJoin(da1, da2, 0, -1);
+ l_dnaJoin(da1, da3, 0, -1);
+ set = l_asetCreateFromDna(da1);
+ c1 = l_dnaGetCount(da1);
+ c2 = l_asetSize(set);
+ regTestCompareValues(rp, 30000, c1, 0); /* 24 */
+ regTestCompareValues(rp, 25428, c2, 0); /* 25 */
+ if (rp->display) {
+ lept_stderr("\nDna results using set:\n");
+ lept_stderr(" da1 count: %d\n", c1);
+ lept_stderr(" da1 set size: %d\n\n", c2);
+ }
+ l_asetDestroy(&set);
+
+ da4 = l_dnaUnionByAset(da2, da3);
+ set = l_asetCreateFromDna(da4);
+ c1 = l_dnaGetCount(da4);
+ c2 = l_asetSize(set);
+ regTestCompareValues(rp, 18571, c1, 0); /* 26 */
+ regTestCompareValues(rp, 18571, c2, 0); /* 27 */
+ if (rp->display) {
+ lept_stderr(" da4 count: %d\n", c1);
+ lept_stderr(" da4 set size: %d\n\n", c2);
+ }
+ l_asetDestroy(&set);
+
+ da5 = l_dnaIntersectionByAset(da1, da2);
+ set = l_asetCreateFromDna(da5);
+ c1 = l_dnaGetCount(da5);
+ c2 = l_asetSize(set);
+ regTestCompareValues(rp, 10000, c1, 0); /* 28 */
+ regTestCompareValues(rp, 10000, c2, 0); /* 29 */
+ if (rp->display) {
+ lept_stderr(" da5 count: %d\n", c1);
+ lept_stderr(" da5 set size: %d\n\n", c2);
+ }
+ l_asetDestroy(&set);
+
+ da6 = l_dnaMakeSequence(100000, 11, 5000);
+ l_dnaJoin(da6, da1, 0, -1);
+ set = l_asetCreateFromDna(da6);
+ c1 = l_dnaGetCount(da6);
+ c2 = l_asetSize(set);
+ regTestCompareValues(rp, 35000, c1, 0); /* 30 */
+ regTestCompareValues(rp, 30428, c2, 0); /* 31 */
+ if (rp->display) {
+ lept_stderr(" da6 count: %d\n", c1);
+ lept_stderr(" da6 set size: %d\n\n", c2);
+ }
+ l_asetDestroy(&set);
+
+ da7 = l_dnaIntersectionByAset(da6, da3);
+ set = l_asetCreateFromDna(da7);
+ c1 = l_dnaGetCount(da7);
+ c2 = l_asetSize(set);
+ regTestCompareValues(rp, 10000, c1, 0); /* 32 */
+ regTestCompareValues(rp, 10000, c2, 0); /* 33 */
+ if (rp->display) {
+ lept_stderr(" da7 count: %d\n", c1);
+ lept_stderr(" da7 set size: %d\n\n", c2);
+ }
+ l_asetDestroy(&set);
+
+ da8 = l_dnaRemoveDupsByAset(da1);
+ c1 = l_dnaGetCount(da8);
+ regTestCompareValues(rp, 25428, c1, 0); /* 34 */
+ if (rp->display)
+ lept_stderr(" da8 count: %d\n\n", c1);
+ l_dnaDestroy(&da1);
+ l_dnaDestroy(&da2);
+ l_dnaDestroy(&da3);
+ l_dnaDestroy(&da4);
+ l_dnaDestroy(&da5);
+ l_dnaDestroy(&da6);
+ l_dnaDestroy(&da7);
+ l_dnaDestroy(&da8);
+
+ return regTestCleanup(rp);
+}
+
+
+ /* Build all possible strings, up to a max of 5 roman alphabet characters */
+static SARRAY *
+BuildShortStrings(l_int32 nchars, /* 3, 4 or 5 */
+ l_int32 add_dups)
+{
+char buf[64];
+l_int32 i, j, k, l, m;
+l_uint64 hash;
+SARRAY *sa;
+
+ sa = sarrayCreate(1000);
+ for (i = 0; i < 26; i++) {
+ snprintf(buf, sizeof(buf), "%c", i + 0x61);
+ sarrayAddString(sa, buf, L_COPY);
+ for (j = 0; j < 26; j++) {
+ snprintf(buf, sizeof(buf), "%c%c", i + 0x61, j + 0x61);
+ sarrayAddString(sa, buf, L_COPY);
+ for (k = 0; k < 26; k++) {
+ snprintf(buf, sizeof(buf), "%c%c%c", i + 0x61, j + 0x61,
+ k + 0x61);
+ sarrayAddString(sa, buf, L_COPY);
+ if (add_dups && k < 4) /* add redundant strings */
+ sarrayAddString(sa, buf, L_COPY);
+ if (nchars > 3) {
+ for (l = 0; l < 26; l++) {
+ snprintf(buf, sizeof(buf), "%c%c%c%c", i + 0x61,
+ j + 0x61, k + 0x61, l + 0x61);
+ sarrayAddString(sa, buf, L_COPY);
+ if (add_dups && l < 4) /* add redundant strings */
+ sarrayAddString(sa, buf, L_COPY);
+ if (nchars > 4) {
+ for (m = 0; m < 26; m++) {
+ snprintf(buf, sizeof(buf), "%c%c%c%c%c",
+ i + 0x61, j + 0x61, k + 0x61,
+ l + 0x61, m + 0x61);
+ sarrayAddString(sa, buf, L_COPY);
+ if (!add_dups && i == 17 && j == 12 &&
+ k == 4 && l == 21) {
+ l_hashStringToUint64(buf, &hash);
+ lept_stderr(" %llx\n", hash);
+ }
+ if (add_dups && m < 4) /* add redundant */
+ sarrayAddString(sa, buf, L_COPY);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return sa;
+}
+
+static PTA *
+BuildPointSet(l_int32 w, l_int32 h, l_int32 add_dups)
+{
+l_int32 i, j;
+PTA *pta;
+
+ pta = ptaCreate(w * h);
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++)
+ ptaAddPt(pta, j, i);
+ if (add_dups) { /* extra (0.2 * w * h) points */
+ for (j = 0.4 * w; j < 0.6 * w; j++)
+ ptaAddPt(pta, j, i);
+ }
+ }
+
+ return pta;
+}
diff --git a/leptonica/prog/heap_reg.c b/leptonica/prog/heap_reg.c
new file mode 100644
index 00000000..0ef077e9
--- /dev/null
+++ b/leptonica/prog/heap_reg.c
@@ -0,0 +1,155 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Tests the heap utility.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+struct HeapElement {
+ l_float32 distance;
+ l_int32 x;
+ l_int32 y;
+};
+typedef struct HeapElement HEAPEL;
+
+static const l_int32 NELEM = 50;
+
+NUMA *ExtractNumaFromHeap(L_HEAP *lh);
+
+
+int main(int argc,
+ char **argv)
+{
+l_uint8 *data;
+l_int32 i;
+size_t size;
+l_float32 frand, fval;
+HEAPEL *item;
+NUMA *na1, *na2, *na3, *na4, *na5;
+L_HEAP *lh;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/heap");
+
+ /* Make a numa of random numbers */
+ na1 = numaCreate(5);
+ for (i = 0; i < NELEM; i++) {
+ frand = (l_float32)rand() / (l_float32)RAND_MAX;
+ numaAddNumber(na1, frand);
+ }
+ numaWriteMem(&data, &size, na1);
+ regTestWriteDataAndCheck(rp, data, size, "na"); /* 0 */
+ lept_free(data);
+
+ /* Make an array of HEAPELs with the same numbers */
+ lh = lheapCreate(5, L_SORT_INCREASING);
+ for (i = 0; i < NELEM; i++) {
+ numaGetFValue(na1, i, &fval);
+ item = (HEAPEL *)lept_calloc(1, sizeof(HEAPEL));
+ item->distance = fval;
+ lheapAdd(lh, item);
+ }
+
+ /* Re-sort for strict order */
+ lheapSortStrictOrder(lh);
+ na2 = ExtractNumaFromHeap(lh);
+ numaWriteMem(&data, &size, na2);
+ regTestWriteDataAndCheck(rp, data, size, "na"); /* 1 */
+ lept_free(data);
+
+ /* Switch the direction and re-sort strict order */
+ lh->direction = L_SORT_DECREASING;
+ lheapSortStrictOrder(lh);
+ na3 = ExtractNumaFromHeap(lh);
+ numaWriteMem(&data, &size, na3);
+ regTestWriteDataAndCheck(rp, data, size, "na"); /* 2 */
+ lept_free(data);
+
+ /* Switch direction again and re-sort strict sort */
+ lh->direction = L_SORT_INCREASING;
+ lheapSortStrictOrder(lh);
+ na4 = ExtractNumaFromHeap(lh);
+ numaWriteMem(&data, &size, na4);
+ regTestWriteDataAndCheck(rp, data, size, "na"); /* 3 */
+ lept_free(data);
+
+ /* Switch direction again and re-sort strict sort */
+ lh->direction = L_SORT_DECREASING;
+ lheapSortStrictOrder(lh);
+ na5 = ExtractNumaFromHeap(lh);
+ numaWriteMem(&data, &size, na5);
+ regTestWriteDataAndCheck(rp, data, size, "na"); /* 4 */
+ lept_free(data);
+
+ regTestCompareFiles(rp, 1, 3); /* 5 */
+ regTestCompareFiles(rp, 2, 4); /* 6 */
+
+ /* Remove the elements, one at a time */
+ for (i = 0; lheapGetCount(lh) > 0; i++) {
+ item = (HEAPEL *)lheapRemove(lh);
+ if (rp->display)
+ lept_stderr("item %d: %f\n", i, item->distance);
+ lept_free(item);
+ }
+
+ lheapDestroy(&lh, 1);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+ numaDestroy(&na5);
+ return regTestCleanup(rp);
+}
+
+
+ /* This just uses the heap array. It will only be
+ ordered if the heap is in strict ordering. */
+NUMA *
+ExtractNumaFromHeap(L_HEAP *lh)
+{
+l_int32 i, n;
+HEAPEL *item;
+NUMA *na;
+
+ n = lheapGetCount(lh);
+ na = numaCreate(0);
+ for (i = 0; i < n; i++) {
+ item = (HEAPEL *)lh->array[i];
+ numaAddNumber(na, item->distance);
+ }
+ return na;
+}
diff --git a/leptonica/prog/histoduptest.c b/leptonica/prog/histoduptest.c
new file mode 100644
index 00000000..db3ad1de
--- /dev/null
+++ b/leptonica/prog/histoduptest.c
@@ -0,0 +1,267 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+/*
+ * histoduptest.c
+ *
+ * This demonstrates two things:
+ * (1) Histogram method of comparing two grayscale images for similarity.
+ * High score (> 0.5) means they're likely to be the same image
+ * (2) The morphological method, based on horizontal lines, for
+ * deciding if a grayscale image is text or non-text.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+
+#define TEST1 1
+#define TEST2 1
+#define TEST3 1
+#define TEST4 1
+#define TEST5 1
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_uint8 *bytea1, *bytea2;
+l_int32 i, j, n, maxi, maxj, istext, w1, h1, w2, h2;
+l_int32 debug;
+size_t size1, size2;
+l_float32 score, maxscore;
+l_float32 *scores;
+BOX *box1, *box2;
+NUMA *nai;
+NUMAA *naa1, *naa2, *naa3, *naa4;
+PIX *pix1, *pix2;
+PIXA *pixa1, *pixa2, *pixa3;
+PIXAC *pac;
+
+ if (argc != 1) {
+ lept_stderr("Syntax: histoduptest\n");
+ return 1;
+ }
+
+ /* Set to 1 for more output from tests 1 and 2 */
+ debug = 0;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/comp");
+ pac = pixacompRead("dinos.pac"); /* resolution = 75 ppi */
+
+#if TEST1
+ /* -------------------------------------------------------------- *
+ * Test comparison with rotation *
+ * -------------------------------------------------------------- */
+ /* Make a second set that is rotated; combine with the input set. */
+ pixa1 = pixaCreateFromPixacomp(pac, L_COPY);
+ pixa2 = pixaScaleBySampling(pixa1, 2.0, 2.0); /* to resolution 150 ppi */
+ n = pixaGetCount(pixa2);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa2, i, L_CLONE);
+ pix2 = pixRotate(pix1, 0.06, L_ROTATE_SAMPLING, L_BRING_IN_WHITE,
+ 0, 0);
+ pixaAddPix(pixa2, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ /* Compare between every pair of images;
+ * can also use n = 2, simthresh = 0.50. */
+ pixaComparePhotoRegionsByHisto(pixa2, 0.85, 1.3, 1, 3, 0.20,
+ &nai, &scores, &pix1, debug);
+ lept_free(scores);
+
+ /* Show the similarity classes. */
+ numaWriteStream(stderr, nai);
+ pixWrite("/tmp/lept/comp/photoclass1.jpg", pix1, IFF_JFIF_JPEG);
+ lept_stderr("Writing photo classes: /tmp/lept/comp/photoclass1.jpg\n");
+ numaDestroy(&nai);
+ pixDestroy(&pix1);
+
+ /* Show the scores between images as a 2d array */
+ pix2 = pixRead("/tmp/lept/comp/scorearray.png");
+ pixDisplay(pix2, 100, 100);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+#endif
+
+#if TEST2
+ /* -------------------------------------------------------------- *
+ * Test translation *
+ * -------------------------------------------------------------- */
+ /* Make a second set that is translated; combine with the input set. */
+ pixa1 = pixaCreateFromPixacomp(pac, L_COPY);
+ pixa2 = pixaScaleBySampling(pixa1, 2.0, 2.0); /* to resolution 150 ppi */
+ pixa3 = pixaTranslate(pixa2, 15, -21, L_BRING_IN_WHITE);
+ pixaJoin(pixa2, pixa3, 0, -1);
+
+ /* Compare between every pair of images. */
+ pixaComparePhotoRegionsByHisto(pixa2, 0.85, 1.3, 1, 3, 0.20,
+ &nai, &scores, &pix1, debug);
+ lept_free(scores);
+
+ /* Show the similarity classes. */
+ numaWriteStream(stderr, nai);
+ pixWrite("/tmp/lept/comp/photoclass2.jpg", pix1, IFF_JFIF_JPEG);
+ lept_stderr("Writing photo classes: /tmp/lept/comp/photoclass2.jpg\n");
+ numaDestroy(&nai);
+ pixDestroy(&pix1);
+
+ /* Show the scores between images as a 2d array */
+ pix2 = pixRead("/tmp/lept/comp/scorearray.png");
+ pixDisplay(pix2, 100, 100);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+#endif
+
+#if TEST3
+ /* -------------------------------------------------------------- *
+ * Compare two image regions *
+ * -------------------------------------------------------------- */
+ /* Do a comparison on a pair: dinos has (5,7) and (4,10) being
+ * superficially similar. But they are far apart by this test. */
+ pixa1 = pixaCreateFromPixacomp(pac, L_COPY);
+ pixa2 = pixaScaleBySampling(pixa1, 2.0, 2.0); /* to resolution 150 ppi */
+ pix1 = pixaGetPix(pixa2, 5, L_CLONE);
+ box1 = pixaGetBox(pixa2, 5, L_COPY);
+ pix2 = pixaGetPix(pixa2, 7, L_CLONE);
+ box2 = pixaGetBox(pixa2, 7, L_COPY);
+ pixGenPhotoHistos(pix1, box1, 1, 1.2, 3, &naa1, &w1, &h1, 5);
+ pixGenPhotoHistos(pix2, box2, 1, 1.2, 3, &naa2, &w2, &h2, 7);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ if (!naa1 || !naa2) {
+ lept_stderr("Not both image; exiting\n");
+ return 0;
+ }
+ bytea1 = l_compressGrayHistograms(naa1, w1, h1, &size1);
+ bytea2 = l_compressGrayHistograms(naa2, w2, h2, &size2);
+ naa3 = l_uncompressGrayHistograms(bytea1, size1, &w1, &h1);
+ naa4 = l_uncompressGrayHistograms(bytea2, size2, &w2, &h2);
+ lept_stderr("******* (%d, %d), (%d, %d) *******\n", w1, h1, w2, h2);
+ pixa1 = pixaCreate(0);
+ /* Set @minratio very small to allow comparison for all pairs */
+ compareTilesByHisto(naa3, naa4, 0.1, w1, h1, w2, h2, &score, pixa1);
+ pixaDestroy(&pixa1);
+ lept_stderr("score = %5.3f\n", score);
+ pixaDestroy(&pixa1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ numaaDestroy(&naa1);
+ numaaDestroy(&naa2);
+ numaaDestroy(&naa3);
+ numaaDestroy(&naa4);
+ lept_free(bytea1);
+ lept_free(bytea2);
+#endif
+
+#if TEST4
+ /* -------------------------------------------------------------- *
+ * Test comparison in detail *
+ * -------------------------------------------------------------- */
+ pixa1 = pixaCreateFromPixacomp(pac, L_COPY);
+ n = pixaGetCount(pixa1);
+ maxscore = 0.0;
+ maxi = 0;
+ maxj = 0;
+ for (i = 0; i < n; i++) {
+ lept_stderr("i = %d\n", i);
+ pix1 = pixaGetPix(pixa1, i, L_CLONE);
+ box1 = pixaGetBox(pixa1, i, L_COPY);
+ for (j = 0; j <= i; j++) {
+ pix2 = pixaGetPix(pixa1, j, L_CLONE);
+ box2 = pixaGetBox(pixa1, j, L_COPY);
+ pixCompareGrayByHisto(pix1, pix2, box1, box2, 0.85, 230, 1, 3,
+ &score, 0);
+ lept_stderr("Score[%d,%d] = %5.3f\n", i, j, score);
+ if (i != j && score > maxscore) {
+ maxscore = score;
+ maxi = i;
+ maxj = j;
+ }
+ pixDestroy(&pix2);
+ boxDestroy(&box2);
+ }
+ pixDestroy(&pix1);
+ boxDestroy(&box1);
+ }
+ pixaDestroy(&pixa1);
+ lept_stderr("max score [%d,%d] = %5.3f\n", maxi, maxj, maxscore);
+#endif
+
+#if TEST5
+ /* -------------------------------------------------------------- *
+ * Text or photo determination in detail *
+ * -------------------------------------------------------------- */
+ /* Are the images photo or text? This is the morphological
+ * method, which is more accurate than the variance of gray
+ * histo method. Output to /tmp/lept/comp/isphoto1.pdf. */
+ pixa1 = pixaCreateFromPixacomp(pac, L_COPY);
+ n = pixaGetCount(pixa1);
+ pixa2 = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pixa3 = pixaCreate(0);
+ lept_stderr("i = %d\n", i);
+ pix1 = pixaGetPix(pixa1, i, L_CLONE);
+ box1 = pixaGetBox(pixa1, i, L_COPY);
+ lept_stderr("w = %d, h = %d\n", pixGetWidth(pix1),
+ pixGetHeight(pix1));
+ pixDecideIfText(pix1, box1, &istext, pixa3);
+ if (istext == 1) {
+ lept_stderr("This is text\n\n");
+ } else if (istext == 0) {
+ lept_stderr("This is a photo\n\n");
+ } else { /* istext == -1 */
+ lept_stderr("Not determined if text or photo\n\n");
+ }
+ if (istext == 0) {
+ pix2 = pixaDisplayTiledInRows(pixa3, 32, 1000, 1.0, 0, 50, 2);
+ pixDisplay(pix2, 100, 100);
+ pixaAddPix(pixa2, pix2, L_INSERT);
+ pixaDestroy(&pixa3);
+ }
+ pixDestroy(&pix1);
+ boxDestroy(&box1);
+ }
+ lept_stderr("Writing to: /tmp/lept/comp/isphoto1.pdf\n");
+ pixaConvertToPdf(pixa2, 300, 1.0, L_FLATE_ENCODE, 0, NULL,
+ "/tmp/lept/comp/isphoto1.pdf");
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+#endif
+
+ pixacompDestroy(&pac);
+ return 0;
+}
+
diff --git a/leptonica/prog/histotest.c b/leptonica/prog/histotest.c
new file mode 100644
index 00000000..72b56327
--- /dev/null
+++ b/leptonica/prog/histotest.c
@@ -0,0 +1,102 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * histotest.c
+ *
+ * Makes histograms of grayscale and color pixels
+ * from a pix. For RGB color, this uses
+ * rgb --> octcube indexing.
+ *
+ * histotest filein sigbits
+ *
+ * where the number of octcubes is 8^(sigbits)
+ *
+ * For gray, sigbits is ignored.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein;
+l_int32 d, sigbits;
+GPLOT *gplot;
+NUMA *na;
+PIX *pixs;
+static char mainName[] = "histotest";
+
+ if (argc != 3)
+ return ERROR_INT(" Syntax: histotest filein sigbits", mainName, 1);
+ filein = argv[1];
+ sigbits = atoi(argv[2]);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/histo");
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+ d = pixGetDepth(pixs);
+ if (d != 8 && d != 32)
+ return ERROR_INT("depth not 8 or 32 bpp", mainName, 1);
+
+ if (d == 32) {
+ startTimer();
+ if ((na = pixOctcubeHistogram(pixs, sigbits, NULL)) == NULL)
+ return ERROR_INT("na not made", mainName, 1);
+ lept_stderr("histo time = %7.3f sec\n", stopTimer());
+ gplot = gplotCreate("/tmp/lept/histo/color", GPLOT_PNG,
+ "color histogram with octcube indexing",
+ "octcube index", "number of pixels in cube");
+ gplotAddPlot(gplot, NULL, na, GPLOT_LINES, "input pix");
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ l_fileDisplay("/tmp/lept/histo/color.png", 100, 100, 1.0);
+ }
+ else {
+ if ((na = pixGetGrayHistogram(pixs, 1)) == NULL)
+ return ERROR_INT("na not made", mainName, 1);
+ numaWrite("/tmp/junkna", na);
+ gplot = gplotCreate("/tmp/lept/histo/gray", GPLOT_PNG,
+ "grayscale histogram", "gray value",
+ "number of pixels");
+ gplotSetScaling(gplot, GPLOT_LOG_SCALE_Y);
+ gplotAddPlot(gplot, NULL, na, GPLOT_LINES, "input pix");
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ l_fileDisplay("/tmp/lept/histo/gray.png", 100, 100, 1.0);
+ }
+
+ pixDestroy(&pixs);
+ numaDestroy(&na);
+ return 0;
+}
+
diff --git a/leptonica/prog/hmttemplate1.txt b/leptonica/prog/hmttemplate1.txt
new file mode 100644
index 00000000..cf1e0f84
--- /dev/null
+++ b/leptonica/prog/hmttemplate1.txt
@@ -0,0 +1,170 @@
+/*====================================================================*
+ - 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>", ...};
+
+/*!
+--- * \brief pixHMTDwa_*()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * Notes:
+ * (1) This simply adds a 32 pixel border, calls the appropriate
+ * pixFHMTGen_*(), and removes the border.
+ * See notes below for that function.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+--- * \brief pixFHMTGen_*()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/prog/hmttemplate2.txt b/leptonica/prog/hmttemplate2.txt
new file mode 100644
index 00000000..cd4da0bf
--- /dev/null
+++ b/leptonica/prog/hmttemplate2.txt
@@ -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;
+l_int32 j, pwpls;
+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/leptonica/prog/hole-filler.png b/leptonica/prog/hole-filler.png
new file mode 100644
index 00000000..e5e5532b
--- /dev/null
+++ b/leptonica/prog/hole-filler.png
Binary files differ
diff --git a/leptonica/prog/htmlviewer.c b/leptonica/prog/htmlviewer.c
new file mode 100644
index 00000000..617dd708
--- /dev/null
+++ b/leptonica/prog/htmlviewer.c
@@ -0,0 +1,286 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * htmlviewer.c
+ *
+ * This takes a directory of image files, optionally scales them,
+ * and generates html files to view the scaled images (and thumbnails).
+ *
+ * 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: max width of view images, in pixels; use 0 for default
+ *
+ * Example:
+ * mkdir /tmp/lept/lion-in
+ * mkdir /tmp/lept/lion-out
+ * cp lion-page* /tmp/lept/lion-in
+ * htmlviewer /tmp/lept/lion-in /tmp/lept/lion-out lion 200 600
+ * ==> output:
+ * /tmp/lept/lion-out/lion.html (main html file)
+ * /tmp/lept/lion-out/lion-links.html (html file of links)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+#ifdef _WIN32
+#include <windows.h> /* for CreateDirectory() */
+#endif
+
+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;
+
+static l_int32 pixHtmlViewer(const char *dirin, const char *dirout,
+ const char *rootname, l_int32 thumbwidth,
+ l_int32 viewwidth);
+static void WriteFormattedPix(const char *fname, PIX *pix);
+
+
+int main(int argc,
+ char **argv)
+{
+char *dirin, *dirout, *rootname;
+l_int32 thumbwidth, viewwidth;
+static char mainName[] = "htmlviewer";
+
+ if (argc != 6)
+ return ERROR_INT(
+ " Syntax: htmlviewer dirin dirout rootname thumbwidth viewwidth",
+ mainName, 1);
+ dirin = argv[1];
+ dirout = argv[2];
+ rootname = argv[3];
+ thumbwidth = atoi(argv[4]);
+ viewwidth = atoi(argv[5]);
+ setLeptDebugOK(1);
+
+ pixHtmlViewer(dirin, dirout, rootname, thumbwidth, viewwidth);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Generate smaller images for viewing and write html *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixHtmlViewer()
+ *
+ * \param[in] dirin directory of input image files
+ * \param[in] dirout directory for output files
+ * \param[in] rootname root name for output files
+ * \param[in] thumbwidth width of thumb images in pixels; use 0 for default
+ * \param[in] viewwidth maximum width of view images no up-scaling
+ * in pixels; use 0 for default
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static l_int32
+pixHtmlViewer(const char *dirin,
+ const char *dirout,
+ const char *rootname,
+ l_int32 thumbwidth,
+ l_int32 viewwidth)
+{
+char *fname, *fullname, *outname;
+char *mainname, *linkname, *linknameshort;
+char *viewfile, *thumbfile;
+char *shtml, *slink;
+char buf[512];
+char htmlstring[] = "<html>";
+char framestring[] = "</frameset></html>";
+l_int32 i, nfiles, index, w, d, 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
+ snprintf(buf, sizeof(buf), "mkdir -p %s", dirout);
+ ret = system(buf);
+#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 */
+ snprintf(buf, sizeof(buf), "%s/%s.html", dirout, rootname);
+ mainname = stringNew(buf);
+ snprintf(buf, sizeof(buf), "%s/%s-links.html", dirout, rootname);
+ linkname = stringNew(buf);
+ linknameshort = stringJoin(rootname, "-links.html");
+
+ /* Generate the thumbs and views */
+ sathumbs = sarrayCreate(0);
+ saviews = sarrayCreate(0);
+ nfiles = sarrayGetCount(safiles);
+ index = 0;
+ for (i = 0; i < nfiles; i++) {
+ fname = sarrayGetString(safiles, i, L_NOCOPY);
+ fullname = genPathname(dirin, fname);
+ lept_stderr("name: %s\n", fullname);
+ if ((pix = pixRead(fullname)) == NULL) {
+ lept_stderr("file %s not a readable image\n", fullname);
+ lept_free(fullname);
+ continue;
+ }
+ lept_free(fullname);
+
+ /* Make and store the thumbnail images */
+ pixGetDimensions(pix, &w, NULL, &d);
+ factor = (l_float32)thumbwidth / (l_float32)w;
+ pixthumb = pixScale(pix, factor, factor);
+ snprintf(buf, sizeof(buf), "%s_thumb_%03d", rootname, index);
+ sarrayAddString(sathumbs, buf, L_COPY);
+ outname = genPathname(dirout, buf);
+ WriteFormattedPix(outname, pixthumb);
+ lept_free(outname);
+ pixDestroy(&pixthumb);
+
+ /* Make and store the view images */
+ factor = (l_float32)viewwidth / (l_float32)w;
+ if (factor >= 1.0)
+ pixview = pixClone(pix); /* no upscaling */
+ else
+ pixview = pixScale(pix, factor, factor);
+ snprintf(buf, sizeof(buf), "%s_view_%03d", rootname, index);
+ sarrayAddString(saviews, buf, L_COPY);
+ outname = genPathname(dirout, buf);
+ WriteFormattedPix(outname, pixview);
+ lept_free(outname);
+ pixDestroy(&pixview);
+ pixDestroy(&pix);
+ index++;
+ }
+
+ /* Generate the main html file */
+ sahtml = sarrayCreate(0);
+ sarrayAddString(sahtml, htmlstring, L_COPY);
+ snprintf(buf, sizeof(buf), "<frameset cols=\"%d, *\">", thumbwidth + 30);
+ sarrayAddString(sahtml, buf, L_COPY);
+ snprintf(buf, sizeof(buf), "<frame name=\"thumbs\" src=\"%s\">",
+ linknameshort);
+ sarrayAddString(sahtml, buf, L_COPY);
+ snprintf(buf, sizeof(buf), "<frame name=\"views\" src=\"%s\">",
+ sarrayGetString(saviews, 0, L_NOCOPY));
+ sarrayAddString(sahtml, buf, L_COPY);
+ sarrayAddString(sahtml, framestring, L_COPY);
+ shtml = sarrayToString(sahtml, 1);
+ l_binaryWrite(mainname, "w", shtml, strlen(shtml));
+ lept_stderr("******************************************\n"
+ "Writing html file: %s\n"
+ "******************************************\n", mainname);
+ lept_free(shtml);
+ lept_free(mainname);
+
+ /* Generate the link html file */
+ nimages = sarrayGetCount(saviews);
+ lept_stderr("num. images = %d\n", nimages);
+ salink = sarrayCreate(0);
+ for (i = 0; i < nimages; i++) {
+ viewfile = sarrayGetString(saviews, i, L_NOCOPY);
+ thumbfile = sarrayGetString(sathumbs, i, L_NOCOPY);
+ snprintf(buf, sizeof(buf),
+ "<a href=\"%s\" TARGET=views><img src=\"%s\"></a>",
+ viewfile, thumbfile);
+ sarrayAddString(salink, buf, 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;
+}
+
+static void
+WriteFormattedPix(const char *fname,
+ PIX *pix)
+{
+l_int32 d;
+
+ d = pixGetDepth(pix);
+ if (d == 1 || pixGetColormap(pix))
+ pixWrite(fname, pix, IFF_PNG);
+ else
+ pixWrite(fname, pix, IFF_JFIF_JPEG);
+ return;
+}
+
diff --git a/leptonica/prog/imagetops.c b/leptonica/prog/imagetops.c
new file mode 100644
index 00000000..66fe8496
--- /dev/null
+++ b/leptonica/prog/imagetops.c
@@ -0,0 +1,101 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * imagetops.c
+ *
+ * This generates a PostScript image, optionally rotating and setting
+ * a scaling factor for printing with maximum size on 8.5 x 11
+ * paper at 300 ppi.
+ *
+ * Syntax: imagetops <filein> <level> <fileout>
+ *
+ * level (corresponding to PostScript compression level):
+ * 1 for uncompressed
+ * 2 for compression with g4 for 1 bpp and dct for everything else
+ * 3 for compression with flate
+ *
+ * The output PostScript file can be printed with lpr or lp.
+ * Examples of the invocation for lp are:
+ * lp -d <printer> <ps-file>
+ * lp -d <printer> -o ColorModel=Color <ps-file>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_float32 FILL_FACTOR = 0.95; /* fill factor on 8.5 x 11 page */
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 w, h, index, level;
+l_float32 scale;
+FILE *fp;
+PIX *pixs, *pix1;
+static char mainName[] = "imagetops";
+
+ if (argc != 4)
+ return ERROR_INT(
+ " Syntax: imagetops <filein> <compression level> <fileout>",
+ mainName, 1);
+ filein = argv[1];
+ level = atoi(argv[2]);
+ fileout = argv[3];
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+ if (level < 1 || level > 3)
+ return ERROR_INT("valid levels are: 1, 2, 3", mainName, 1);
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ if (w > h) {
+ pix1 = pixRotate90(pixs, 1);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ }
+ else {
+ pix1 = pixClone(pixs);
+ }
+
+ scale = L_MIN(FILL_FACTOR * 2550 / w, FILL_FACTOR * 3300 / h);
+ if (level == 1) {
+ fp = lept_fopen(fileout, "wb+");
+ pixWriteStreamPS(fp, pix1, NULL, 300, scale);
+ lept_fclose(fp);
+ } else { /* levels 2 and 3 */
+ index = 0;
+ pixWriteCompressedToPS(pix1, fileout, (l_int32)(300. / scale),
+ level, &index);
+ }
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ return 0;
+}
diff --git a/leptonica/prog/insert_reg.c b/leptonica/prog/insert_reg.c
new file mode 100644
index 00000000..a3183f1e
--- /dev/null
+++ b/leptonica/prog/insert_reg.c
@@ -0,0 +1,163 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * insert_reg.c
+ *
+ * This tests removal and insertion operations in numa, boxa and pixa.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, n;
+l_float32 pi, angle, val;
+BOX *box;
+BOXA *boxa, *boxa1, *boxa2;
+NUMA *na1, *na2;
+PIX *pix, *pix1, *pix2;
+PIXA *pixa1, *pixa2, *pixa3, *pixa4;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "insert_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_rmfile("/tmp/lept/regout/insert3.ba");
+ lept_rmfile("/tmp/lept/regout/insert4.ba");
+ lept_rmfile("/tmp/lept/regout/insert6.pa");
+ lept_rmfile("/tmp/lept/regout/insert7.pa");
+ lept_rmfile("/tmp/lept/regout/insert9.pa");
+ lept_rmfile("/tmp/lept/regout/insert10.pa");
+
+ /* ----------------- Test numa operations -------------------- */
+ pi = 3.1415926535;
+ na1 = numaCreate(500);
+ for (i = 0; i < 500; i++) {
+ angle = 0.02293 * i * pi;
+ val = (l_float32)sin(angle);
+ numaAddNumber(na1, val);
+ }
+ numaWrite("/tmp/lept/regout/insert0.na", na1);
+ na2 = numaCopy(na1);
+ n = numaGetCount(na2);
+ for (i = 0; i < n; i++) {
+ numaGetFValue(na2, i, &val);
+ numaRemoveNumber(na2, i);
+ numaInsertNumber(na2, i, val);
+ }
+ numaWrite("/tmp/lept/regout/insert1.na", na2);
+ regTestCheckFile(rp, "/tmp/lept/regout/insert0.na"); /* 0 */
+ regTestCheckFile(rp, "/tmp/lept/regout/insert1.na"); /* 1 */
+ regTestCompareFiles(rp, 0, 1); /* 2 */
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+
+ /* ----------------- Test boxa operations -------------------- */
+ pix1 = pixRead("feyn.tif");
+ box = boxCreate(1138, 1666, 1070, 380);
+ pix2 = pixClipRectangle(pix1, box, NULL);
+ boxDestroy(&box);
+ boxa1 = pixConnComp(pix2, NULL, 8);
+ boxaWrite("/tmp/lept/regout/insert3.ba", boxa1);
+ boxa2 = boxaCopy(boxa1, L_COPY);
+ n = boxaGetCount(boxa2);
+ for (i = 0; i < n; i++) {
+ boxaRemoveBoxAndSave(boxa2, i, &box);
+ boxaInsertBox(boxa2, i, box);
+ }
+ boxaWrite("/tmp/lept/regout/insert4.ba", boxa2);
+ regTestCheckFile(rp, "/tmp/lept/regout/insert3.ba"); /* 3 */
+ regTestCheckFile(rp, "/tmp/lept/regout/insert4.ba"); /* 4 */
+ regTestCompareFiles(rp, 3, 4); /* 5 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+
+ /* ----------------- Test pixa operations -------------------- */
+ pix1 = pixRead("feyn.tif");
+ box = boxCreate(1138, 1666, 1070, 380);
+ pix2 = pixClipRectangle(pix1, box, NULL);
+ boxDestroy(&box);
+ boxa = pixConnComp(pix2, &pixa1, 8);
+ boxaDestroy(&boxa);
+ pixaWrite("/tmp/lept/regout/insert6.pa", pixa1);
+ regTestCheckFile(rp, "/tmp/lept/regout/insert6.pa"); /* 6 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Remove and insert each one */
+ pixa2 = pixaCopy(pixa1, L_COPY);
+ n = pixaGetCount(pixa2);
+ for (i = 0; i < n; i++) {
+ pixaRemovePixAndSave(pixa2, i, &pix, &box);
+ pixaInsertPix(pixa2, i, pix, box);
+ }
+ pixaWrite("/tmp/lept/regout/insert7.pa", pixa2);
+ regTestCheckFile(rp, "/tmp/lept/regout/insert7.pa"); /* 7 */
+ regTestCompareFiles(rp, 6, 7); /* 8 */
+
+ /* Move the last to the beginning; do it n times */
+ pixa3 = pixaCopy(pixa2, L_COPY);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixa3, n - 1, L_CLONE);
+ box = pixaGetBox(pixa3, n - 1, L_CLONE);
+ pixaInsertPix(pixa3, 0, pix, box);
+ pixaRemovePix(pixa3, n);
+ }
+ pixaWrite("/tmp/lept/regout/insert9.pa", pixa3);
+ regTestCheckFile(rp, "/tmp/lept/regout/insert9.pa"); /* 9 */
+
+ /* Move the first one to the end; do it n times */
+ pixa4 = pixaCopy(pixa3, L_COPY);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixa4, 0, L_CLONE);
+ box = pixaGetBox(pixa4, 0, L_CLONE);
+ pixaInsertPix(pixa4, n, pix, box); /* make sure insert works at end */
+ pixaRemovePix(pixa4, 0);
+ }
+ pixaWrite("/tmp/lept/regout/insert10.pa", pixa4);
+ regTestCheckFile(rp, "/tmp/lept/regout/insert10.pa"); /* 10 */
+ regTestCompareFiles(rp, 9, 10); /* 11 */
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ pixaDestroy(&pixa4);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/invertedtext.tif b/leptonica/prog/invertedtext.tif
new file mode 100644
index 00000000..3861864f
--- /dev/null
+++ b/leptonica/prog/invertedtext.tif
Binary files differ
diff --git a/leptonica/prog/ioformats_reg.c b/leptonica/prog/ioformats_reg.c
new file mode 100644
index 00000000..5f703513
--- /dev/null
+++ b/leptonica/prog/ioformats_reg.c
@@ -0,0 +1,921 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * ioformats_reg.c
+ *
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This is the primary Leptonica regression test for lossless
+ * read/write I/O to standard image files (png, tiff, bmp, etc.)
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ *
+ * This tests reading and writing of images in different formats
+ * It should work properly on input images of any depth, with
+ * and without colormaps. There are 7 sections.
+ *
+ * Section 1. Test write/read with lossless and lossy compression, with
+ * and without colormaps. The lossless results are tested for equality.
+ *
+ * Section 2. Test read/write to file with different tiff compressions.
+ *
+ * Section 3. Test read/write to memory with different tiff compressions.
+ *
+ * Section 4. Test read/write to memory with other compression formats.
+ *
+ * Section 5. Test multippage tiff read/write to file and memory.
+ *
+ * Section 6. Test writing 24 bpp (not 32 bpp) pix
+ *
+ * Section 7. Test header reading
+ *
+ * This test requires the following external I/O libraries
+ * libjpeg, libtiff, libpng, libz
+ * and optionally tests these:
+ * libwebp, libopenjp2, libgif
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define BMP_FILE "test1.bmp"
+#define FILE_1BPP "feyn.tif"
+#define FILE_2BPP "speckle2.png"
+#define FILE_2BPP_C "weasel2.4g.png"
+#define FILE_4BPP "speckle4.png"
+#define FILE_4BPP_C "weasel4.16c.png"
+#define FILE_8BPP_1 "dreyfus8.png"
+#define FILE_8BPP_2 "weasel8.240c.png"
+#define FILE_8BPP_3 "test8.jpg"
+#define FILE_16BPP "test16.tif"
+#define FILE_32BPP "marge.jpg"
+#define FILE_32BPP_ALPHA "test32-alpha.png"
+#define FILE_1BIT_ALPHA "test-1bit-alpha.png"
+#define FILE_CMAP_ALPHA "test-cmap-alpha.png"
+#define FILE_TRANS_ALPHA "test-fulltrans-alpha.png"
+#define FILE_GRAY_ALPHA "test-gray-alpha.png"
+#define FILE_GRAY_ALPHA_TIF "gray-alpha.tif"
+#define FILE_RGB16_TIF "rgb16.tif"
+
+static l_int32 testcomp(const char *filename, PIX *pix, l_int32 comptype);
+static l_int32 testcomp_mem(PIX *pixs, PIX **ppixt, l_int32 index,
+ l_int32 format);
+static l_int32 test_writemem(PIX *pixs, l_int32 format, char *psfile);
+static PIX *make_24_bpp_pix(PIX *pixs);
+static l_int32 get_header_data(const char *filename, l_int32 true_format);
+static const char *get_tiff_compression_name(l_int32 format);
+
+LEPT_DLL extern const char *ImageFileFormatExtensions[];
+
+int main(int argc,
+ char **argv)
+{
+char psname[256];
+char *tempname;
+l_uint8 *data;
+l_int32 i, d, n, success, failure, same;
+l_int32 w, h, bps, spp, iscmap, res;
+size_t size, nbytes;
+PIX *pix1, *pix2, *pix3, *pix4, *pix8, *pix16, *pix32;
+PIX *pix, *pixt, *pixd;
+PIXA *pixa;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+#if !HAVE_LIBJPEG
+ lept_stderr("Omitting libjpeg tests in ioformats_reg\n");
+#endif /* !HAVE_LIBJPEG */
+
+#if !HAVE_LIBTIFF
+ lept_stderr("Omitting libtiff tests in ioformats_reg\n");
+#endif /* !HAVE_LIBTIFF */
+
+#if !HAVE_LIBPNG || !HAVE_LIBZ
+ lept_stderr("Omitting libpng tests in ioformats_reg\n");
+#endif /* !HAVE_LIBPNG || !HAVE_LIBZ */
+
+#if !HAVE_LIBWEBP
+ lept_stderr("Omitting libwebp tests in ioformats_reg\n");
+#endif /* !HAVE_LIBWEBP */
+
+#if !HAVE_LIBJP2K
+ lept_stderr("Omitting libopenjp2 tests in ioformats_reg\n");
+#endif /* !HAVE_LIBJP2K */
+
+#if !HAVE_LIBGIF
+ lept_stderr("Omitting libgif tests in ioformats_reg\n");
+#endif /* !HAVE_LIBGIF */
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* --------- Part 1: Test all formats for r/w to file ---------*/
+
+ failure = FALSE;
+ success = TRUE;
+ lept_stderr("Test bmp 1 bpp file:\n");
+ if (ioFormatTest(BMP_FILE)) success = FALSE;
+
+#if HAVE_LIBTIFF
+ lept_stderr("\nTest other 1 bpp file:\n");
+ if (ioFormatTest(FILE_1BPP)) success = FALSE;
+#endif /* HAVE_LIBTIFF */
+
+#if HAVE_LIBPNG
+ lept_stderr("\nTest 2 bpp file:\n");
+ if (ioFormatTest(FILE_2BPP)) success = FALSE;
+ lept_stderr("\nTest 2 bpp file with cmap:\n");
+ if (ioFormatTest(FILE_2BPP_C)) success = FALSE;
+ lept_stderr("\nTest 4 bpp file:\n");
+ if (ioFormatTest(FILE_4BPP)) success = FALSE;
+ lept_stderr("\nTest 4 bpp file with cmap:\n");
+ if (ioFormatTest(FILE_4BPP_C)) success = FALSE;
+ lept_stderr("\nTest 8 bpp grayscale file with cmap:\n");
+ if (ioFormatTest(FILE_8BPP_1)) success = FALSE;
+ lept_stderr("\nTest 8 bpp color file with cmap:\n");
+ if (ioFormatTest(FILE_8BPP_2)) success = FALSE;
+#endif /* HAVE_LIBPNG */
+
+#if HAVE_LIBJPEG
+ lept_stderr("\nTest 8 bpp file without cmap:\n");
+ if (ioFormatTest(FILE_8BPP_3)) success = FALSE;
+#endif /* HAVE_LIBJPEG */
+
+#if HAVE_LIBTIFF
+ lept_stderr("\nTest 16 bpp file:\n");
+ if (ioFormatTest(FILE_16BPP)) success = FALSE;
+#endif /* HAVE_LIBTIFF */
+
+#if HAVE_LIBJPEG
+ lept_stderr("\nTest 32 bpp files:\n");
+ if (ioFormatTest(FILE_32BPP)) success = FALSE;
+ if (ioFormatTest(FILE_32BPP_ALPHA)) success = FALSE;
+#endif /* HAVE_LIBJPEG */
+
+#if HAVE_LIBPNG && HAVE_LIBJPEG
+ lept_stderr("\nTest spp = 1, bpp = 1, cmap with alpha file:\n");
+ if (ioFormatTest(FILE_1BIT_ALPHA)) success = FALSE;
+ lept_stderr("\nTest spp = 1, bpp = 8, cmap with alpha file:\n");
+ if (ioFormatTest(FILE_CMAP_ALPHA)) success = FALSE;
+ lept_stderr("\nTest spp = 1, fully transparent with alpha file:\n");
+ if (ioFormatTest(FILE_TRANS_ALPHA)) success = FALSE;
+ lept_stderr("\nTest spp = 2, gray with alpha file:\n");
+ if (ioFormatTest(FILE_GRAY_ALPHA)) success = FALSE;
+#endif /* HAVE_LIBJPEG */
+
+ if (success)
+ lept_stderr(
+ "\n ********** Success on all i/o format tests *********\n");
+ else
+ lept_stderr(
+ "\n ******* Failure on at least one i/o format test ******\n");
+ if (!success) failure = TRUE;
+
+
+ /* ------------------ Part 2: Test tiff r/w to file ------------------- */
+#if !HAVE_LIBTIFF
+ lept_stderr("\nNo libtiff. Skipping:\n"
+ " part 2 (tiff r/w)\n"
+ " part 3 (tiff r/w to memory)\n"
+ " part 4 (non-tiff r/w to memory)\n"
+ " part 5 (multipage tiff r/w to memory)\n\n");
+ goto part6;
+#endif /* !HAVE_LIBTIFF */
+
+ lept_stderr("\nTest tiff r/w and format extraction\n");
+ pixa = pixaCreate(6);
+ pix1 = pixRead(BMP_FILE);
+ pix2 = pixConvert1To2(NULL, pix1, 3, 0);
+ pix4 = pixConvert1To4(NULL, pix1, 15, 0);
+ pix16 = pixRead(FILE_16BPP);
+ lept_stderr("Input format: %d\n", pixGetInputFormat(pix16));
+ pix8 = pixConvert16To8(pix16, 1);
+ pix32 = pixRead(FILE_32BPP);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixaAddPix(pixa, pix8, L_INSERT);
+ pixaAddPix(pixa, pix16, L_INSERT);
+ pixaAddPix(pixa, pix32, L_INSERT);
+ n = pixaGetCount(pixa);
+
+ success = (n < 6) ? FALSE : TRUE;
+ if (!success)
+ lept_stderr("Error: only %d / 6 images loaded\n", n);
+ for (i = 0; i < n; i++) {
+ if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) {
+ success = FALSE;
+ continue;
+ }
+ d = pixGetDepth(pix);
+ lept_stderr("%d bpp\n", d);
+ if (i == 0) { /* 1 bpp */
+ pixWrite("/tmp/lept/regout/junkg3.tif", pix, IFF_TIFF_G3);
+ pixWrite("/tmp/lept/regout/junkg4.tif", pix, IFF_TIFF_G4);
+ pixWrite("/tmp/lept/regout/junkrle.tif", pix, IFF_TIFF_RLE);
+ pixWrite("/tmp/lept/regout/junkpb.tif", pix, IFF_TIFF_PACKBITS);
+ if (testcomp("/tmp/lept/regout/junkg3.tif", pix, IFF_TIFF_G3))
+ success = FALSE;
+ if (testcomp("/tmp/lept/regout/junkg4.tif", pix, IFF_TIFF_G4))
+ success = FALSE;
+ if (testcomp("/tmp/lept/regout/junkrle.tif", pix, IFF_TIFF_RLE))
+ success = FALSE;
+ if (testcomp("/tmp/lept/regout/junkpb.tif", pix, IFF_TIFF_PACKBITS))
+ success = FALSE;
+ }
+ pixWrite("/tmp/lept/regout/junklzw.tif", pix, IFF_TIFF_LZW);
+ pixWrite("/tmp/lept/regout/junkzip.tif", pix, IFF_TIFF_ZIP);
+ pixWrite("/tmp/lept/regout/junknon.tif", pix, IFF_TIFF);
+ if (testcomp("/tmp/lept/regout/junklzw.tif", pix, IFF_TIFF_LZW))
+ success = FALSE;
+ if (testcomp("/tmp/lept/regout/junkzip.tif", pix, IFF_TIFF_ZIP))
+ success = FALSE;
+ if (testcomp("/tmp/lept/regout/junknon.tif", pix, IFF_TIFF))
+ success = FALSE;
+ pixDestroy(&pix);
+ }
+
+ /* Test writing and reading tiff colormaps */
+ lept_stderr("Tiff read/write 8 bpp with cmap\n");
+ pix1 = pixRead(FILE_8BPP_2);
+ pixWrite("/tmp/lept/regout/weas8.tif", pix1, IFF_TIFF);
+ readHeaderTiff("/tmp/lept/regout/weas8.tif", 0, &w, &h, &bps, &spp,
+ NULL, &iscmap, NULL);
+ if (w != 82 || h != 73 || bps != 8 || spp != 1 || iscmap != 1) {
+ lept_stderr("Header error testing tiff cmaps\n");
+ success = FALSE;
+ }
+ pix2 = pixRead("/tmp/lept/regout/weas8.tif");
+ pixWrite("/tmp/lept/regout/weas8a.tif", pix2, IFF_TIFF);
+ pix3 = pixRead("/tmp/lept/regout/weas8a.tif");
+ pixEqual(pix1, pix3, &same);
+ if (!same) {
+ lept_stderr("Tiff read/write failed for cmaps\n");
+ success = FALSE;
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Test writing and reading 1 bpp tiff with colormap */
+ lept_stderr("Tiff read/write 1 bpp with cmap\n");
+ pix1 = pixRead("feyn-fract2.tif");
+ cmap = pixcmapCreate(1);
+ pixcmapAddColor(cmap, 0, 0, 0); /* inverted b/w */
+ pixcmapAddColor(cmap, 255, 255, 255);
+ pixSetColormap(pix1, cmap);
+ pixWrite("/tmp/lept/regout/fract1.tif", pix1, IFF_TIFF_ZIP);
+ pix2 = pixRead("/tmp/lept/regout/fract1.tif");
+ pixEqual(pix1, pix2, &same);
+ if (!same) {
+ lept_stderr("Tiff read/write failed for 1 bpp cmap\n");
+ success = FALSE;
+ }
+ cmap = pixcmapCreate(1);
+ pixcmapAddColor(cmap, 255, 255, 255);
+ pixcmapAddColor(cmap, 100, 200, 50); /* with color */
+ pixSetColormap(pix1, cmap); /* replace the colormap */
+ pixWrite("/tmp/lept/regout/fract2.tif", pix1, IFF_TIFF_ZIP);
+ pix3 = pixRead("/tmp/lept/regout/fract2.tif");
+ pixEqual(pix1, pix3, &same);
+ if (!same) {
+ lept_stderr("Tiff read/write failed for 1 bpp color cmap\n");
+ success = FALSE;
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Test writing and reading tiff with alpha */
+ lept_stderr("Tiff read/write gray plus alpha\n");
+ pix1 = pixRead(FILE_GRAY_ALPHA_TIF); /* converts to RGBA */
+ pixWrite("/tmp/lept/regout/graya.tif", pix1, IFF_TIFF);
+ readHeaderTiff("/tmp/lept/regout/graya.tif", 0, &w, &h, &bps, &spp,
+ NULL, &iscmap, NULL);
+ if (w != 100 || h != 100 || bps != 8 || spp != 4 || iscmap != 0) {
+ lept_stderr("Header error testing tiff with alpha\n");
+ success = FALSE;
+ }
+ pix2 = pixRead("/tmp/lept/regout/graya.tif");
+ pixEqual(pix1, pix2, &same);
+ if (!same) {
+ lept_stderr("Tiff read/write failed for graya.tif\n");
+ success = FALSE;
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pix1 = pixRead(FILE_GRAY_ALPHA); /* converts to RGBA */
+ pixWriteTiff("/tmp/lept/regout/graya2.tif", pix1, IFF_TIFF_ZIP, "w");
+ pix2 = pixRead("/tmp/lept/regout/graya2.tif");
+ pixEqual(pix1, pix2, &same);
+ if (!same) {
+ lept_stderr("Tiff read/write failed for graya2.tif\n");
+ success = FALSE;
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Test reading 16 bit sampled rgb tiff */
+ lept_stderr("Tiff read/write 16 bit sampled rgb\n");
+ pix1 = pixRead(FILE_RGB16_TIF); /* converts 16 to 8 bits RGB */
+ pixWrite("/tmp/lept/regout/rgb16.tif", pix1, IFF_TIFF_ZIP);
+ readHeaderTiff("/tmp/lept/regout/rgb16.tif", 0, &w, &h, &bps, &spp,
+ NULL, &iscmap, NULL);
+ if (w != 129 || h != 90 || bps != 8 || spp != 3 || iscmap != 0) {
+ lept_stderr("Header error testing tiff with alpha\n");
+ success = FALSE;
+ }
+ pix2 = pixRead("/tmp/lept/regout/rgb16.tif");
+ pixEqual(pix1, pix2, &same);
+ if (!same) {
+ lept_stderr("Tiff read/write failed for rgb16.tif\n");
+ success = FALSE;
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Test reading 32 bit rgb with approx half-sized tiff buffer */
+ lept_stderr("Tiff read/write rgb with half-sized tiff buffer\n");
+ pix1 = pixRead("testbuffer.tif");
+ pixWrite("/tmp/lept/regout/testbuffer.tif", pix1, IFF_TIFF_ZIP);
+ readHeaderTiff("/tmp/lept/regout/testbuffer.tif", 0, &w, &h, &bps, &spp,
+ &res, &iscmap, NULL);
+ if (w != 659 || h != 799 || bps != 8 || spp != 3 || res != 96) {
+ lept_stderr("Header error testing rgb tiff with small tif buffer\n");
+ success = FALSE;
+ }
+ pix2 = pixRead("/tmp/lept/regout/testbuffer.tif");
+ pixEqual(pix1, pix2, &same);
+ if (!same) {
+ lept_stderr("Tiff read/write failed for testbuffer.tif\n");
+ success = FALSE;
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ if (success)
+ lept_stderr(
+ "\n ********** Success on tiff r/w to file *********\n\n");
+ else
+ lept_stderr(
+ "\n ******* Failure on at least one tiff r/w to file ******\n\n");
+ if (!success) failure = TRUE;
+
+ /* ------------------ Part 3: Test tiff r/w to memory ----------------- */
+
+ success = (n < 6) ? FALSE : TRUE;
+ for (i = 0; i < n; i++) {
+ if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) {
+ success = FALSE;
+ continue;
+ }
+ d = pixGetDepth(pix);
+ lept_stderr("%d bpp\n", d);
+ if (i == 0) { /* 1 bpp */
+ pixWriteMemTiff(&data, &size, pix, IFF_TIFF_G3);
+ nbytes = nbytesInFile("/tmp/lept/regout/junkg3.tif");
+ lept_stderr("nbytes = %lu, size = %lu\n",
+ (unsigned long)nbytes, (unsigned long)size);
+ pixt = pixReadMemTiff(data, size, 0);
+ if (testcomp_mem(pix, &pixt, i, IFF_TIFF_G3)) success = FALSE;
+ lept_free(data);
+ pixWriteMemTiff(&data, &size, pix, IFF_TIFF_G4);
+ nbytes = nbytesInFile("/tmp/lept/regout/junkg4.tif");
+ lept_stderr("nbytes = %lu, size = %lu\n",
+ (unsigned long)nbytes, (unsigned long)size);
+ pixt = pixReadMemTiff(data, size, 0);
+ if (testcomp_mem(pix, &pixt, i, IFF_TIFF_G4)) success = FALSE;
+ readHeaderMemTiff(data, size, 0, &w, &h, &bps, &spp,
+ NULL, NULL, NULL);
+ lept_stderr("(w,h,bps,spp) = (%d,%d,%d,%d)\n", w, h, bps, spp);
+ lept_free(data);
+ pixWriteMemTiff(&data, &size, pix, IFF_TIFF_RLE);
+ nbytes = nbytesInFile("/tmp/lept/regout/junkrle.tif");
+ lept_stderr("nbytes = %lu, size = %lu\n",
+ (unsigned long)nbytes, (unsigned long)size);
+ pixt = pixReadMemTiff(data, size, 0);
+ if (testcomp_mem(pix, &pixt, i, IFF_TIFF_RLE)) success = FALSE;
+ lept_free(data);
+ pixWriteMemTiff(&data, &size, pix, IFF_TIFF_PACKBITS);
+ nbytes = nbytesInFile("/tmp/lept/regout/junkpb.tif");
+ lept_stderr("nbytes = %lu, size = %lu\n",
+ (unsigned long)nbytes, (unsigned long)size);
+ pixt = pixReadMemTiff(data, size, 0);
+ if (testcomp_mem(pix, &pixt, i, IFF_TIFF_PACKBITS)) success = FALSE;
+ lept_free(data);
+ }
+ pixWriteMemTiff(&data, &size, pix, IFF_TIFF_LZW);
+ pixt = pixReadMemTiff(data, size, 0);
+ if (testcomp_mem(pix, &pixt, i, IFF_TIFF_LZW)) success = FALSE;
+ lept_free(data);
+ pixWriteMemTiff(&data, &size, pix, IFF_TIFF_ZIP);
+ pixt = pixReadMemTiff(data, size, 0);
+ if (testcomp_mem(pix, &pixt, i, IFF_TIFF_ZIP)) success = FALSE;
+ readHeaderMemTiff(data, size, 0, &w, &h, &bps, &spp, NULL, NULL, NULL);
+ lept_stderr("(w,h,bps,spp) = (%d,%d,%d,%d)\n", w, h, bps, spp);
+ lept_free(data);
+ pixWriteMemTiff(&data, &size, pix, IFF_TIFF);
+ pixt = pixReadMemTiff(data, size, 0);
+ if (testcomp_mem(pix, &pixt, i, IFF_TIFF)) success = FALSE;
+ lept_free(data);
+ pixDestroy(&pix);
+ }
+ if (success)
+ lept_stderr(
+ "\n ********** Success on tiff r/w to memory *********\n\n");
+ else
+ lept_stderr(
+ "\n ******* Failure on at least one tiff r/w to memory ******\n\n");
+ if (!success) failure = TRUE;
+
+ /* ---------------- Part 4: Test non-tiff r/w to memory ---------------- */
+
+ success = (n < 6) ? FALSE : TRUE;
+ for (i = 0; i < n; i++) {
+ if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) {
+ success = FALSE;
+ continue;
+ }
+ d = pixGetDepth(pix);
+ snprintf(psname, sizeof(psname), "/tmp/lept/regout/junkps.%d", d);
+ lept_stderr("%d bpp\n", d);
+ if (test_writemem(pix, IFF_PNM, NULL)) success = FALSE;
+ if (test_writemem(pix, IFF_PS, psname)) success = FALSE;
+ if (d == 16) {
+ pixDestroy(&pix);
+ continue;
+ }
+ if (test_writemem(pix, IFF_PNG, NULL)) success = FALSE;
+ if (test_writemem(pix, IFF_BMP, NULL)) success = FALSE;
+ if (d != 32)
+ if (test_writemem(pix, IFF_GIF, NULL)) success = FALSE;
+ if (d == 8 || d == 32) {
+ if (test_writemem(pix, IFF_JFIF_JPEG, NULL)) success = FALSE;
+ if (test_writemem(pix, IFF_JP2, NULL)) success = FALSE;
+ if (test_writemem(pix, IFF_WEBP, NULL)) success = FALSE;
+ }
+ pixDestroy(&pix);
+ }
+ if (success)
+ lept_stderr(
+ "\n ********** Success on non-tiff r/w to memory *********\n\n");
+ else
+ lept_stderr(
+ "\n **** Failure on at least one non-tiff r/w to memory *****\n\n");
+ if (!success) failure = TRUE;
+ pixaDestroy(&pixa);
+
+ /* ------------ Part 5: Test multipage tiff r/w to memory ------------ */
+
+ /* Make a multipage tiff file, and read it back into memory */
+ pix = pixRead("feyn.tif");
+ pixa = pixaSplitPix(pix, 3, 3, 0, 0);
+ for (i = 0; i < 9; i++) {
+ if ((pixt = pixaGetPix(pixa, i, L_CLONE)) == NULL)
+ continue;
+ if (i == 0)
+ pixWriteTiff("/tmp/lept/regout/junktiffmpage.tif", pixt,
+ IFF_TIFF_G4, "w");
+ else
+ pixWriteTiff("/tmp/lept/regout/junktiffmpage.tif", pixt,
+ IFF_TIFF_G4, "a");
+ pixDestroy(&pixt);
+ }
+ data = l_binaryRead("/tmp/lept/regout/junktiffmpage.tif", &nbytes);
+ pixaDestroy(&pixa);
+
+ /* Read the individual pages from memory to a pix */
+ pixa = pixaCreate(9);
+ for (i = 0; i < 9; i++) {
+ pixt = pixReadMemTiff(data, nbytes, i);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ }
+ lept_free(data);
+
+ /* Un-tile the pix in the pixa back to the original image */
+ pixt = pixaDisplayUnsplit(pixa, 3, 3, 0, 0);
+ pixaDestroy(&pixa);
+
+ /* Clip to foreground to remove any extra rows or columns */
+ pixClipToForeground(pix, &pix1, NULL);
+ pixClipToForeground(pixt, &pix2, NULL);
+ pixEqual(pix1, pix2, &same);
+ if (same)
+ lept_stderr(
+ "\n ******* Success on tiff multipage read from memory ******\n\n");
+ else
+ lept_stderr(
+ "\n ******* Failure on tiff multipage read from memory ******\n\n");
+ if (!same) failure = TRUE;
+
+ pixDestroy(&pix);
+ pixDestroy(&pixt);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* ------------ Part 6: Test 24 bpp writing ------------ */
+#if !HAVE_LIBTIFF
+part6:
+#endif /* !HAVE_LIBTIFF */
+
+#if !HAVE_LIBPNG || !HAVE_LIBJPEG || !HAVE_LIBTIFF
+ lept_stderr("Missing libpng, libjpeg or libtiff. Skipping:\n"
+ " part 6 (24 bpp r/w)\n"
+ " part 7 (header read)\n\n");
+ goto finish;
+#endif /* !HAVE_LIBPNG || !HAVE_LIBJPEG || !HAVE_LIBTIFF */
+
+ /* Generate a 24 bpp (not 32 bpp !!) rgb pix and write it out */
+ success = TRUE;
+ if ((pix = pixRead("marge.jpg")) == NULL)
+ success = FALSE;
+ pixt = make_24_bpp_pix(pix);
+ pixWrite("/tmp/lept/regout/junk24.png", pixt, IFF_PNG);
+ pixWrite("/tmp/lept/regout/junk24.jpg", pixt, IFF_JFIF_JPEG);
+ pixWrite("/tmp/lept/regout/junk24.tif", pixt, IFF_TIFF);
+ pixd = pixRead("/tmp/lept/regout/junk24.png");
+ pixEqual(pix, pixd, &same);
+ if (same) {
+ lept_stderr(" **** success writing 24 bpp png ****\n");
+ } else {
+ lept_stderr(" **** failure writing 24 bpp png ****\n");
+ success = FALSE;
+ }
+ pixDestroy(&pixd);
+ pixd = pixRead("/tmp/lept/regout/junk24.jpg");
+ regTestCompareSimilarPix(rp, pix, pixd, 10, 0.0002, 0);
+ pixDestroy(&pixd);
+ pixd = pixRead("/tmp/lept/regout/junk24.tif");
+ pixEqual(pix, pixd, &same);
+ if (same) {
+ lept_stderr(" **** success writing 24 bpp tif ****\n");
+ } else {
+ lept_stderr(" **** failure writing 24 bpp tif ****\n");
+ success = FALSE;
+ }
+ pixDestroy(&pixd);
+ if (success)
+ lept_stderr("\n ******* Success on 24 bpp rgb writing *******\n\n");
+ else
+ lept_stderr("\n ******* Failure on 24 bpp rgb writing *******\n\n");
+ if (!success) failure = TRUE;
+ pixDestroy(&pix);
+ pixDestroy(&pixt);
+
+ /* -------------- Part 7: Read header information -------------- */
+ success = TRUE;
+ if (get_header_data(FILE_1BPP, IFF_TIFF_G4)) success = FALSE;
+ if (get_header_data(FILE_2BPP, IFF_PNG)) success = FALSE;
+ if (get_header_data(FILE_2BPP_C, IFF_PNG)) success = FALSE;
+ if (get_header_data(FILE_4BPP, IFF_PNG)) success = FALSE;
+ if (get_header_data(FILE_4BPP_C, IFF_PNG)) success = FALSE;
+ if (get_header_data(FILE_8BPP_1, IFF_PNG)) success = FALSE;
+ if (get_header_data(FILE_8BPP_2, IFF_PNG)) success = FALSE;
+ if (get_header_data(FILE_8BPP_3, IFF_JFIF_JPEG)) success = FALSE;
+ if (get_header_data(FILE_GRAY_ALPHA, IFF_PNG)) success = FALSE;
+ if (get_header_data(FILE_16BPP, IFF_TIFF_ZIP)) success = FALSE;
+ if (get_header_data(FILE_32BPP, IFF_JFIF_JPEG)) success = FALSE;
+ if (get_header_data(FILE_32BPP_ALPHA, IFF_PNG)) success = FALSE;
+
+ pix = pixRead(FILE_8BPP_1);
+ tempname = l_makeTempFilename();
+ pixWrite(tempname, pix, IFF_PNM);
+ if (get_header_data(tempname, IFF_PNM)) success = FALSE;
+ pixDestroy(&pix);
+
+ /* These tiff formats work on 1 bpp images */
+ pix = pixRead(FILE_1BPP);
+ pixWrite(tempname, pix, IFF_TIFF_G3);
+ if (get_header_data(tempname, IFF_TIFF_G3)) success = FALSE;
+ pixWrite(tempname, pix, IFF_TIFF_G4);
+ if (get_header_data(tempname, IFF_TIFF_G4)) success = FALSE;
+ pixWrite(tempname, pix, IFF_TIFF_PACKBITS);
+ if (get_header_data(tempname, IFF_TIFF_PACKBITS)) success = FALSE;
+ pixWrite(tempname, pix, IFF_TIFF_RLE);
+ if (get_header_data(tempname, IFF_TIFF_RLE)) success = FALSE;
+ pixWrite(tempname, pix, IFF_TIFF_LZW);
+ if (get_header_data(tempname, IFF_TIFF_LZW)) success = FALSE;
+ pixWrite(tempname, pix, IFF_TIFF_ZIP);
+ if (get_header_data(tempname, IFF_TIFF_ZIP)) success = FALSE;
+ pixWrite(tempname, pix, IFF_TIFF);
+ if (get_header_data(tempname, IFF_TIFF)) success = FALSE;
+ pixDestroy(&pix);
+ lept_rmfile(tempname);
+ lept_free(tempname);
+
+ if (success)
+ lept_stderr( "\n ******* Success on reading headers *******\n\n");
+ else
+ lept_stderr( "\n ******* Failure on reading headers *******\n\n");
+ if (!success) failure = TRUE;
+
+#if !HAVE_LIBPNG || !HAVE_LIBJPEG || !HAVE_LIBTIFF
+finish:
+#endif /* !HAVE_LIBPNG || !HAVE_LIBJPEG || !HAVE_LIBTIFF */
+
+ if (!failure)
+ lept_stderr(" ******* Success on all tests *******\n\n");
+ else
+ lept_stderr(" ******* Failure on at least one test *******\n\n");
+
+ return regTestCleanup(rp);
+}
+
+
+ /* Returns 1 on error */
+static l_int32
+testcomp(const char *filename,
+ PIX *pix,
+ l_int32 comptype)
+{
+l_int32 format, sameformat, sameimage;
+FILE *fp;
+PIX *pixt;
+
+ fp = lept_fopen(filename, "rb");
+ findFileFormatStream(fp, &format);
+ sameformat = TRUE;
+ if (format != comptype) {
+ lept_stderr("File %s has format %d, not comptype %d\n",
+ filename, format, comptype);
+ sameformat = FALSE;
+ }
+ lept_fclose(fp);
+ pixt = pixRead(filename);
+ pixEqual(pix, pixt, &sameimage);
+ pixDestroy(&pixt);
+ if (!sameimage)
+ lept_stderr("Write/read fail for file %s with format %d\n",
+ filename, format);
+ return (!sameformat || !sameimage);
+}
+
+
+ /* Returns 1 on error */
+static l_int32
+testcomp_mem(PIX *pixs,
+ PIX **ppixt, /* input; nulled on return */
+ l_int32 index,
+ l_int32 format)
+{
+l_int32 sameimage;
+PIX *pixt;
+
+ pixt = *ppixt;
+ pixEqual(pixs, pixt, &sameimage);
+ if (!sameimage)
+ lept_stderr("Mem Write/read fail for file %d with format %d\n",
+ index, format);
+ pixDestroy(&pixt);
+ *ppixt = NULL;
+ return (!sameimage);
+}
+
+
+ /* Returns 1 on error */
+static l_int32
+test_writemem(PIX *pixs,
+ l_int32 format,
+ char *psfile)
+{
+l_uint8 *data = NULL;
+l_int32 same = TRUE;
+l_int32 ds, dd;
+l_float32 diff;
+size_t size = 0;
+PIX *pixd = NULL;
+
+ if (format == IFF_PS) {
+ pixWriteMemPS(&data, &size, pixs, NULL, 0, 1.0);
+ l_binaryWrite(psfile, "w", data, size);
+ lept_free(data);
+ return 0;
+ }
+
+ /* Fail silently if library is not available */
+#if !HAVE_LIBJPEG
+ if (format == IFF_JFIF_JPEG)
+ return 0;
+#endif /* !HAVE_LIBJPEG */
+#if !HAVE_LIBPNG
+ if (format == IFF_PNG)
+ return 0;
+#endif /* !HAVE_LIBPNG */
+#if !HAVE_LIBTIFF
+ if (format == IFF_TIFF)
+ return 0;
+#endif /* !HAVE_LIBTIFF */
+#if !HAVE_LIBWEBP
+ if (format == IFF_WEBP)
+ return 0;
+#endif /* !HAVE_LIBWEBP */
+#if !HAVE_LIBJP2K
+ if (format == IFF_JP2)
+ return 0;
+#endif /* !HAVE_LIBJP2K */
+#if !HAVE_LIBGIF
+ if (format == IFF_GIF)
+ return 0;
+#endif /* !HAVE_LIBGIF */
+
+ if (pixWriteMem(&data, &size, pixs, format)) {
+ lept_stderr("Mem write fail for format %d\n", format);
+ return 1;
+ }
+ if ((pixd = pixReadMem(data, size)) == NULL) {
+ lept_stderr("Mem read fail for format %d\n", format);
+ lept_free(data);
+ return 1;
+ }
+
+ if (format == IFF_JFIF_JPEG || format == IFF_JP2 ||
+ format == IFF_WEBP || format == IFF_TIFF_JPEG) {
+ ds = pixGetDepth(pixs);
+ dd = pixGetDepth(pixd);
+ if (dd == 8) {
+ pixCompareGray(pixs, pixd, L_COMPARE_ABS_DIFF, 0, NULL, &diff,
+ NULL, NULL);
+ } else if (ds == 32 && dd == 32) {
+ pixCompareRGB(pixs, pixd, L_COMPARE_ABS_DIFF, 0, NULL, &diff,
+ NULL, NULL);
+ } else {
+ lept_stderr("skipping: ds = %d, dd = %d, format = %d\n",
+ ds, dd, format);
+ lept_free(data);
+ pixDestroy(&pixd);
+ return 0;
+ }
+
+/* lept_stderr(" size = %lu bytes; diff = %5.2f, format = %d\n",
+ (unsigned long)size, diff, format); */
+ if (diff > 8.0) {
+ same = FALSE;
+ lept_stderr("Mem write/read fail for format %d, diff = %5.2f\n",
+ format, diff);
+ }
+ } else {
+ pixEqual(pixs, pixd, &same);
+ if (!same)
+ lept_stderr("Mem write/read fail for format %d\n", format);
+ }
+ pixDestroy(&pixd);
+ lept_free(data);
+ return (!same);
+}
+
+
+ /* Composes 24 bpp rgb pix */
+static PIX *
+make_24_bpp_pix(PIX *pixs)
+{
+l_int32 i, j, w, h, wpls, wpld, rval, gval, bval;
+l_uint32 *lines, *lined, *datas, *datad;
+PIX *pixd;
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixd = pixCreate(w, h, 24);
+ 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++) {
+ extractRGBValues(lines[j], &rval, &gval, &bval);
+ *((l_uint8 *)lined + 3 * j) = rval;
+ *((l_uint8 *)lined + 3 * j + 1) = gval;
+ *((l_uint8 *)lined + 3 * j + 2) = bval;
+ }
+ }
+
+ return pixd;
+}
+
+
+ /* Retrieve header data from file */
+static l_int32
+get_header_data(const char *filename,
+ l_int32 true_format)
+{
+const char *tiff_compression_name = "undefined";
+l_uint8 *data;
+l_int32 ret1, ret2, format1, format2;
+l_int32 w1, w2, h1, h2, d1, d2, bps1, bps2, spp1, spp2, iscmap1, iscmap2;
+size_t size1, size2;
+
+ /* Fail silently if library is not available */
+#if !HAVE_LIBJPEG
+ if (true_format == IFF_JFIF_JPEG)
+ return 0;
+#endif /* !HAVE_LIBJPEG */
+#if !HAVE_LIBPNG
+ if (true_format == IFF_PNG)
+ return 0;
+#endif /* !HAVE_LIBPNG */
+#if !HAVE_LIBTIFF
+ if (L_FORMAT_IS_TIFF(true_format))
+ return 0;
+#endif /* !HAVE_LIBTIFF */
+
+ /* Read header from file */
+ size1 = nbytesInFile(filename);
+ ret1 = pixReadHeader(filename, &format1, &w1, &h1, &bps1, &spp1, &iscmap1);
+ d1 = bps1 * spp1;
+ if (d1 == 24) d1 = 32;
+ if (ret1)
+ lept_stderr("Error: couldn't read header data: %s\n", filename);
+ else {
+ if (L_FORMAT_IS_TIFF(format1)) {
+ tiff_compression_name = get_tiff_compression_name(format1);
+ lept_stderr("Format data for image %s with format %s:\n"
+ " nbytes = %lu, size (w, h, d) = (%d, %d, %d)\n"
+ " bps = %d, spp = %d, iscmap = %d\n",
+ filename, tiff_compression_name,
+ (unsigned long)size1, w1, h1, d1,
+ bps1, spp1, iscmap1);
+ } else {
+ lept_stderr("Format data for image %s with format %s:\n"
+ " nbytes = %lu, size (w, h, d) = (%d, %d, %d)\n"
+ " bps = %d, spp = %d, iscmap = %d\n",
+ filename, ImageFileFormatExtensions[format1],
+ (unsigned long)size1, w1, h1, d1, bps1, spp1, iscmap1);
+ }
+ if (format1 != true_format) {
+ lept_stderr("Error: format is %d; should be %d\n",
+ format1, true_format);
+ ret1 = 1;
+ }
+ }
+
+ /* Read header from array in memory */
+ data = l_binaryRead(filename, &size2);
+ ret2 = pixReadHeaderMem(data, size2, &format2, &w2, &h2, &bps2,
+ &spp2, &iscmap2);
+ lept_free(data);
+ d2 = bps2 * spp2;
+ if (d2 == 24) d2 = 32;
+ if (ret2) {
+ lept_stderr("Error: couldn't mem-read header data: %s\n", filename);
+ } else {
+ if (size1 != size2 || format1 != format2 || w1 != w2 ||
+ h1 != h2 || d1 != d2 || bps1 != bps2 || spp1 != spp2 ||
+ iscmap1 != iscmap2) {
+ lept_stderr("Inconsistency reading image %s with format %s\n",
+ filename, tiff_compression_name);
+ ret2 = 1;
+ }
+ }
+ return ret1 || ret2;
+}
+
+
+static const char *
+get_tiff_compression_name(l_int32 format)
+{
+ const char *tiff_compression_name = "unknown";
+ if (format == IFF_TIFF_G4)
+ tiff_compression_name = "tiff_g4";
+ else if (format == IFF_TIFF_G3)
+ tiff_compression_name = "tiff_g3";
+ else if (format == IFF_TIFF_ZIP)
+ tiff_compression_name = "tiff_zip";
+ else if (format == IFF_TIFF_LZW)
+ tiff_compression_name = "tiff_lzw";
+ else if (format == IFF_TIFF_RLE)
+ tiff_compression_name = "tiff_rle";
+ else if (format == IFF_TIFF_PACKBITS)
+ tiff_compression_name = "tiff_packbits";
+ else if (format == IFF_TIFF_JPEG)
+ tiff_compression_name = "tiff_jpeg";
+ else if (format == IFF_TIFF)
+ tiff_compression_name = "tiff_uncompressed";
+ else
+ lept_stderr("format %d: not tiff\n", format);
+ return tiff_compression_name;
+}
diff --git a/leptonica/prog/iomisc_reg.c b/leptonica/prog/iomisc_reg.c
new file mode 100644
index 00000000..eb8d94bd
--- /dev/null
+++ b/leptonica/prog/iomisc_reg.c
@@ -0,0 +1,300 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * iomisc_reg.c
+ *
+ * Tests several special I/O operations:
+ * * special operations for handling 16 bpp png input
+ * * zlib compression quality in png
+ * * chroma sampling options in jpeg
+ * * read/write of alpha with png
+ * * i/o with colormaps
+ * * removal and regeneration of rgb and gray colormaps
+ * * tiff compression
+ *
+ * This does not test these exotic formats:
+ * * multipage/custom tiff (tested by mtiff_reg)
+ * * pdf (tested by pdfiotest and pdfseg_reg)
+ * * PostScript (tested by psio_reg and psioseg_reg)
+ *
+ * The large error values that are being used for tests 3-7 and 36
+ * are only required for older versions of tifflib (e.g., libtiff 4.0.7).
+ * For libtiff 4.0.11, small values like 10 will pass tests 6, 7 and 36.
+ * The issue was initially found on the AArch64 (ARM) processor.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+LEPT_DLL extern const char *ImageFileFormatExtensions[];
+
+static const size_t zlibsize[5] = {1047873, 215039, 195778, 189709, 180987};
+static const size_t tiffsize[8] = {65674, 34872, 20482, 20998, 11178,
+ 21500, 18472, 151885};
+
+int main(int argc,
+ char **argv)
+{
+char *text;
+l_int32 w, h, d, level, wpl, format, xres, yres;
+l_int32 bps, spp, res, iscmap;
+size_t size;
+FILE *fp;
+PIX *pixs, *pixg, *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/io");
+
+ /* Test 16 to 8 stripping */
+ pixs = pixRead("test16.tif");
+ pixWrite("/tmp/lept/io/test16.png", pixs, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/io/test16.png"); /* 0 */
+ pix1 = pixRead("/tmp/lept/io/test16.png");
+ d = pixGetDepth(pix1);
+ regTestCompareValues(rp, 8, d, 0.0); /* 1 */
+ pixDestroy(&pix1);
+ l_pngSetReadStrip16To8(0);
+ pix1 = pixRead("/tmp/lept/io/test16.png");
+ d = pixGetDepth(pix1);
+ regTestCompareValues(rp, 16, d, 0.0); /* 2 */
+ pixDestroy(&pix1);
+ pixDestroy(&pixs);
+
+ /* Test zlib compression in png */
+ pixs = pixRead("feyn.tif");
+ for (level = 0; level < 5; level += 1) {
+ pixSetZlibCompression(pixs, 2 * level);
+ pixWrite("/tmp/lept/io/zlibtest.png", pixs, IFF_PNG);
+ size = nbytesInFile("/tmp/lept/io/zlibtest.png");
+ regTestCompareValues(rp, zlibsize[level], size, 3000.0); /* 3 - 7 */
+ if (rp->display)
+ lept_stderr("zlib level = %d, file size = %ld\n",
+ level, (unsigned long)size);
+ }
+ pixDestroy(&pixs);
+
+ /* Test chroma sampling options in jpeg */
+ pixs = pixRead("marge.jpg");
+ pixWrite("/tmp/lept/io/chromatest1.jpg", pixs, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/io/chromatest1.jpg"); /* 8 */
+ if (rp->display) {
+ size = nbytesInFile("/tmp/lept/io/chromatest1.jpg");
+ lept_stderr("chroma default: file size = %ld\n", (unsigned long)size);
+ }
+ pixSetChromaSampling(pixs, 0);
+ pixWrite("/tmp/lept/io/chromatest2.jpg", pixs, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/io/chromatest2.jpg"); /* 9 */
+ if (rp->display) {
+ size = nbytesInFile("/tmp/lept/io/chromatest2.jpg");
+ lept_stderr("no ch. sampling: file size = %ld\n", (unsigned long)size);
+ }
+ pixSetChromaSampling(pixs, 1);
+ pixWrite("/tmp/lept/io/chromatest3.jpg", pixs, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/io/chromatest3.jpg"); /* 10 */
+ if (rp->display) {
+ size = nbytesInFile("/tmp/lept/io/chromatest3.jpg");
+ lept_stderr("chroma default: file size = %ld\n", (unsigned long)size);
+ }
+ pixDestroy(&pixs);
+
+ /* Test read/write of alpha with png */
+ pixs = pixRead("books_logo.png");
+ pixDisplayWithTitle(pixs, 0, 100, NULL, rp->display);
+ pixg = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+ regTestWritePixAndCheck(rp, pixg, IFF_PNG); /* 11 */
+ pixDisplayWithTitle(pixg, 300, 100, NULL, rp->display);
+ pixDestroy(&pixg);
+ pix1 = pixAlphaBlendUniform(pixs, 0xffffff00); /* render rgb over white */
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 12 */
+ pixDisplayWithTitle(pix1, 0, 250, NULL, rp->display);
+ pix2 = pixSetAlphaOverWhite(pix1); /* regenerate alpha from white */
+ pixWrite("/tmp/lept/io/logo2.png", pix2, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/io/logo2.png"); /* 13 */
+ pixDisplayWithTitle(pix2, 0, 400, NULL, rp->display);
+ pixg = pixGetRGBComponent(pix2, L_ALPHA_CHANNEL);
+ regTestWritePixAndCheck(rp, pixg, IFF_PNG); /* 14 */
+ pixDisplayWithTitle(pixg, 300, 400, NULL, rp->display);
+ pixDestroy(&pixg);
+ pix3 = pixRead("/tmp/lept/io/logo2.png");
+ pix4 = pixAlphaBlendUniform(pix3, 0x00ffff00); /* render rgb over cyan */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 15 */
+ pixDisplayWithTitle(pix3, 0, 550, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pixs);
+
+ /* A little fun with rgb colormaps */
+ pixs = pixRead("weasel4.11c.png");
+ pixa = pixaCreate(6);
+ pixaAddPix(pixa, pixs, L_CLONE);
+ pixGetDimensions(pixs, &w, &h, &d);
+ wpl = pixGetWpl(pixs);
+ if (rp->display)
+ lept_stderr("w = %d, h = %d, d = %d, wpl = %d\n", w, h, d, wpl);
+ pixGetResolution(pixs, &xres, &yres);
+ if (rp->display && xres != 0 && yres != 0)
+ lept_stderr("xres = %d, yres = %d\n", xres, yres);
+ cmap = pixGetColormap(pixs);
+ /* Write and read back the colormap */
+ if (rp->display) pixcmapWriteStream(stderr, pixGetColormap(pixs));
+ fp = lept_fopen("/tmp/lept/io/cmap1", "wb");
+ pixcmapWriteStream(fp, pixGetColormap(pixs));
+ lept_fclose(fp);
+ regTestCheckFile(rp, "/tmp/lept/io/cmap1"); /* 16 */
+ fp = lept_fopen("/tmp/lept/io/cmap1", "rb");
+ cmap = pixcmapReadStream(fp);
+ lept_fclose(fp);
+ fp = lept_fopen("/tmp/lept/io/cmap2", "wb");
+ pixcmapWriteStream(fp, cmap);
+ lept_fclose(fp);
+ regTestCheckFile(rp, "/tmp/lept/io/cmap2"); /* 17 */
+ pixcmapDestroy(&cmap);
+
+ /* Remove and regenerate colormap */
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 18 */
+ pixaAddPix(pixa, pix1, L_CLONE);
+ pix2 = pixConvertRGBToColormap(pix1, 1);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 19 */
+ pixaAddPix(pixa, pix2, L_CLONE);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Remove and regnerate gray colormap */
+ pixs = pixRead("weasel4.5g.png");
+ pixaAddPix(pixa, pixs, L_CLONE);
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 20 */
+ pixaAddPix(pixa, pix1, L_CLONE);
+ pix2 = pixConvertGrayToColormap(pix1);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 21 */
+ pixaAddPix(pixa, pix2, L_CLONE);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pix3 = pixaDisplayTiled(pixa, 400, 0, 20);
+ pixDisplayWithTitle(pix3, 0, 750, NULL, rp->display);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+
+ /* Other fields in the pix */
+ format = pixGetInputFormat(pixs);
+ regTestCompareValues(rp, format, IFF_PNG, 0.0); /* 22 */
+ if (rp->display)
+ lept_stderr("Input format extension: %s\n",
+ ImageFileFormatExtensions[format]);
+ pixSetText(pixs, "reconstituted 4-bit weasel");
+ text = pixGetText(pixs);
+ if (rp->display && text && strlen(text) != 0)
+ lept_stderr("Text: %s\n", text);
+ pixDestroy(&pixs);
+
+ /* Some tiff compression and headers */
+ readHeaderTiff("feyn-fract.tif", 0, &w, &h, &bps, &spp,
+ &res, &iscmap, &format);
+ if (rp->display) {
+ lept_stderr("w = %d, h = %d, bps = %d, spp = %d, res = %d, cmap = %d\n",
+ w, h, bps, spp, res, iscmap);
+ lept_stderr("Input format extension: %s\n",
+ ImageFileFormatExtensions[format]);
+ }
+ pixs = pixRead("feyn-fract.tif");
+ pixWrite("/tmp/lept/io/fract1.tif", pixs, IFF_TIFF);
+ regTestCheckFile(rp, "/tmp/lept/io/fract1.tif"); /* 23 */
+ size = nbytesInFile("/tmp/lept/io/fract1.tif");
+ regTestCompareValues(rp, tiffsize[0], size, 0.0); /* 24 */
+ if (rp->display)
+ lept_stderr("uncompressed: %ld\n", (unsigned long)size);
+ pixWrite("/tmp/lept/io/fract2.tif", pixs, IFF_TIFF_PACKBITS);
+ regTestCheckFile(rp, "/tmp/lept/io/fract2.tif"); /* 25 */
+ size = nbytesInFile("/tmp/lept/io/fract2.tif");
+ regTestCompareValues(rp, tiffsize[1], size, 0.0); /* 26 */
+ if (rp->display)
+ lept_stderr("packbits: %ld\n", (unsigned long)size);
+ pixWrite("/tmp/lept/io/fract3.tif", pixs, IFF_TIFF_RLE);
+ regTestCheckFile(rp, "/tmp/lept/io/fract3.tif"); /* 27 */
+ size = nbytesInFile("/tmp/lept/io/fract3.tif");
+ regTestCompareValues(rp, tiffsize[2], size, 0.0); /* 28 */
+ if (rp->display)
+ lept_stderr("rle: %ld\n", (unsigned long)size);
+ pixWrite("/tmp/lept/io/fract4.tif", pixs, IFF_TIFF_G3);
+ regTestCheckFile(rp, "/tmp/lept/io/fract4.tif"); /* 29 */
+ size = nbytesInFile("/tmp/lept/io/fract4.tif");
+ regTestCompareValues(rp, tiffsize[3], size, 0.0); /* 30 */
+ if (rp->display)
+ lept_stderr("g3: %ld\n", (unsigned long)size);
+ pixWrite("/tmp/lept/io/fract5.tif", pixs, IFF_TIFF_G4);
+ regTestCheckFile(rp, "/tmp/lept/io/fract5.tif"); /* 31 */
+ size = nbytesInFile("/tmp/lept/io/fract5.tif");
+ regTestCompareValues(rp, tiffsize[4], size, 0.0); /* 32 */
+ if (rp->display)
+ lept_stderr("g4: %ld\n", (unsigned long)size);
+ pixWrite("/tmp/lept/io/fract6.tif", pixs, IFF_TIFF_LZW);
+ regTestCheckFile(rp, "/tmp/lept/io/fract6.tif"); /* 33 */
+ size = nbytesInFile("/tmp/lept/io/fract6.tif");
+ regTestCompareValues(rp, tiffsize[5], size, 0.0); /* 34 */
+ if (rp->display)
+ lept_stderr("lzw: %ld\n", (unsigned long)size);
+ pixWrite("/tmp/lept/io/fract7.tif", pixs, IFF_TIFF_ZIP);
+ regTestCheckFile(rp, "/tmp/lept/io/fract7.tif"); /* 35 */
+ size = nbytesInFile("/tmp/lept/io/fract7.tif");
+ regTestCompareValues(rp, tiffsize[6], size, 200.0); /* 36 */
+ if (rp->display)
+ lept_stderr("zip: %ld\n", (unsigned long)size);
+ pixg = pixConvertTo8(pixs, 0);
+ pixWrite("/tmp/lept/io/fract8.tif", pixg, IFF_TIFF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/io/fract8.tif"); /* 37 */
+ size = nbytesInFile("/tmp/lept/io/fract8.tif");
+ regTestCompareValues(rp, tiffsize[7], size, 100.0); /* 38 */
+ if (rp->display)
+ lept_stderr("jpeg: %ld\n", (unsigned long)size);
+ pixDestroy(&pixs);
+ pixDestroy(&pixg);
+
+ /* Test read/write of alpha with pnm */
+ pixs = pixRead("books_logo.png");
+ pixWrite("/tmp/lept/io/alpha1.pnm", pixs, IFF_PNM);
+ regTestCheckFile(rp, "/tmp/lept/io/alpha1.pnm"); /* 39 */
+ pix1 = pixRead("/tmp/lept/io/alpha1.pnm");
+ regTestComparePix(rp, pixs, pix1); /* 40 */
+ pixDisplayWithTitle(pix1, 600, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/italic.png b/leptonica/prog/italic.png
new file mode 100644
index 00000000..7fdc9fc2
--- /dev/null
+++ b/leptonica/prog/italic.png
Binary files differ
diff --git a/leptonica/prog/italic_reg.c b/leptonica/prog/italic_reg.c
new file mode 100644
index 00000000..0bb04dc1
--- /dev/null
+++ b/leptonica/prog/italic_reg.c
@@ -0,0 +1,116 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+
+/*
+ * italic_reg.c
+ *
+ * This demonstrates binary reconstruction for finding italic text.
+ * It also tests debug output of word masking.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char opstring[32];
+l_int32 size;
+BOXA *boxa1, *boxa2, *boxa3, *boxa4;
+PIX *pixs, *pixm, *pix1;
+PIXA *pixadb;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "italic_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/ital");
+ pixs = pixRead("italic.png");
+
+ /* Basic functionality with debug flag */
+ pixItalicWords(pixs, NULL, NULL, &boxa1, 1);
+ boxaWrite("/tmp/lept/ital/ital1.ba", boxa1);
+ regTestCheckFile(rp, "/tmp/lept/ital/ital1.ba"); /* 0 */
+ regTestCheckFile(rp, "/tmp/lept/ital/ital.pdf"); /* 1 */
+ pix1 = pixRead("/tmp/lept/ital/ital.png");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix1, 0, 0, "Intermediate steps", rp->display);
+ pixDestroy(&pix1);
+ pix1 = pixRead("/tmp/lept/ital/runhisto.png");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pix1, 400, 0, "Histogram of white runs", rp->display);
+ pixDestroy(&pix1);
+
+ /* Generate word mask */
+ pixadb = pixaCreate(5);
+ pixWordMaskByDilation(pixs, NULL, &size, pixadb);
+ l_pdfSetDateAndVersion(0);
+ pixaConvertToPdf(pixadb, 100, 1.0, L_FLATE_ENCODE, 0, "Word Mask",
+ "/tmp/lept/ital/wordmask.pdf");
+ regTestCheckFile(rp, "/tmp/lept/ital/wordmask.pdf"); /* 4 */
+ pix1 = pixaDisplayTiledInColumns(pixadb, 1, 1.0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix1, 1400, 0, "Intermediate mask step", rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixadb);
+ L_INFO("dilation size = %d\n", rp->testname, size);
+ snprintf(opstring, sizeof(opstring), "d1.5 + c%d.1", size);
+ pixm = pixMorphSequence(pixs, opstring, 0);
+ regTestWritePixAndCheck(rp, pixm, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pixm, 400, 550, "Word mask", rp->display);
+
+ /* Re-run italic finder using the word mask */
+ pixItalicWords(pixs, NULL, pixm, &boxa2, 1);
+ boxaWrite("/tmp/lept/ital/ital2.ba", boxa2);
+ regTestCheckFile(rp, "/tmp/lept/ital/ital2.ba"); /* 7 */
+
+ /* Re-run italic finder using word mask bounding boxes */
+ boxa3 = pixConnComp(pixm, NULL, 8);
+ pixItalicWords(pixs, boxa3, NULL, &boxa4, 1);
+ boxaWrite("/tmp/lept/ital/ital3.ba", boxa3);
+ regTestCheckFile(rp, "/tmp/lept/ital/ital3.ba"); /* 8 */
+ boxaWrite("/tmp/lept/ital/ital4.ba", boxa4);
+ regTestCheckFile(rp, "/tmp/lept/ital/ital4.ba"); /* 9 */
+ regTestCompareFiles(rp, 7, 9); /* 10 */
+
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+ boxaDestroy(&boxa4);
+ pixDestroy(&pixs);
+ pixDestroy(&pixm);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/jbclass_reg.c b/leptonica/prog/jbclass_reg.c
new file mode 100644
index 00000000..fd220d0c
--- /dev/null
+++ b/leptonica/prog/jbclass_reg.c
@@ -0,0 +1,211 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Regression test for
+ * jbCorrelation
+ * jbRankhaus
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Choose one of these */
+#define COMPONENTS JB_CONN_COMPS
+/* #define COMPONENTS JB_CHARACTERS */
+/* #define COMPONENTS JB_WORDS */
+
+static PIXA *PixaOutlineTemplates(PIXA *pixas, NUMA *na);
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, w, h;
+BOX *box;
+JBDATA *data;
+JBCLASSER *classer;
+NUMA *na;
+SARRAY *sa;
+PIX *pix1, *pix2;
+PIXA *pixa1, *pixa2;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/class");
+
+ /* Set up the input data */
+ pix1 = pixRead("pageseg1.tif");
+ pixGetDimensions(pix1, &w, &h, NULL);
+ box = boxCreate(0, 0, w, h / 2);
+ pix2 = pixClipRectangle(pix1, box, NULL);
+ pixWrite("/tmp/lept/class/pix1.tif", pix2, IFF_TIFF_G4);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pix1 = pixRead("pageseg4.tif");
+ pix2 = pixClipRectangle(pix1, box, NULL);
+ pixWrite("/tmp/lept/class/pix2.tif", pix2, IFF_TIFF_G4);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxDestroy(&box);
+ sa = sarrayCreate(2);
+ sarrayAddString(sa, "/tmp/lept/class/pix1.tif", L_COPY);
+ sarrayAddString(sa, "/tmp/lept/class/pix2.tif", L_COPY);
+
+ /*--------------------------------------------------------------*/
+
+ /* Run the correlation-based classifier */
+ classer = jbCorrelationInit(COMPONENTS, 0, 0, 0.8, 0.6);
+ jbAddPages(classer, sa);
+
+ /* Save and write out the result */
+ data = jbDataSave(classer);
+ jbDataWrite("/tmp/lept/class/corr", data);
+ lept_stderr("Number of classes: %d\n", classer->nclass);
+
+ pix1 = pixRead("/tmp/lept/class/corr.templates.png");
+ regTestWritePixAndCheck(rp, pix1, IFF_TIFF_G4); /* 0 */
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ /* Render the pages from the classifier data.
+ * Use debugflag == FALSE to omit outlines of each component. */
+ pixa1 = jbDataRender(data, FALSE);
+ for (i = 0; i < 2; i++) {
+ pix1 = pixaGetPix(pixa1, i, L_CLONE);
+ regTestWritePixAndCheck(rp, pix1, IFF_TIFF_G4); /* 1, 2 */
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa1);
+
+ /* Display all instances, organized by template */
+ pixa1 = pixaaFlattenToPixa(classer->pixaa, &na, L_CLONE);
+ pixa2 = PixaOutlineTemplates(pixa1, na);
+ pix1 = pixaDisplayTiledInColumns(pixa2, 40, 1.0, 10, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_TIFF_G4); /* 3 */
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ numaDestroy(&na);
+ jbClasserDestroy(&classer);
+ jbDataDestroy(&data);
+
+ /*--------------------------------------------------------------*/
+
+ lept_mkdir("lept/class2");
+
+ /* Run the rank hausdorff-based classifier */
+ classer = jbRankHausInit(COMPONENTS, 0, 0, 2, 0.97);
+ jbAddPages(classer, sa);
+
+ /* Save and write out the result */
+ data = jbDataSave(classer);
+ jbDataWrite("/tmp/lept/class2/haus", data);
+ lept_stderr("Number of classes: %d\n", classer->nclass);
+
+ pix1 = pixRead("/tmp/lept/class2/haus.templates.png");
+ regTestWritePixAndCheck(rp, pix1, IFF_TIFF_G4); /* 4 */
+ pixDisplayWithTitle(pix1, 200, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ /* Render the pages from the classifier data.
+ * Use debugflag == FALSE to omit outlines of each component. */
+ pixa1 = jbDataRender(data, FALSE);
+ for (i = 0; i < 2; i++) {
+ pix1 = pixaGetPix(pixa1, i, L_CLONE);
+ regTestWritePixAndCheck(rp, pix1, IFF_TIFF_G4); /* 5, 6 */
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa1);
+
+ /* Display all instances, organized by template */
+ pixa1 = pixaaFlattenToPixa(classer->pixaa, &na, L_CLONE);
+ pixa2 = PixaOutlineTemplates(pixa1, na);
+ pix1 = pixaDisplayTiledInColumns(pixa2, 40, 1.0, 10, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_TIFF_G4); /* 7 */
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ numaDestroy(&na);
+ jbClasserDestroy(&classer);
+ jbDataDestroy(&data);
+
+ /*--------------------------------------------------------------*/
+
+ sarrayDestroy(&sa);
+ return regTestCleanup(rp);
+}
+
+
+static PIXA *
+PixaOutlineTemplates(PIXA *pixas,
+ NUMA *na)
+{
+l_int32 i, n, val, prev, curr;
+NUMA *nai;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixad;
+
+ /* Make an indicator array with a 1 for each template image */
+ n = numaGetCount(na);
+ nai = numaCreate(n);
+ prev = -1;
+ for (i = 0; i < n; i++) {
+ numaGetIValue(na, i, &curr);
+ if (curr != prev) { /* index change */
+ prev = curr;
+ numaAddNumber(nai, 1);
+ } else {
+ numaAddNumber(nai, 0);
+ }
+ }
+
+ /* Add a boundary of 3 white and 1 black pixels to templates */
+ pixad = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ numaGetIValue(nai, i, &val);
+ if (val == 0) {
+ pixaAddPix(pixad, pix1, L_INSERT);
+ } else {
+ pix2 = pixAddBorder(pix1, 3, 0);
+ pix3 = pixAddBorder(pix2, 1, 1);
+ pixaAddPix(pixad, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ }
+
+ numaDestroy(&nai);
+ return pixad;
+}
diff --git a/leptonica/prog/jbcorrelation.c b/leptonica/prog/jbcorrelation.c
new file mode 100644
index 00000000..205d66f9
--- /dev/null
+++ b/leptonica/prog/jbcorrelation.c
@@ -0,0 +1,226 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * jbcorrelation.c
+ *
+ * jbcorrelation dirin thresh weight [firstpage npages]
+ *
+ * dirin: directory of input pages
+ * thresh: 0.80 - 0.85 is a reasonable compromise between accuracy
+ * and number of classes, for characters
+ * weight: 0.6 seems to work reasonably with thresh = 0.8.
+ *
+ * Notes:
+ * (1) All components larger than a default size are not saved.
+ * The default size is given in jbclass.c.
+ * (2) The two output files (for templates and c.c. data)
+ * are written with the rootname
+ * /tmp/lept/jb/result
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Choose one of these */
+#define COMPONENTS JB_CONN_COMPS
+/* #define COMPONENTS JB_CHARACTERS */
+/* #define COMPONENTS JB_WORDS */
+
+#define BUF_SIZE 512
+
+ /* Select additional debug output */
+#define DEBUG_TEST_DATA_IO 0
+#define RENDER_DEBUG 1
+#define DISPLAY_DIFFERENCE 1
+#define DISPLAY_ALL_INSTANCES 0
+
+static const char rootname[] = "/tmp/lept/jb/result";
+
+int main(int argc,
+ char **argv)
+{
+char filename[BUF_SIZE];
+char *dirin;
+l_int32 i, firstpage, npages, nfiles;
+l_float32 thresh, weight;
+JBDATA *data;
+JBCLASSER *classer;
+SARRAY *safiles;
+PIX *pix;
+PIXA *pixa, *pixadb;
+static char mainName[] = "jbcorrelation";
+
+ if (argc != 4 && argc != 6)
+ return ERROR_INT(
+ " Syntax: jbcorrelation dirin thresh weight [firstpage, npages]",
+ mainName, 1);
+ dirin = argv[1];
+ thresh = atof(argv[2]);
+ weight = atof(argv[3]);
+
+ if (argc == 4) {
+ firstpage = 0;
+ npages = 0;
+ }
+ else {
+ firstpage = atoi(argv[4]);
+ npages = atoi(argv[5]);
+ }
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/jb");
+
+#if 0
+
+ /*--------------------------------------------------------------*/
+
+ jbCorrelation(dirin, thresh, weight, COMPONENTS, rootname,
+ firstpage, npages, 1);
+
+ /*--------------------------------------------------------------*/
+
+#else
+
+ /*--------------------------------------------------------------*/
+
+ safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages);
+ nfiles = sarrayGetCount(safiles);
+
+/* sarrayWriteStream(stderr, safiles); */
+
+ /* Classify components on requested pages */
+ startTimer();
+ classer = jbCorrelationInit(COMPONENTS, 0, 0, thresh, weight);
+ jbAddPages(classer, safiles);
+ lept_stderr("Time to generate classes: %6.3f sec\n", stopTimer());
+
+ /* Save and write out the result */
+ data = jbDataSave(classer);
+ jbDataWrite(rootname, data);
+ lept_stderr("Number of classes: %d\n", classer->nclass);
+
+ /* Render the pages from the classifier data.
+ * Use debugflag == FALSE to omit outlines of each component. */
+ pixa = jbDataRender(data, FALSE);
+
+ /* Write the pages out */
+ npages = pixaGetCount(pixa);
+ if (npages != nfiles)
+ lept_stderr("npages = %d, nfiles = %d, not equal!\n", npages, nfiles);
+ for (i = 0; i < npages; i++) {
+ pix = pixaGetPix(pixa, i, L_CLONE);
+ snprintf(filename, BUF_SIZE, "%s.%03d", rootname, i);
+ lept_stderr("filename: %s\n", filename);
+ pixWrite(filename, pix, IFF_PNG);
+ pixDestroy(&pix);
+ }
+
+#if DISPLAY_DIFFERENCE
+ {
+ char *fname;
+ PIX *pix1, *pix2;
+ fname = sarrayGetString(safiles, 0, L_NOCOPY);
+ pix1 = pixRead(fname);
+ pix2 = pixaGetPix(pixa, 0, L_CLONE);
+ pixXor(pix1, pix1, pix2);
+ pixWrite("/tmp/lept/jb/output_diff.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+#endif /* DISPLAY_DIFFERENCE */
+
+#if DEBUG_TEST_DATA_IO
+ {
+ JBDATA *newdata;
+ PIX *newpix;
+ PIXA *newpixa;
+ l_int32 same, iofail;
+
+ /* Read the data back in and render the pages */
+ newdata = jbDataRead(rootname);
+ newpixa = jbDataRender(newdata, FALSE);
+ iofail = FALSE;
+ for (i = 0; i < npages; i++) {
+ pix = pixaGetPix(pixa, i, L_CLONE);
+ newpix = pixaGetPix(newpixa, i, L_CLONE);
+ pixEqual(pix, newpix, &same);
+ if (!same) {
+ iofail = TRUE;
+ lept_stderr("pix on page %d are unequal!\n", i);
+ }
+ pixDestroy(&pix);
+ pixDestroy(&newpix);
+
+ }
+ if (iofail)
+ lept_stderr("read/write for jbdata fails\n");
+ else
+ lept_stderr("read/write for jbdata succeeds\n");
+ jbDataDestroy(&newdata);
+ pixaDestroy(&newpixa);
+ }
+#endif /* DEBUG_TEST_DATA_IO */
+
+#if RENDER_DEBUG
+ /* Use debugflag == TRUE to see outlines of each component. */
+ pixadb = jbDataRender(data, TRUE);
+ /* Write the debug pages out */
+ npages = pixaGetCount(pixadb);
+ for (i = 0; i < npages; i++) {
+ pix = pixaGetPix(pixadb, i, L_CLONE);
+ snprintf(filename, BUF_SIZE, "%s.db.%04d", rootname, i);
+ lept_stderr("filename: %s\n", filename);
+ pixWrite(filename, pix, IFF_PNG);
+ pixDestroy(&pix);
+ }
+ pixaDestroy(&pixadb);
+#endif /* RENDER_DEBUG */
+
+#if DISPLAY_ALL_INSTANCES
+ /* Display all instances, organized by template.
+ * The display programs have a lot of trouble with these. */
+ pix = pixaaDisplayByPixa(classer->pixaa, 5, 1.0, 10, 0, 0);
+ pixWrite("/tmp/lept/jb/output_instances", pix, IFF_PNG);
+ pixDestroy(&pix);
+#endif /* DISPLAY_ALL_INSTANCES */
+
+ pixaDestroy(&pixa);
+ sarrayDestroy(&safiles);
+ jbClasserDestroy(&classer);
+ jbDataDestroy(&data);
+
+ /*--------------------------------------------------------------*/
+
+#endif
+
+ return 0;
+}
+
+
diff --git a/leptonica/prog/jbrankhaus.c b/leptonica/prog/jbrankhaus.c
new file mode 100644
index 00000000..d4eb54f5
--- /dev/null
+++ b/leptonica/prog/jbrankhaus.c
@@ -0,0 +1,224 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * jbrankhaus.c
+ *
+ * jbrankhaus dirin size rank rootname [firstpage npages]
+ *
+ * dirin: directory of input pages
+ * size: size of SE used for dilation
+ * rank: min pixel fraction required in both directions in match
+ *
+ * Notes:
+ * (1) All components larger than a default size are not saved.
+ * The default size is given in jbclass.c.
+ * (2) A set of reasonable values for cc or characters, that
+ * gives good accuracy without too manyclasses, is:
+ * size = 2 (2 x 2 structuring element)
+ * rank = 0.97
+ * (3) The two output files (for templates and c.c. data)
+ * are written with the rootname
+ * /tmp/lept/jb/result
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Choose one of these */
+#define COMPONENTS JB_CONN_COMPS
+/* #define COMPONENTS JB_CHARACTERS */
+/* #define COMPONENTS JB_WORDS */
+
+#define BUF_SIZE 512
+
+ /* select additional debug output */
+#define DEBUG_TEST_DATA_IO 0
+#define RENDER_DEBUG 1
+#define DISPLAY_DIFFERENCE 1
+#define DISPLAY_ALL_INSTANCES 0
+
+static const char rootname[] = "/tmp/lept/jb/result";
+
+int main(int argc,
+ char **argv)
+{
+char filename[BUF_SIZE];
+char *dirin, *fname;
+l_int32 i, size, firstpage, npages, nfiles;
+l_float32 rank;
+JBDATA *data;
+JBCLASSER *classer;
+SARRAY *safiles;
+PIX *pix, *pixt;
+PIXA *pixa, *pixadb;
+static char mainName[] = "jbrankhaus";
+
+ if (argc != 4 && argc != 6)
+ return ERROR_INT(
+ " Syntax: jbrankhaus dirin size rank [firstpage, npages]",
+ mainName, 1);
+ dirin = argv[1];
+ size = atoi(argv[2]);
+ rank = atof(argv[3]);
+ if (argc == 4) {
+ firstpage = 0;
+ npages = 0;
+ }
+ else {
+ firstpage = atoi(argv[4]);
+ npages = atoi(argv[5]);
+ }
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/jb");
+
+#if 0
+
+ /*--------------------------------------------------------------*/
+
+ jbRankHaus(dirin, size, rank, COMPONENTS, rootname, firstpage, npages, 1);
+
+ /*--------------------------------------------------------------*/
+
+#else
+
+ /*--------------------------------------------------------------*/
+
+ safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages);
+ nfiles = sarrayGetCount(safiles);
+
+/* sarrayWriteStream(stderr, safiles); */
+
+ /* Classify components on requested pages */
+ startTimer();
+ classer = jbRankHausInit(COMPONENTS, 0, 0, size, rank);
+ jbAddPages(classer, safiles);
+ lept_stderr("Time to classify components: %6.3f sec\n", stopTimer());
+
+ /* Save and write out the result */
+ data = jbDataSave(classer);
+ jbDataWrite(rootname, data);
+
+ /* Render the pages from the classifier data.
+ * Use debugflag == FALSE to omit outlines of each component. */
+ pixa = jbDataRender(data, FALSE);
+
+ /* Write the pages out */
+ npages = pixaGetCount(pixa);
+ if (npages != nfiles)
+ lept_stderr("npages = %d, nfiles = %d, not equal!\n", npages, nfiles);
+ for (i = 0; i < npages; i++) {
+ pix = pixaGetPix(pixa, i, L_CLONE);
+ snprintf(filename, BUF_SIZE, "%s.%03d", rootname, i);
+ lept_stderr("filename: %s\n", filename);
+ pixWrite(filename, pix, IFF_PNG);
+ pixDestroy(&pix);
+ }
+
+#if DISPLAY_DIFFERENCE
+ {
+ char *fname;
+ PIX *pix1, *pix2;
+ fname = sarrayGetString(safiles, 0, L_NOCOPY);
+ pix1 = pixRead(fname);
+ pix2 = pixaGetPix(pixa, 0, L_CLONE);
+ pixXor(pix1, pix1, pix2);
+ pixWrite("/tmp/lept/jb/output_diff.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+#endif /* DISPLAY_DIFFERENCE */
+
+#if DEBUG_TEST_DATA_IO
+ {
+ JBDATA *newdata;
+ PIX *newpix;
+ PIXA *newpixa;
+ l_int32 same, iofail;
+
+ /* Read the data back in and render the pages */
+ newdata = jbDataRead(rootname);
+ newpixa = jbDataRender(newdata, FALSE);
+ iofail = FALSE;
+ for (i = 0; i < npages; i++) {
+ pix = pixaGetPix(pixa, i, L_CLONE);
+ newpix = pixaGetPix(newpixa, i, L_CLONE);
+ pixEqual(pix, newpix, &same);
+ if (!same) {
+ iofail = TRUE;
+ lept_stderr("pix on page %d are unequal!\n", i);
+ }
+ pixDestroy(&pix);
+ pixDestroy(&newpix);
+
+ }
+ if (iofail)
+ lept_stderr("read/write for jbdata fails\n");
+ else
+ lept_stderr("read/write for jbdata succeeds\n");
+ jbDataDestroy(&newdata);
+ pixaDestroy(&newpixa);
+ }
+#endif /* DEBUG_TEST_DATA_IO */
+
+#if RENDER_DEBUG
+ /* Use debugflag == TRUE to see outlines of each component. */
+ pixadb = jbDataRender(data, TRUE);
+ /* Write the debug pages out */
+ npages = pixaGetCount(pixadb);
+ for (i = 0; i < npages; i++) {
+ pix = pixaGetPix(pixadb, i, L_CLONE);
+ snprintf(filename, BUF_SIZE, "%s.db.%04d", rootname, i);
+ lept_stderr("filename: %s\n", filename);
+ pixWrite(filename, pix, IFF_PNG);
+ pixDestroy(&pix);
+ }
+ pixaDestroy(&pixadb);
+#endif /* RENDER_DEBUG */
+
+#if DISPLAY_ALL_INSTANCES
+ /* Display all instances, organized by template
+ * The display programs have a lot of trouble with these. */
+ pix = pixaaDisplayByPixa(classer->pixaa, 5, 1.0, 10, 0, 0);
+ pixWrite("/tmp/lept/jb/output_instances", pix, IFF_PNG);
+ pixDestroy(&pix);
+#endif /* DISPLAY_ALL_INSTANCES */
+
+ pixaDestroy(&pixa);
+ sarrayDestroy(&safiles);
+ jbClasserDestroy(&classer);
+ jbDataDestroy(&data);
+
+ /*--------------------------------------------------------------*/
+
+#endif
+
+ return 0;
+}
diff --git a/leptonica/prog/jbwords.c b/leptonica/prog/jbwords.c
new file mode 100644
index 00000000..582cb415
--- /dev/null
+++ b/leptonica/prog/jbwords.c
@@ -0,0 +1,135 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * jbwords.c
+ *
+ * jbwords dirin thresh weight rootname [firstpage npages]
+ *
+ * dirin: directory of input pages
+ * reduction: 1 (full res) or 2 (half-res)
+ * thresh: 0.80 is a reasonable compromise between accuracy
+ * and number of classes, for characters
+ * weight: 0.6 seems to work reasonably with thresh = 0.8.
+ * rootname: used for naming the two output files (templates
+ * and c.c. data)
+ * firstpage: <optional> 0-based; default is 0
+ * npages: <optional> use 0 for all pages; default is 0
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Eliminate very large "words" */
+static const l_int32 MAX_WORD_WIDTH = 500;
+static const l_int32 MAX_WORD_HEIGHT = 200;
+
+#define BUF_SIZE 512
+
+ /* select additional debug output */
+#define RENDER_PAGES 1
+#define RENDER_DEBUG 1
+
+
+int main(int argc,
+ char **argv)
+{
+char filename[BUF_SIZE];
+char *dirin, *rootname;
+l_int32 reduction, i, firstpage, npages;
+l_float32 thresh, weight;
+JBDATA *data;
+JBCLASSER *classer;
+NUMA *natl;
+PIX *pix;
+PIXA *pixa, *pixadb;
+static char mainName[] = "jbwords";
+
+ if (argc != 6 && argc != 8)
+ return ERROR_INT(" Syntax: jbwords dirin reduction thresh "
+ "weight rootname [firstpage, npages]", mainName, 1);
+ dirin = argv[1];
+ reduction = atoi(argv[2]);
+ thresh = atof(argv[3]);
+ weight = atof(argv[4]);
+ rootname = argv[5];
+ if (argc == 6) {
+ firstpage = 0;
+ npages = 0;
+ } else {
+ firstpage = atoi(argv[6]);
+ npages = atoi(argv[7]);
+ }
+ setLeptDebugOK(1);
+
+ classer = jbWordsInTextlines(dirin, reduction, MAX_WORD_WIDTH,
+ MAX_WORD_HEIGHT, thresh, weight,
+ &natl, firstpage, npages);
+
+ /* Save and write out the result */
+ data = jbDataSave(classer);
+ jbDataWrite(rootname, data);
+
+#if RENDER_PAGES
+ /* Render the pages from the classifier data, and write to file.
+ * Use debugflag == FALSE to omit outlines of each component. */
+ pixa = jbDataRender(data, FALSE);
+ npages = pixaGetCount(pixa);
+ for (i = 0; i < npages; i++) {
+ pix = pixaGetPix(pixa, i, L_CLONE);
+ snprintf(filename, BUF_SIZE, "%s.%05d", rootname, i);
+ lept_stderr("filename: %s\n", filename);
+ pixWrite(filename, pix, IFF_PNG);
+ pixDestroy(&pix);
+ }
+ pixaDestroy(&pixa);
+#endif /* RENDER_PAGES */
+
+#if RENDER_DEBUG
+ /* Use debugflag == TRUE to see outlines of each component. */
+ pixadb = jbDataRender(data, TRUE);
+ /* Write the debug pages out */
+ npages = pixaGetCount(pixadb);
+ for (i = 0; i < npages; i++) {
+ pix = pixaGetPix(pixadb, i, L_CLONE);
+ snprintf(filename, BUF_SIZE, "%s.db.%05d", rootname, i);
+ lept_stderr("filename: %s\n", filename);
+ pixWrite(filename, pix, IFF_PNG);
+ pixDestroy(&pix);
+ }
+ pixaDestroy(&pixadb);
+#endif /* RENDER_DEBUG */
+
+ jbClasserDestroy(&classer);
+ jbDataDestroy(&data);
+ numaDestroy(&natl);
+ return 0;
+}
+
diff --git a/leptonica/prog/jp2kio_reg.c b/leptonica/prog/jp2kio_reg.c
new file mode 100644
index 00000000..678063ab
--- /dev/null
+++ b/leptonica/prog/jp2kio_reg.c
@@ -0,0 +1,184 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This is the Leptonica regression test for lossy read/write
+ * I/O in jp2k format.
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ *
+ * This tests reading and writing of images in jp2k format.
+ *
+ * * jp2k supports 8 bpp gray, rgb, and rgba.
+ * * This makes calls into the jpeg2000 library libopenjp2.
+ * * Compared to reading and writing jpeg, reading jp2k is
+ * very slow, and writing jp2k is miserably slow.
+ * * If we try to run this starting with image half the size,
+ * the library gives opj_start_compress() encoding errors!
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+ /* Needed for HAVE_LIBJP2K */
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+void DoJp2kTest1(L_REGPARAMS *rp, const char *fname);
+void DoJp2kTest2(L_REGPARAMS *rp, const char *fname);
+
+
+int main(int argc,
+ char **argv)
+{
+L_REGPARAMS *rp;
+
+#if !HAVE_LIBJP2K
+ lept_stderr("jp2kio is not enabled\n"
+ "libopenjp2 is required for jp2kio_reg\n"
+ "See environ.h: #define HAVE_LIBJP2K\n"
+ "See prog/Makefile: link in -lopenjp2\n\n");
+ return 0;
+#endif /* abort */
+
+ /* This test uses libjpeg */
+#if !HAVE_LIBJPEG
+ lept_stderr("libjpeg is required for jp2kio_reg\n\n");
+ return 0;
+#endif /* abort */
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ DoJp2kTest1(rp, "karen8.jpg");
+ DoJp2kTest1(rp, "test24.jpg");
+/* DoJp2kTest2(rp, "karen8.jpg"); */ /* encode fails on smallest image */
+ DoJp2kTest2(rp, "test24.jpg");
+ return regTestCleanup(rp);
+}
+
+
+void DoJp2kTest1(L_REGPARAMS *rp,
+ const char *fname)
+{
+char buf[64];
+char *name;
+l_int32 w, h;
+BOX *box;
+PIX *pix0, *pix1, *pix2, *pix3;
+
+ /* Read, write, read back and write again */
+ pix0 = pixRead(fname);
+ pix1 = pixScale(pix0, 0.5, 0.5);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ regTestWritePixAndCheck(rp, pix1, IFF_JP2);
+ name = regTestGenLocalFilename(rp, -1, IFF_JP2);
+ pix2 = pixRead(name);
+ regTestWritePixAndCheck(rp, pix2, IFF_JP2);
+ pixDisplayWithTitle(pix2, 0, 100, "1", rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Test cropping and scaling in the jp2 interface */
+ box = boxCreate(w / 4, h / 4, w / 2, h / 2);
+ pix1 = pixReadJp2k(name, 1, box, 0, 0); /* read cropped to the box */
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/jp2kio.%02d.jp2",
+ rp->index + 1);
+ pixWriteJp2k(buf, pix1, 38, 0, 0, 0); /* write cropped to the box */
+ regTestCheckFile(rp, buf);
+ pix2 = pixRead(buf); /* read the cropped image */
+ regTestWritePixAndCheck(rp, pix2, IFF_JP2);
+ pixDisplayWithTitle(pix2, 500, 100, "2", rp->display);
+ pix3 = pixReadJp2k(buf, 2, NULL, 0, 0); /* read cropped image at 2x red */
+ regTestWritePixAndCheck(rp, pix3, IFF_JP2);
+ pixDisplayWithTitle(pix3, 1000, 100, "3", rp->display);
+ pixDestroy(&pix0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ boxDestroy(&box);
+ lept_free(name);
+ return;
+}
+
+void DoJp2kTest2(L_REGPARAMS *rp,
+ const char *fname)
+{
+char buf[256];
+char *name;
+l_uint8 *data;
+l_int32 w, h;
+size_t nbytes;
+BOX *box;
+PIX *pix0, *pix1, *pix2, *pix3;
+
+ /* Test the memory interface */
+ pix0 = pixRead(fname);
+ pix1 = pixScale(pix0, 0.5, 0.5);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ regTestWritePixAndCheck(rp, pix1, IFF_JP2);
+ name = regTestGenLocalFilename(rp, -1, IFF_JP2);
+ pix2 = pixRead(name);
+ regTestWritePixAndCheck(rp, pix2, IFF_JP2);
+ data = l_binaryRead(name, &nbytes);
+ pix3 = pixReadMemJp2k(data, nbytes, 1, NULL, 0, 0);
+ regTestWritePixAndCheck(rp, pix3, IFF_JP2);
+ pixDisplayWithTitle(pix3, 0, 100, "1", rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ lept_free(data);
+
+ /* Test scaling on read with the memory interface */
+ box = boxCreate(w / 3, h / 3, w / 3, h / 3);
+ pix1 = pixReadJp2k(name, 1, box, 0, 0); /* just read the box region */
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/jp2kio.%02d.jp2",
+ rp->index + 1);
+ pixWriteJp2k(buf, pix1, 38, 0, 0, 0); /* write cropped to the box */
+ regTestCheckFile(rp, buf);
+ data = l_binaryRead(buf, &nbytes);
+ pix2 = pixReadMemJp2k(data, nbytes, 1, NULL, 0, 0); /* read it again */
+ regTestWritePixAndCheck(rp, pix2, IFF_JP2);
+ pixDisplayWithTitle(pix2, 500, 100, "2", rp->display);
+ pix3 = pixReadMemJp2k(data, nbytes, 2, NULL, 0, 0); /* read at 2x red */
+ regTestWritePixAndCheck(rp, pix3, IFF_JP2);
+ pixDisplayWithTitle(pix3, 1000, 100, "3", rp->display);
+ boxDestroy(&box);
+ pixDestroy(&pix0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ lept_free(data);
+ lept_free(name);
+ return;
+}
diff --git a/leptonica/prog/jpeg-coded.tif b/leptonica/prog/jpeg-coded.tif
new file mode 100644
index 00000000..d4bfb3f7
--- /dev/null
+++ b/leptonica/prog/jpeg-coded.tif
Binary files differ
diff --git a/leptonica/prog/jpegio_reg.c b/leptonica/prog/jpegio_reg.c
new file mode 100644
index 00000000..c9a03b69
--- /dev/null
+++ b/leptonica/prog/jpegio_reg.c
@@ -0,0 +1,265 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This is a Leptonica regression test for jpeg I/O
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ *
+ * This tests reading and writing of images and image
+ * metadata, between Pix and compressed data in jpeg format.
+ *
+ * This only tests properly written jpeg files. To test
+ * reading of corrupted jpeg files to insure that the
+ * reader does not crash, use prog/corrupttest.c.
+ *
+ * TODO (5/5/14): Add tests for
+ * (1) different color spaces
+ * (2) no chroma subsampling
+ * (3) luminance only reading
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Needed for HAVE_LIBJPEG */
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+void DoJpegTest1(L_REGPARAMS *rp, const char *fname);
+void DoJpegTest2(L_REGPARAMS *rp, const char *fname);
+void DoJpegTest3(L_REGPARAMS *rp, const char *fname);
+void DoJpegTest4(L_REGPARAMS *rp, const char *fname);
+
+
+int main(int argc,
+ char **argv)
+{
+L_REGPARAMS *rp;
+
+#if !HAVE_LIBJPEG
+ lept_stderr("jpegio is not enabled\n"
+ "See environ.h: #define HAVE_LIBJPEG\n"
+ "See prog/Makefile: link in -ljpeg\n\n");
+ return 0;
+#endif /* abort */
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ DoJpegTest1(rp, "test8.jpg");
+ DoJpegTest1(rp, "fish24.jpg");
+ DoJpegTest1(rp, "test24.jpg");
+ DoJpegTest2(rp, "weasel2.png");
+ DoJpegTest2(rp, "weasel2.4g.png");
+ DoJpegTest2(rp, "weasel4.png");
+ DoJpegTest2(rp, "weasel4.5g.png");
+ DoJpegTest2(rp, "weasel4.16c.png");
+ DoJpegTest2(rp, "weasel8.16g.png");
+ DoJpegTest2(rp, "weasel8.240c.png");
+ DoJpegTest3(rp, "lucasta.150.jpg");
+ DoJpegTest3(rp, "tetons.jpg");
+ DoJpegTest4(rp, "karen8.jpg");
+
+ return regTestCleanup(rp);
+}
+
+
+/* Use this for 8 bpp (no cmap), 24 bpp or 32 bpp pix */
+void DoJpegTest1(L_REGPARAMS *rp,
+ const char *fname)
+{
+size_t size;
+l_uint8 *data;
+char buf[256];
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5;
+
+ /* Test file read/write (general functions) */
+ pixs = pixRead(fname);
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/jpegio.%d.jpg", rp->index + 1);
+ pixWrite(buf, pixs, IFF_JFIF_JPEG);
+ pix1 = pixRead(buf);
+ regTestCompareSimilarPix(rp, pixs, pix1, 6, 0.01, 0);
+ pixDisplayWithTitle(pix1, 500, 100, "pix1", rp->display);
+
+ /* Test memory read/write (general functions) */
+ pixWriteMemJpeg(&data, &size, pixs, 75, 0);
+ pix2 = pixReadMem(data, size);
+ regTestComparePix(rp, pix1, pix2);
+ lept_free(data);
+
+ /* Test file read/write (specialized jpeg functions) */
+ pix3 = pixReadJpeg(fname, 0, 1, NULL, 0);
+ regTestComparePix(rp, pixs, pix3);
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/jpegio.%d.jpg", rp->index + 1);
+ pixWriteJpeg(buf, pix3, 75, 0);
+ pix4 = pixReadJpeg(buf, 0, 1, NULL, 0);
+ regTestComparePix(rp, pix2, pix4);
+
+ /* Test memory read/write (specialized jpeg functions) */
+ pixWriteMemJpeg(&data, &size, pixs, 75, 0);
+ pix5 = pixReadMemJpeg(data, size, 0, 1, NULL, 0);
+ regTestComparePix(rp, pix4, pix5);
+ lept_free(data);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ return;
+}
+
+/* Use this for colormapped pix and for pix with d < 8 */
+void DoJpegTest2(L_REGPARAMS *rp,
+ const char *fname)
+{
+size_t size;
+l_uint8 *data;
+char buf[256];
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+
+ /* Test file read/write (general functions) */
+ pixs = pixRead(fname);
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/jpegio.%d.jpg", rp->index + 1);
+ pixWrite(buf, pixs, IFF_JFIF_JPEG);
+ pix1 = pixRead(buf);
+ if (pixGetColormap(pixs) != NULL)
+ pix2 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+ else
+ pix2 = pixConvertTo8(pixs, 0);
+ regTestCompareSimilarPix(rp, pix1, pix2, 20, 0.2, 0);
+ pixDisplayWithTitle(pix1, 500, 100, "pix1", rp->display);
+
+ /* Test memory read/write (general functions) */
+ pixWriteMemJpeg(&data, &size, pixs, 75, 0);
+ pix3 = pixReadMem(data, size);
+ regTestComparePix(rp, pix1, pix3);
+ lept_free(data);
+
+ /* Test file write (specialized jpeg function) */
+ pix4 = pixRead(fname);
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/jpegio.%d.jpg", rp->index + 1);
+ pixWriteJpeg(buf, pix4, 75, 0);
+ pix5 = pixReadJpeg(buf, 0, 1, NULL, 0);
+ regTestComparePix(rp, pix5, pix5);
+
+ /* Test memory write (specialized jpeg function) */
+ pixWriteMemJpeg(&data, &size, pixs, 75, 0);
+ pix6 = pixReadMemJpeg(data, size, 0, 1, NULL, 0);
+ regTestComparePix(rp, pix5, pix6);
+ lept_free(data);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ return;
+}
+
+void DoJpegTest3(L_REGPARAMS *rp,
+ const char *fname)
+{
+l_int32 w1, h1, bps1, spp1, w2, h2, bps2, spp2, format1, format2;
+size_t size;
+l_uint8 *data;
+PIX *pixs;
+
+ /* Test header reading (specialized jpeg functions) */
+ readHeaderJpeg(fname, &w1, &h1, &spp1, NULL, NULL);
+ pixs = pixRead(fname);
+ pixWriteMemJpeg(&data, &size, pixs, 75, 0);
+ readHeaderMemJpeg(data, size, &w2, &h2, &spp2, NULL, NULL);
+ regTestCompareValues(rp, w1, w2, 0.0);
+ regTestCompareValues(rp, h1, h2, 0.0);
+ regTestCompareValues(rp, spp1, spp2, 0.0);
+ lept_free(data);
+
+ /* Test header reading (general jpeg functions) */
+ pixReadHeader(fname, &format1, &w1, &h1, &bps1, &spp1, NULL);
+ pixWriteMem(&data, &size, pixs, IFF_JFIF_JPEG);
+ pixReadHeaderMem(data, size, &format2, &w2, &h2, &bps2, &spp2, NULL);
+ regTestCompareValues(rp, format1, format2, 0.0);
+ regTestCompareValues(rp, w1, w2, 0.0);
+ regTestCompareValues(rp, h1, h2, 0.0);
+ regTestCompareValues(rp, bps1, bps2, 0.0);
+ regTestCompareValues(rp, bps1, 8, 0.0);
+ regTestCompareValues(rp, spp1, spp2, 0.0);
+ lept_stderr("w = %d, h = %d, bps = %d, spp = %d, format = %d\n",
+ w1, h1, bps1, spp1, format1);
+
+ pixDestroy(&pixs);
+ lept_free(data);
+ return;
+}
+
+void DoJpegTest4(L_REGPARAMS *rp,
+ const char *fname)
+{
+char buf[256];
+char comment1[256];
+l_uint8 *comment2;
+l_int32 xres, yres;
+FILE *fp;
+PIX *pixs;
+
+ /* Test special comment and resolution readers */
+ pixs = pixRead(fname);
+ snprintf(comment1, sizeof(comment1), "Test %d", rp->index + 1);
+ pixSetText(pixs, comment1);
+ pixSetResolution(pixs, 137, 137);
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/jpegio.%d.jpg", rp->index + 1);
+ pixWrite(buf, pixs, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, buf);
+ fp = lept_fopen(buf, "rb");
+ fgetJpegResolution(fp, &xres, &yres);
+ fgetJpegComment(fp, &comment2);
+ if (!comment2)
+ comment2 = (l_uint8 *)stringNew("");
+ lept_fclose(fp);
+ regTestCompareValues(rp, xres, 137, 0.0);
+ regTestCompareValues(rp, yres, 137, 0.0);
+ regTestCompareStrings(rp, (l_uint8 *)comment1, strlen(comment1),
+ comment2, strlen((char *)comment2));
+ lept_stderr("xres = %d, yres = %d, comment = %s\n", xres, yres, comment1);
+
+ lept_free(comment2);
+ pixDestroy(&pixs);
+ return;
+}
+
+
diff --git a/leptonica/prog/juditharismax.jpg b/leptonica/prog/juditharismax.jpg
new file mode 100644
index 00000000..f1a510d8
--- /dev/null
+++ b/leptonica/prog/juditharismax.jpg
Binary files differ
diff --git a/leptonica/prog/karen8.jpg b/leptonica/prog/karen8.jpg
new file mode 100644
index 00000000..e9627d5b
--- /dev/null
+++ b/leptonica/prog/karen8.jpg
Binary files differ
diff --git a/leptonica/prog/kernel_reg.c b/leptonica/prog/kernel_reg.c
new file mode 100644
index 00000000..997d0d7e
--- /dev/null
+++ b/leptonica/prog/kernel_reg.c
@@ -0,0 +1,356 @@
+/*====================================================================*
+ - 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_reg.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const char *kdatastr = " 20.3 50 80 50 20 "
+ " 51.4 100 140 100 50 "
+ " 92.5 160 200 160 90 "
+ " 53.7 100 140 100 50 "
+ " 24.9 50 80 50 20 ";
+
+int main(int argc,
+ char **argv)
+{
+char *str;
+l_int32 i, j, same, ok, plottype;
+l_float32 sum, avediff, rmsdiff;
+L_KERNEL *kel1, *kel2, *kel3, *kel4, *kelx, *kely;
+BOX *box;
+PIX *pix, *pixs, *pixb, *pixg, *pixd, *pixp, *pixt;
+PIX *pixt1, *pixt2, *pixt3;
+PIXA *pixa;
+PIXAA *paa;
+SARRAY *sa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "kernel_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ paa = pixaaCreate(0);
+
+ /* Test creating from a string */
+ pixa = pixaCreate(0);
+ kel1 = kernelCreateFromString(5, 5, 2, 2, kdatastr);
+ pixd = kernelDisplayInPix(kel1, 41, 2);
+ pixWrite("/tmp/lept/regout/pixkern.png", pixd, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/pixkern.png"); /* 0 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ kernelDestroy(&kel1);
+
+ /* Test read/write for kernel. Note that both get
+ * compared to the same golden file, which is
+ * overwritten with a copy of /tmp/lept/regout/kern2.kel */
+ kel1 = kernelCreateFromString(5, 5, 2, 2, kdatastr);
+ kernelWrite("/tmp/lept/regout/kern1.kel", kel1);
+ regTestCheckFile(rp, "/tmp/lept/regout/kern1.kel"); /* 1 */
+ kel2 = kernelRead("/tmp/lept/regout/kern1.kel");
+ kernelWrite("/tmp/lept/regout/kern2.kel", kel2);
+ regTestCheckFile(rp, "/tmp/lept/regout/kern2.kel"); /* 2 */
+ regTestCompareFiles(rp, 1, 2); /* 3 */
+ kernelDestroy(&kel1);
+ kernelDestroy(&kel2);
+
+ /* Test creating from a file */
+ pixa = pixaCreate(0);
+ sa = sarrayCreate(0);
+ sarrayAddString(sa, "# small 3x3 kernel", L_COPY);
+ sarrayAddString(sa, "3 5", L_COPY);
+ sarrayAddString(sa, "1 2", L_COPY);
+ sarrayAddString(sa, "20.5 50 80 50 20", L_COPY);
+ sarrayAddString(sa, "82. 120 180 120 80", L_COPY);
+ sarrayAddString(sa, "22.1 50 80 50 20", L_COPY);
+ str = sarrayToString(sa, 1);
+ l_binaryWrite("/tmp/lept/regout/kernfile.kel", "w", str, strlen(str));
+ kel2 = kernelCreateFromFile("/tmp/lept/regout/kernfile.kel");
+ pixd = kernelDisplayInPix(kel2, 41, 2);
+ pixWrite("/tmp/lept/regout/ker1.png", pixd, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/ker1.png"); /* 4 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ sarrayDestroy(&sa);
+ lept_free(str);
+ kernelDestroy(&kel2);
+
+ /* Test creating from a pix */
+ pixt = pixCreate(5, 3, 8);
+ pixSetPixel(pixt, 0, 0, 20);
+ pixSetPixel(pixt, 1, 0, 50);
+ pixSetPixel(pixt, 2, 0, 80);
+ pixSetPixel(pixt, 3, 0, 50);
+ pixSetPixel(pixt, 4, 0, 20);
+ pixSetPixel(pixt, 0, 1, 80);
+ pixSetPixel(pixt, 1, 1, 120);
+ pixSetPixel(pixt, 2, 1, 180);
+ pixSetPixel(pixt, 3, 1, 120);
+ pixSetPixel(pixt, 4, 1, 80);
+ pixSetPixel(pixt, 0, 0, 20);
+ pixSetPixel(pixt, 1, 2, 50);
+ pixSetPixel(pixt, 2, 2, 80);
+ pixSetPixel(pixt, 3, 2, 50);
+ pixSetPixel(pixt, 4, 2, 20);
+ kel3 = kernelCreateFromPix(pixt, 1, 2);
+ pixd = kernelDisplayInPix(kel3, 41, 2);
+ pixWrite("/tmp/lept/regout/ker2.png", pixd, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/ker2.png"); /* 5 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ pixDestroy(&pixt);
+ kernelDestroy(&kel3);
+
+ /* Test convolution with kel1 */
+ pixa = pixaCreate(0);
+ pixs = pixRead("test24.jpg");
+ pixg = pixScaleRGBToGrayFast(pixs, 3, COLOR_GREEN);
+ pixaAddPix(pixa, pixg, L_INSERT);
+ kel1 = kernelCreateFromString(5, 5, 2, 2, kdatastr);
+ pixd = pixConvolve(pixg, kel1, 8, 1);
+ pixWrite("/tmp/lept/regout/ker3.png", pixd, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/ker3.png"); /* 6 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ pixDestroy(&pixs);
+ kernelDestroy(&kel1);
+
+ /* Test convolution with flat rectangular kel; also test
+ * block convolution with tiling. */
+ pixa = pixaCreate(0);
+ pixs = pixRead("test24.jpg");
+ pixg = pixScaleRGBToGrayFast(pixs, 3, COLOR_GREEN);
+ kel2 = makeFlatKernel(11, 11, 5, 5);
+ pixd = pixConvolve(pixg, kel2, 8, 1);
+ pixaAddPix(pixa, pixd, L_COPY);
+ pixWrite("/tmp/lept/regout/ker4.png", pixd, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/ker4.png"); /* 7 */
+ pixt = pixBlockconv(pixg, 5, 5);
+ pixaAddPix(pixa, pixt, L_COPY);
+ pixWrite("/tmp/lept/regout/ker5.png", pixt, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/ker5.png"); /* 8 */
+ if (rp->display)
+ pixCompareGray(pixd, pixt, L_COMPARE_ABS_DIFF, GPLOT_PNG, NULL,
+ NULL, NULL, NULL);
+ pixt2 = pixBlockconvTiled(pixg, 5, 5, 3, 6);
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ pixWrite("/tmp/lept/regout/ker5a.png", pixt2, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/ker5a.png"); /* 9 */
+
+ ok = TRUE;
+ for (i = 1; i <= 7; i++) {
+ for (j = 1; j <= 7; j++) {
+ if (i == 1 && j == 1) continue;
+ pixt2 = pixBlockconvTiled(pixg, 5, 5, j, i);
+ pixEqual(pixt2, pixd, &same);
+ if (!same) {
+ lept_stderr("Error for nx = %d, ny = %d\n", j, i);
+ ok = FALSE;
+ }
+ pixDestroy(&pixt2);
+ }
+ }
+ if (ok)
+ lept_stderr("OK: Tiled results identical to pixConvolve()\n");
+ else
+ lept_stderr("ERROR: Tiled results not identical to pixConvolve()\n");
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixg);
+ pixDestroy(&pixd);
+ pixDestroy(&pixt);
+ kernelDestroy(&kel2);
+
+ /* Do another flat rectangular test; this time with white at edge.
+ * About 1% of the pixels near the image edge differ by 1 between
+ * the pixConvolve() and pixBlockconv(). For what it's worth,
+ * pixConvolve() gives the more accurate result; namely, 255 for
+ * pixels at the edge. */
+ pix = pixRead("pageseg1.tif");
+ box = boxCreate(100, 100, 2260, 3160);
+ pixb = pixClipRectangle(pix, box, NULL);
+ pixs = pixScaleToGray4(pixb);
+
+ pixa = pixaCreate(0);
+ kel3 = makeFlatKernel(7, 7, 3, 3);
+ startTimer();
+ pixt = pixConvolve(pixs, kel3, 8, 1);
+ lept_stderr("Generic convolution time: %5.3f sec\n", stopTimer());
+ pixaAddPix(pixa, pixt, L_INSERT);
+ pixWrite("/tmp/lept/regout/conv1.png", pixt, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/conv1.png"); /* 10 */
+
+ startTimer();
+ pixt2 = pixBlockconv(pixs, 3, 3);
+ lept_stderr("Flat block convolution time: %5.3f sec\n", stopTimer());
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixWrite("/tmp/lept/regout/conv2.png", pixt2, IFF_PNG); /* ditto */
+ regTestCheckFile(rp, "/tmp/lept/regout/conv2.png"); /* 11 */
+
+ plottype = (rp->display) ? GPLOT_PNG : 0;
+ pixCompareGray(pixt, pixt2, L_COMPARE_ABS_DIFF, plottype, NULL,
+ &avediff, &rmsdiff, NULL);
+ pixp = pixRead("/tmp/lept/comp/compare_gray0.png");
+ pixaAddPix(pixa, pixp, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ pixWrite("/tmp/lept/regout/conv3.png", pixp, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/conv3.png"); /* 12 */
+ lept_stderr("Ave diff = %6.4f, RMS diff = %6.4f\n", avediff, rmsdiff);
+ if (avediff <= 0.01)
+ lept_stderr("OK: avediff = %6.4f <= 0.01\n", avediff);
+ else
+ lept_stderr("Bad?: avediff = %6.4f > 0.01\n", avediff);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix);
+ pixDestroy(&pixb);
+ boxDestroy(&box);
+ kernelDestroy(&kel3);
+
+ /* Do yet another set of flat rectangular tests, this time
+ * on an RGB image */
+ pixs = pixRead("test24.jpg");
+ kel4 = makeFlatKernel(7, 7, 3, 3);
+ startTimer();
+ pixt1 = pixConvolveRGB(pixs, kel4);
+ lept_stderr("Time 7x7 non-separable: %7.3f sec\n", stopTimer());
+ pixWrite("/tmp/lept/regout/conv4.jpg", pixt1, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/regout/conv4.jpg"); /* 13 */
+
+ kelx = makeFlatKernel(1, 7, 0, 3);
+ kely = makeFlatKernel(7, 1, 3, 0);
+ startTimer();
+ pixt2 = pixConvolveRGBSep(pixs, kelx, kely);
+ lept_stderr("Time 7x1,1x7 separable: %7.3f sec\n", stopTimer());
+ pixWrite("/tmp/lept/regout/conv5.jpg", pixt2, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/regout/conv5.jpg"); /* 14 */
+
+ startTimer();
+ pixt3 = pixBlockconv(pixs, 3, 3);
+ lept_stderr("Time 7x7 blockconv: %7.3f sec\n", stopTimer());
+ pixWrite("/tmp/lept/regout/conv6.jpg", pixt3, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/regout/conv6.jpg"); /* 15 */
+ regTestComparePix(rp, pixt1, pixt2); /* 16 */
+ regTestCompareSimilarPix(rp, pixt2, pixt3, 15, 0.0005, 0); /* 17 */
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixDestroy(&pixt3);
+ kernelDestroy(&kel4);
+ kernelDestroy(&kelx);
+ kernelDestroy(&kely);
+
+ /* Test generation and convolution with gaussian kernel */
+ pixa = pixaCreate(0);
+ pixs = pixRead("test8.jpg");
+ pixaAddPix(pixa, pixs, L_COPY);
+ kel1 = makeGaussianKernel(5, 5, 3.0, 5.0);
+ kernelGetSum(kel1, &sum);
+ lept_stderr("Sum for gaussian kernel = %f\n", sum);
+ kernelWrite("/tmp/lept/regout/gauss.kel", kel1);
+ pixt = pixConvolve(pixs, kel1, 8, 1);
+ pixt2 = pixConvolve(pixs, kel1, 16, 0);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixWrite("/tmp/lept/regout/ker6.png", pixt, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/ker6.png"); /* 18 */
+
+ pixt = kernelDisplayInPix(kel1, 25, 2);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ kernelDestroy(&kel1);
+ pixDestroy(&pixs);
+
+ /* Test generation and convolution with separable gaussian kernel */
+ pixa = pixaCreate(0);
+ pixs = pixRead("test8.jpg");
+ pixaAddPix(pixa, pixs, L_INSERT);
+ makeGaussianKernelSep(5, 5, 3.0, 5.0, &kelx, &kely);
+ kernelGetSum(kelx, &sum);
+ lept_stderr("Sum for x gaussian kernel = %f\n", sum);
+ kernelGetSum(kely, &sum);
+ lept_stderr("Sum for y gaussian kernel = %f\n", sum);
+ kernelWrite("/tmp/lept/regout/gauss.kelx", kelx);
+ kernelWrite("/tmp/lept/regout/gauss.kely", kely);
+
+ pixt = pixConvolveSep(pixs, kelx, kely, 8, 1);
+ pixt2 = pixConvolveSep(pixs, kelx, kely, 16, 0);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixWrite("/tmp/lept/regout/ker7.png", pixt, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/ker7.png"); /* 19 */
+
+ pixt = kernelDisplayInPix(kelx, 25, 2);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ pixt = kernelDisplayInPix(kely, 25, 2);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ kernelDestroy(&kelx);
+ kernelDestroy(&kely);
+
+ /* Test generation and convolution with diff of gaussians kernel */
+/* pixt = pixRead("marge.jpg");
+ pixs = pixConvertRGBToLuminance(pixt);
+ pixDestroy(&pixt); */
+ pixa = pixaCreate(0);
+ pixs = pixRead("test8.jpg");
+ pixaAddPix(pixa, pixs, L_INSERT);
+ kel1 = makeDoGKernel(7, 7, 1.5, 2.7);
+ kernelGetSum(kel1, &sum);
+ lept_stderr("Sum for DoG kernel = %f\n", sum);
+ kernelWrite("/tmp/lept/regout/dog.kel", kel1);
+ pixt = pixConvolve(pixs, kel1, 8, 0);
+/* pixInvert(pixt, pixt); */
+ pixaAddPix(pixa, pixt, L_INSERT);
+ pixWrite("/tmp/lept/regout/ker8.png", pixt, IFF_PNG);
+ regTestCheckFile(rp, "/tmp/lept/regout/ker8.png"); /* 20 */
+
+ pixt = kernelDisplayInPix(kel1, 20, 2);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ kernelDestroy(&kel1);
+
+ pixd = pixaaDisplayByPixa(paa, 10, 1.0, 20, 20, 0);
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixWrite("/tmp/lept/regout/kernel.jpg", pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixd);
+ pixaaDestroy(&paa);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/keystone.png b/leptonica/prog/keystone.png
new file mode 100644
index 00000000..7b88eb6f
--- /dev/null
+++ b/leptonica/prog/keystone.png
Binary files differ
diff --git a/leptonica/prog/label_reg.c b/leptonica/prog/label_reg.c
new file mode 100644
index 00000000..ef3e522b
--- /dev/null
+++ b/leptonica/prog/label_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * label_reg.c
+ *
+ * Regression test for earthmover distance and these labelling operations:
+ * Connected component labelling
+ * Connected component area labelling
+ * Color coded transform of 1 bpp images
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void FindEMD(PIX *pix1, PIX *pix2, l_float32 *pdistr, l_float32 *pdistg,
+ l_float32 *pdistb);
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_float32 dist, distr, distg, distb;
+NUMA *na1, *na2;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Test earthmover distance: extreme example */
+ lept_stderr("Test earthmover distance\n");
+ na1 = numaMakeConstant(0, 201);
+ na2 = numaMakeConstant(0, 201);
+ numaSetValue(na1, 0, 100);
+ numaSetValue(na2, 200, 100);
+ numaEarthMoverDistance(na1, na2, &dist);
+ regTestCompareValues(rp, 200.0, dist, 0.0001); /* 0 */
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+
+ /* Test connected component labelling */
+ lept_stderr("Test c.c. labelling\n");
+ pix1 = pixRead("feyn-fract.tif");
+ pix2 = pixConnCompTransform(pix1, 8, 8);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix2, 0, 0, NULL, rp->display);
+ pix3 = pixConnCompTransform(pix1, 8, 16);
+ pix4 = pixConvert16To8(pix3, L_LS_BYTE);
+ regTestCompareSimilarPix(rp, pix2, pix4, 3, 0.001, 0); /* 2 */
+ pix5 = pixConnCompTransform(pix1, 8, 32);
+ pix6 = pixConvert32To8(pix5, L_LS_TWO_BYTES, L_LS_BYTE);
+ regTestCompareSimilarPix(rp, pix2, pix6, 3, 0.001, 0); /* 3 */
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+
+ /* Test connected component area labelling */
+ lept_stderr("Test c.c. area labelling\n");
+ pix2 = pixConnCompAreaTransform(pix1, 8);
+ pix3 = pixConvert32To8(pix2, L_LS_TWO_BYTES, L_CLIP_TO_FF);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix3, 0, 350, NULL, rp->display);
+ pixMultConstantGray(pix2, 0.3);
+ pix4 = pixConvert32To8(pix2, L_LS_TWO_BYTES, L_CLIP_TO_FF);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix4, 0, 700, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Test color transform: 4-fold symmetry */
+ lept_stderr("Test color transform: 4-fold symmetry\n");
+ pix1 = pixRead("form1.tif");
+ pix2 = pixRotateOrth(pix1, 1);
+ pix3 = pixRotateOrth(pix1, 2);
+ pix4 = pixRotateOrth(pix1, 3);
+ pix5 = pixLocToColorTransform(pix1);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 6 */
+ pix6 = pixLocToColorTransform(pix2);
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 7 */
+ FindEMD(pix5, pix6, &distr, &distg, &distb);
+ regTestCompareValues(rp, 0.12, distr, 0.01); /* 8 */
+ regTestCompareValues(rp, 0.00, distg, 0.01); /* 9 */
+ regTestCompareValues(rp, 0.00, distb, 0.01); /* 10 */
+ lept_stderr("90 deg rotation: dist (r,g,b) = (%5.2f, %5.2f, %5.2f)\n",
+ distr, distg, distb);
+ pixDestroy(&pix6);
+ pix6 = pixLocToColorTransform(pix3);
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 11 */
+ FindEMD(pix5, pix6, &distr, &distg, &distb);
+ regTestCompareValues(rp, 0.12, distr, 0.01); /* 12 */
+ regTestCompareValues(rp, 0.09, distg, 0.01); /* 13 */
+ regTestCompareValues(rp, 0.00, distb, 0.01); /* 14 */
+ lept_stderr("180 deg rotation: dist (r,g,b) = (%5.2f, %5.2f, %5.2f)\n",
+ distr, distg, distb);
+ pixDestroy(&pix6);
+ pix6 = pixLocToColorTransform(pix4);
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 15 */
+ FindEMD(pix5, pix6, &distr, &distg, &distb);
+ regTestCompareValues(rp, 0.00, distr, 0.01); /* 16 */
+ regTestCompareValues(rp, 0.09, distg, 0.01); /* 17 */
+ regTestCompareValues(rp, 0.00, distb, 0.01); /* 18 */
+ lept_stderr("270 deg rotation: dist (r,g,b) = (%5.2f, %5.2f, %5.2f)\n",
+ distr, distg, distb);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+
+ /* Test color transform: same form with translation */
+ lept_stderr("Test color transform with translation\n");
+ pix1 = pixRead("form1.tif");
+ pix2 = pixLocToColorTransform(pix1);
+ pixDisplayWithTitle(pix2, 0, 0, NULL, rp->display);
+ pixTranslate(pix1, pix1, 10, 10, L_BRING_IN_WHITE);
+ pix3 = pixLocToColorTransform(pix1);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 19 */
+ pixDisplayWithTitle(pix3, 470, 0, NULL, rp->display);
+ FindEMD(pix2, pix3, &distr, &distg, &distb);
+ regTestCompareValues(rp, 1.76, distr, 0.01); /* 20 */
+ regTestCompareValues(rp, 2.65, distg, 0.01); /* 21 */
+ regTestCompareValues(rp, 2.03, distb, 0.01); /* 22 */
+ lept_stderr("Translation dist (r,g,b) = (%5.2f, %5.2f, %5.2f)\n",
+ distr, distg, distb);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Test color transform: same form with small rotation */
+ lept_stderr("Test color transform with small rotation\n");
+ pix1 = pixRead("form1.tif");
+ pix2 = pixLocToColorTransform(pix1);
+ pixRotateShearCenterIP(pix1, 0.1, L_BRING_IN_WHITE);
+ pix3 = pixLocToColorTransform(pix1);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 23 */
+ pixDisplayWithTitle(pix3, 880, 0, NULL, rp->display);
+ FindEMD(pix2, pix3, &distr, &distg, &distb);
+ regTestCompareValues(rp, 1.50, distr, 0.01); /* 24 */
+ regTestCompareValues(rp, 1.71, distg, 0.01); /* 25 */
+ regTestCompareValues(rp, 1.42, distb, 0.01); /* 26 */
+ lept_stderr("Rotation dist (r,g,b) = (%5.2f, %5.2f, %5.2f)\n",
+ distr, distg, distb);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Test color transform: 2 different forms */
+ lept_stderr("Test color transform (2 forms)\n");
+ pix1 = pixRead("form1.tif");
+ pix2 = pixLocToColorTransform(pix1);
+ pixDisplayWithTitle(pix2, 0, 600, NULL, rp->display);
+ pix3 = pixRead("form2.tif");
+ pix4 = pixLocToColorTransform(pix3);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 27 */
+ pixDisplayWithTitle(pix4, 470, 600, NULL, rp->display);
+ FindEMD(pix2, pix4, &distr, &distg, &distb);
+ regTestCompareValues(rp, 6.10, distr, 0.02); /* 28 */
+ regTestCompareValues(rp, 11.13, distg, 0.01); /* 29 */
+ regTestCompareValues(rp, 10.53, distb, 0.01); /* 30 */
+ lept_stderr("Different forms: dist (r,g,b) = (%5.2f, %5.2f, %5.2f)\n",
+ distr, distg, distb);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ return regTestCleanup(rp);
+}
+
+
+void FindEMD(PIX *pix1, PIX *pix2,
+ l_float32 *pdistr, l_float32 *pdistg, l_float32 *pdistb)
+{
+NUMA *nar1, *nar2, *nag1, *nag2, *nab1, *nab2;
+
+ pixGetColorHistogram(pix1, 1, &nar1, &nag1, &nab1);
+ pixGetColorHistogram(pix2, 1, &nar2, &nag2, &nab2);
+ numaEarthMoverDistance(nar1, nar2, pdistr);
+ numaEarthMoverDistance(nag1, nag2, pdistg);
+ numaEarthMoverDistance(nab1, nab2, pdistb);
+ numaDestroy(&nar1);
+ numaDestroy(&nar2);
+ numaDestroy(&nag1);
+ numaDestroy(&nag2);
+ numaDestroy(&nab1);
+ numaDestroy(&nab2);
+ return;
+}
+
diff --git a/leptonica/prog/lapide.052.100.jpg b/leptonica/prog/lapide.052.100.jpg
new file mode 100644
index 00000000..ca3fbf17
--- /dev/null
+++ b/leptonica/prog/lapide.052.100.jpg
Binary files differ
diff --git a/leptonica/prog/leptonica-license.txt b/leptonica/prog/leptonica-license.txt
new file mode 100644
index 00000000..73d44c60
--- /dev/null
+++ b/leptonica/prog/leptonica-license.txt
@@ -0,0 +1,26 @@
+/*====================================================================*
+ - Copyright (C) 2001-2020 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/prog/lightcolortest.c b/leptonica/prog/lightcolortest.c
new file mode 100644
index 00000000..dee4adc7
--- /dev/null
+++ b/leptonica/prog/lightcolortest.c
@@ -0,0 +1,128 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * lightcolortest.c
+ *
+ * Determines if there are light colors on the image.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 nbins = 10;
+
+int main(int argc,
+ char **argv)
+{
+char *name, *tail;
+l_int32 i, j, n, minval, maxval, rdiff, gdiff, bdiff, maxdiff;
+l_uint32 *rau32, *gau32, *bau32, *carray, *darray;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa, *pixa1;
+SARRAY *sa;
+static char mainName[] = "lightcolortest";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: lightcolortest", mainName, 1);
+
+ setLeptDebugOK(1);
+ sa = getSortedPathnamesInDirectory( ".", "comap.", 0, 0);
+ sarrayWriteStream(stderr, sa);
+ n = sarrayGetCount(sa);
+ lept_stderr("n = %d\n", n);
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pixa1 = pixaCreate(2);
+ name = sarrayGetString(sa, i, L_NOCOPY);
+ splitPathAtDirectory(name, NULL, &tail);
+ pixs = pixRead(name);
+ lept_stderr("%s:\n", tail);
+ pix1 = pixScaleBySampling(pixs, 0.2, 0.2);
+
+ pixGetBinnedComponentRange(pix1, nbins, 2, L_SELECT_RED,
+ &minval, &maxval, &rau32, 0);
+ lept_stderr(" Red: max = %d, min = %d\n", maxval, minval);
+ rdiff = maxval - minval;
+ pixGetBinnedComponentRange(pix1, nbins, 2, L_SELECT_GREEN,
+ &minval, &maxval, &gau32, 0);
+ lept_stderr(" Green: max = %d, min = %d\n", maxval, minval);
+ gdiff = maxval - minval;
+ pixGetBinnedComponentRange(pix1, nbins, 2, L_SELECT_BLUE,
+ &minval, &maxval, &bau32, 0);
+ lept_stderr(" Blue: max = %d, min = %d\n", maxval, minval);
+ bdiff = maxval - minval;
+ lept_stderr("rdiff = %d, gdiff = %d, bdiff = %d\n\n",
+ rdiff, gdiff, bdiff);
+ maxdiff = L_MAX(rdiff, gdiff);
+ maxdiff = L_MAX(maxdiff, bdiff);
+ if (maxdiff == rdiff) {
+ carray = rau32;
+ lept_free(gau32);
+ lept_free(bau32);
+ } else if (maxdiff == gdiff) {
+ carray = gau32;
+ lept_free(bau32);
+ lept_free(rau32);
+ } else { /* maxdiff == bdiff */
+ carray = bau32;
+ lept_free(rau32);
+ lept_free(gau32);
+ }
+
+ pix2 = pixDisplayColorArray(carray, nbins, 200, 5, 6);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+
+ darray = (l_uint32 *)lept_calloc(nbins, sizeof(l_uint32));
+ for (j = 0; j < nbins; j++) {
+ pixelLinearMapToTargetColor(carray[j], carray[nbins - 1],
+ 0xffffff00, &darray[j]);
+ }
+ pix3 = pixDisplayColorArray(darray, nbins, 200, 5, 6);
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ pix4 = pixaDisplayLinearly(pixa1, L_VERT, 1.0, 0, 30, 3, NULL);
+ pixaAddPix(pixa, pix4, L_INSERT);
+
+ pixaDestroy(&pixa1);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ lept_free(tail);
+ lept_free(carray);
+ lept_free(darray);
+ }
+
+ lept_mkdir("lept/color");
+ pixaConvertToPdf(pixa, 100, 1.0, L_FLATE_ENCODE, 0, "lightcolortest",
+ "/tmp/lept/color/lightcolortest.pdf");
+ L_INFO("Generated pdf file: /tmp/lept/color/lightcolortest.pdf",
+ mainName);
+ pixaDestroy(&pixa);
+ sarrayDestroy(&sa);
+ return 0;
+}
diff --git a/leptonica/prog/lighttext.jpg b/leptonica/prog/lighttext.jpg
new file mode 100644
index 00000000..c5205275
--- /dev/null
+++ b/leptonica/prog/lighttext.jpg
Binary files differ
diff --git a/leptonica/prog/lineremoval_reg.c b/leptonica/prog/lineremoval_reg.c
new file mode 100644
index 00000000..422a24b3
--- /dev/null
+++ b/leptonica/prog/lineremoval_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * lineremoval_reg.c
+ *
+ * A fun little application, saved as a regression test.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_float32 angle, conf, deg2rad;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5;
+PIX *pix6, *pix7, *pix8, *pix9;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ deg2rad = 3.14159 / 180.;
+ pixs = pixRead("dave-orig.png");
+ pixa = pixaCreate(0);
+
+ /* Threshold to binary, extracting much of the lines */
+ pix1 = pixThresholdToBinary(pixs, 170);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Find the skew angle and deskew using an interpolated
+ * rotator for anti-aliasing (to avoid jaggies) */
+ pixFindSkew(pix1, &angle, &conf);
+ pix2 = pixRotateAMGray(pixs, deg2rad * angle, 255);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ /* Extract the lines to be removed */
+ pix3 = pixCloseGray(pix2, 51, 1);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 2 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+
+ /* Solidify the lines to be removed */
+ pix4 = pixErodeGray(pix3, 1, 5);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 3 */
+ pixaAddPix(pixa, pix4, L_INSERT);
+
+ /* Clean the background of those lines */
+ pix5 = pixThresholdToValue(NULL, pix4, 210, 255);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 4 */
+ pixaAddPix(pixa, pix5, L_INSERT);
+
+ pix6 = pixThresholdToValue(NULL, pix5, 200, 0);
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 5 */
+ pixaAddPix(pixa, pix6, L_COPY);
+
+ /* Get paint-through mask for changed pixels */
+ pix7 = pixThresholdToBinary(pix6, 210);
+ regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 6 */
+ pixaAddPix(pixa, pix7, L_INSERT);
+
+ /* Add the inverted, cleaned lines to orig. Because
+ * the background was cleaned, the inversion is 0,
+ * so when you add, it doesn't lighten those pixels.
+ * It only lightens (to white) the pixels in the lines! */
+ pixInvert(pix6, pix6);
+ pix8 = pixAddGray(NULL, pix2, pix6);
+ regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 7 */
+ pixaAddPix(pixa, pix8, L_COPY);
+
+ pix9 = pixOpenGray(pix8, 1, 9);
+ regTestWritePixAndCheck(rp, pix9, IFF_PNG); /* 8 */
+ pixaAddPix(pixa, pix9, L_INSERT);
+ pixCombineMasked(pix8, pix9, pix7);
+ regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 9 */
+ pixaAddPix(pixa, pix8, L_INSERT);
+
+ if (rp->display) {
+ lept_rmdir("lept/lines");
+ lept_mkdir("lept/lines");
+ lept_stderr("Writing to: /tmp/lept/lines/lineremoval.pdf\n");
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, "lineremoval example",
+ "/tmp/lept/lines/lineremoval.pdf");
+ pix1 = pixaDisplayTiledInColumns(pixa, 5, 0.5, 30, 2);
+ pixWrite("/tmp/lept/lines/lineremoval.jpg", pix1, IFF_JFIF_JPEG);
+ pixDisplay(pix1, 100, 100);
+ pixDestroy(&pix1);
+ }
+
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs);
+ pixDestroy(&pix6);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/lion-mask.00010.tif b/leptonica/prog/lion-mask.00010.tif
new file mode 100644
index 00000000..e66122a6
--- /dev/null
+++ b/leptonica/prog/lion-mask.00010.tif
Binary files differ
diff --git a/leptonica/prog/lion-mask.00016.tif b/leptonica/prog/lion-mask.00016.tif
new file mode 100644
index 00000000..d524a80c
--- /dev/null
+++ b/leptonica/prog/lion-mask.00016.tif
Binary files differ
diff --git a/leptonica/prog/lion-page.00010.jpg b/leptonica/prog/lion-page.00010.jpg
new file mode 100644
index 00000000..41113342
--- /dev/null
+++ b/leptonica/prog/lion-page.00010.jpg
Binary files differ
diff --git a/leptonica/prog/lion-page.00011.png b/leptonica/prog/lion-page.00011.png
new file mode 100644
index 00000000..f6252728
--- /dev/null
+++ b/leptonica/prog/lion-page.00011.png
Binary files differ
diff --git a/leptonica/prog/lion-page.00012.png b/leptonica/prog/lion-page.00012.png
new file mode 100644
index 00000000..28cf2deb
--- /dev/null
+++ b/leptonica/prog/lion-page.00012.png
Binary files differ
diff --git a/leptonica/prog/lion-page.00013.png b/leptonica/prog/lion-page.00013.png
new file mode 100644
index 00000000..b41b0a99
--- /dev/null
+++ b/leptonica/prog/lion-page.00013.png
Binary files differ
diff --git a/leptonica/prog/lion-page.00016.jpg b/leptonica/prog/lion-page.00016.jpg
new file mode 100644
index 00000000..25427dc3
--- /dev/null
+++ b/leptonica/prog/lion-page.00016.jpg
Binary files differ
diff --git a/leptonica/prog/lion-page.00017.png b/leptonica/prog/lion-page.00017.png
new file mode 100644
index 00000000..c7b1c0f3
--- /dev/null
+++ b/leptonica/prog/lion-page.00017.png
Binary files differ
diff --git a/leptonica/prog/listtest.c b/leptonica/prog/listtest.c
new file mode 100644
index 00000000..ecbb43df
--- /dev/null
+++ b/leptonica/prog/listtest.c
@@ -0,0 +1,279 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+
+/*
+ * listtest.c
+ *
+ * This file tests the main functions in the generic
+ * list facility, given in list.c and list.h.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein;
+l_int32 i, n, w, h, samecount, count;
+BOX *box, *boxc;
+BOXA *boxa, *boxan;
+DLLIST *head, *tail, *head2, *tail2, *elem, *nextelem;
+PIX *pixs;
+static char mainName[] = "listtest";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: listtest filein", mainName, 1);
+ filein = argv[1];
+ setLeptDebugOK(1);
+
+ boxa = boxan = NULL;
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pix not made", mainName, 1);
+
+ /* start with a boxa */
+ boxa = pixConnComp(pixs, NULL, 4);
+ n = boxaGetCount(boxa);
+
+ /*-------------------------------------------------------*
+ * Do one of these two ...
+ *-------------------------------------------------------*/
+ if (1) {
+ /* listAddToTail(): make a list by adding to tail */
+ head = NULL;
+ tail = NULL;
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxa, i, L_CLONE);
+ listAddToTail(&head, &tail, box);
+ }
+ } else {
+ /* listAddToHead(): make a list by adding to head */
+ head = NULL;
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxa, i, L_CLONE);
+ listAddToHead(&head, box);
+ }
+ }
+
+ /* list concatenation */
+ head2 = NULL; /* cons up 2nd list from null */
+ tail2 = NULL;
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxa, i, L_CLONE);
+ listAddToTail(&head2, &tail2, box);
+ }
+ listJoin(&head, &head2);
+
+ count = listGetCount(head);
+ lept_stderr("%d items in list\n", count);
+ listReverse(&head);
+ count = listGetCount(head);
+ lept_stderr("%d items in reversed list\n", count);
+ listReverse(&head);
+ count = listGetCount(head);
+ lept_stderr("%d items in doubly reversed list\n", count);
+
+ boxan = boxaCreate(n);
+
+ /*-------------------------------------------------------*
+ * Then do one of these ...
+ *-------------------------------------------------------*/
+ if (1) {
+ /* Removal of all elements and data from a list,
+ * without using L_BEGIN_LIST_FORWARD macro */
+ for (elem = head; elem; elem = nextelem) {
+ nextelem = elem->next;
+ box = (BOX *)elem->data;
+ boxaAddBox(boxan, box, L_INSERT);
+ elem->data = NULL;
+ listRemoveElement(&head, elem);
+ }
+ } else if (0) {
+ /* Removal of all elements and data from a list,
+ * using L_BEGIN_LIST_FORWARD macro */
+ L_BEGIN_LIST_FORWARD(head, elem)
+ box = (BOX *)elem->data;
+ boxaAddBox(boxan, box, L_INSERT);
+ elem->data = NULL;
+ listRemoveElement(&head, elem);
+ L_END_LIST
+ } else if (0) {
+ /* Removal of all elements and data from a list,
+ * using L_BEGIN_LIST_REVERSE macro */
+ tail = listFindTail(head);
+ L_BEGIN_LIST_REVERSE(tail, elem)
+ box = (BOX *)elem->data;
+ boxaAddBox(boxan, box, L_INSERT);
+ elem->data = NULL;
+ listRemoveElement(&head, elem);
+ L_END_LIST
+ } else if (0) {
+ /* boxa and boxan are same when list made with listAddToHead() */
+ tail = listFindTail(head);
+ L_BEGIN_LIST_REVERSE(tail, elem)
+ box = (BOX *)elem->data;
+ boxaAddBox(boxan, box, L_INSERT);
+ elem->data = NULL;
+ listRemoveElement(&head, elem);
+ L_END_LIST
+ for (i = 0, samecount = 0; i < n; i++) {
+ if (boxa->box[i]->w == boxan->box[i]->w &&
+ boxa->box[i]->h == boxan->box[i]->h)
+ samecount++;
+ }
+ lept_stderr(" num boxes = %d, same count = %d\n",
+ boxaGetCount(boxa), samecount);
+ } else if (0) {
+ /* boxa and boxan are same when list made with listAddToTail() */
+ L_BEGIN_LIST_FORWARD(head, elem)
+ box = (BOX *)elem->data;
+ boxaAddBox(boxan, box, L_INSERT);
+ elem->data = NULL;
+ listRemoveElement(&head, elem);
+ L_END_LIST
+ for (i = 0, samecount = 0; i < n; i++) {
+ if (boxa->box[i]->w == boxan->box[i]->w &&
+ boxa->box[i]->h == boxan->box[i]->h)
+ samecount++;
+ }
+ lept_stderr(" num boxes = %d, same count = %d\n",
+ boxaGetCount(boxa), samecount);
+ } else if (0) {
+ /* Destroy the boxes and then the list */
+ L_BEGIN_LIST_FORWARD(head, elem)
+ box = (BOX *)elem->data;
+ boxDestroy(&box);
+ elem->data = NULL;
+ L_END_LIST
+ listDestroy(&head);
+ } else if (0) {
+ /* listInsertBefore(): inserting a copy BEFORE each element */
+ L_BEGIN_LIST_FORWARD(head, elem)
+ box = (BOX *)elem->data;
+ boxc = boxCopy(box);
+ listInsertBefore(&head, elem, boxc);
+ L_END_LIST
+ L_BEGIN_LIST_FORWARD(head, elem)
+ box = (BOX *)elem->data;
+ boxaAddBox(boxan, box, L_INSERT);
+ elem->data = NULL;
+ L_END_LIST
+ listDestroy(&head);
+ } else if (0) {
+ /* listInsertAfter(): inserting a copy AFTER that element */
+ L_BEGIN_LIST_FORWARD(head, elem)
+ box = (BOX *)elem->data;
+ boxc = boxCopy(box);
+ listInsertAfter(&head, elem, boxc);
+ L_END_LIST
+ L_BEGIN_LIST_FORWARD(head, elem)
+ box = (BOX *)elem->data;
+ boxaAddBox(boxan, box, L_INSERT);
+ elem->data = NULL;
+ listRemoveElement(&head, elem);
+ L_END_LIST
+/* listDestroy(&head); */
+ } else if (0) {
+ /* Test listRemoveFromHead(), to successively
+ * remove the head of the list for all elements. */
+ count = 0;
+ while (head) {
+ box = (BOX *)listRemoveFromHead(&head);
+ boxDestroy(&box);
+ count++;
+ }
+ lept_stderr("removed %d items\n", count);
+ } else if (0) {
+ /* Another version to test listRemoveFromHead(), using
+ * an iterator macro. */
+ count = 0;
+ L_BEGIN_LIST_FORWARD(head, elem)
+ box = (BOX *)listRemoveFromHead(&head);
+ boxDestroy(&box);
+ count++;
+ L_END_LIST
+ lept_stderr("removed %d items\n", count);
+ } else if (0) {
+ /* test listRemoveFromTail(), to successively remove
+ * the tail of the list for all elements. */
+ count = 0;
+ tail = NULL; /* will find tail automatically */
+ while (head) {
+ box = (BOX *)listRemoveFromTail(&head, &tail);
+ boxDestroy(&box);
+ count++;
+ }
+ lept_stderr("removed %d items\n", count);
+ } else if (0) {
+ /* another version to test listRemoveFromTail(), using
+ * an iterator macro. */
+ count = 0;
+ tail = listFindTail(head); /* need to initialize tail */
+ L_BEGIN_LIST_REVERSE(tail, elem)
+ box = (BOX *)listRemoveFromTail(&head, &tail);
+ boxDestroy(&box);
+ count++;
+ L_END_LIST
+ lept_stderr("removed %d items\n", count);
+ } else if (0) {
+ /* Iterate backwards over the box array, and use
+ * listFindElement() to find each corresponding data structure
+ * within the list; then remove it. Should completely
+ * destroy the list. Note that listFindElement()
+ * returns the cell without removing it from the list! */
+ n = boxaGetCount(boxa);
+ for (i = 0, count = 0; i < n; i++) {
+ box = boxaGetBox(boxa, n - i - 1, L_CLONE);
+ if (i % 1709 == 0) boxPrintStreamInfo(stderr, box);
+ elem = listFindElement(head, box);
+ boxDestroy(&box);
+ if (elem) { /* found */
+ box = (BOX *)listRemoveElement(&head, elem);
+ if (i % 1709 == 0) boxPrintStreamInfo(stderr, box);
+ boxDestroy(&box);
+ count++;
+ }
+ }
+ lept_stderr("removed %d items\n", count);
+ }
+
+ lept_stderr("boxa count = %d; boxan count = %d\n",
+ boxaGetCount(boxa), boxaGetCount(boxan));
+ boxaGetExtent(boxa, &w, &h, NULL);
+ lept_stderr("boxa extent = (%d, %d)\n", w, h);
+ boxaGetExtent(boxan, &w, &h, NULL);
+ lept_stderr("boxan extent = (%d, %d)\n", w, h);
+
+ pixDestroy(&pixs);
+ boxaDestroy(&boxa);
+ boxaDestroy(&boxan);
+ return 0;
+}
+
diff --git a/leptonica/prog/livre_adapt.c b/leptonica/prog/livre_adapt.c
new file mode 100644
index 00000000..f897a317
--- /dev/null
+++ b/leptonica/prog/livre_adapt.c
@@ -0,0 +1,105 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * livre_adapt.c
+ *
+ * This shows two ways to normalize a document image for uneven
+ * illumination. It is somewhat more complicated than using the
+ * morphological tophat.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pix1, *pix2, *pix3, *pixr, *pixg, *pixb, *pixsg, *pixsm;
+PIXA *pixa;
+static char mainName[] = "livre_adapt";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: livre_adapt", mainName, 1);
+ setLeptDebugOK(1);
+
+ /* Read the image in at 150 ppi. */
+ if ((pixs = pixRead("brothers.150.jpg")) == NULL)
+ return ERROR_INT("pix not made", mainName, 1);
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixs, L_INSERT);
+
+ /* Normalize for uneven illumination on RGB image */
+ pixBackgroundNormRGBArraysMorph(pixs, NULL, 4, 5, 200,
+ &pixr, &pixg, &pixb);
+ pix1 = pixApplyInvBackgroundRGBMap(pixs, pixr, pixg, pixb, 4, 4);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pixr);
+ pixDestroy(&pixg);
+ pixDestroy(&pixb);
+
+ /* Convert the RGB image to grayscale. */
+ pixsg = pixConvertRGBToLuminance(pixs);
+ pixaAddPix(pixa, pixsg, L_INSERT);
+
+ /* Remove the text in the fg. */
+ pix1 = pixCloseGray(pixsg, 25, 25);
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Smooth the bg with a convolution. */
+ pixsm = pixBlockconv(pix1, 15, 15);
+ pixaAddPix(pixa, pixsm, L_INSERT);
+
+ /* Normalize for uneven illumination on gray image. */
+ pixBackgroundNormGrayArrayMorph(pixsg, NULL, 4, 5, 200, &pixg);
+ pix1 = pixApplyInvBackgroundGrayMap(pixsg, pixg, 4, 4);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pixg);
+
+ /* Increase the dynamic range. */
+ pix2 = pixGammaTRC(NULL, pix1, 1.0, 30, 180);
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ /* Threshold to 1 bpp. */
+ pix3 = pixThresholdToBinary(pix2, 120);
+ pixaAddPix(pixa, pix3, L_INSERT);
+
+ /* Generate the output image and pdf */
+ lept_mkdir("lept/livre");
+ lept_stderr("Writing jpg and pdf to: /tmp/lept/livre/adapt.*\n");
+ pix1 = pixaDisplayTiledAndScaled(pixa, 8, 350, 4, 0, 25, 2);
+ pixWrite("/tmp/lept/livre/adapt.jpg", pix1, IFF_DEFAULT);
+ pixDisplay(pix1, 100, 100);
+ pixaConvertToPdf(pixa, 0, 1.0, 0, 0, "Livre: adaptive thresholding",
+ "/tmp/lept/livre/adapt.pdf");
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+ return 0;
+}
+
diff --git a/leptonica/prog/livre_hmt.c b/leptonica/prog/livre_hmt.c
new file mode 100644
index 00000000..835d2078
--- /dev/null
+++ b/leptonica/prog/livre_hmt.c
@@ -0,0 +1,151 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * livre_hmt.c
+ *
+ * This demonstrates use of pixGenerateSelBoundary() to
+ * generate a hit-miss Sel.
+ *
+ * (1) The Sel is displayed with the hit and miss elements in color.
+ *
+ * (2) We produce several 4 bpp colormapped renditions,
+ * with the matched pattern either highlighted or removed.
+ *
+ * (3) For figures in the Document Image Applications chapter:
+ * fig 7: livre_hmt 1 8
+ * fig 8: livre_hmt 2 4
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* for pixDisplayHitMissSel() */
+static const l_uint32 HitColor = 0x33aa4400;
+static const l_uint32 MissColor = 0xaa44bb00;
+
+ /* Patterns at full resolution */
+static const char *patname[3] = {
+ "",
+ "tribune-word.png", /* patno = 1 */
+ "tribune-t.png"}; /* patno = 2 */
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 patno, reduction, width, cols, cx, cy;
+PIX *pixs, *pixt, *pix, *pixr, *pixp, *pixsel, *pixhmt;
+PIX *pixd1, *pixd2, *pixd3, *pixd;
+PIXA *pixa;
+SEL *selhm;
+static char mainName[] = "livre_hmt";
+
+ if (argc != 3)
+ return ERROR_INT(" Syntax: livre_hmt pattern reduction", mainName, 1);
+ patno = atoi(argv[1]);
+ reduction = atoi(argv[2]);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/livre");
+ if ((pixs = pixRead(patname[patno])) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+ if (reduction != 4 && reduction != 8 && reduction != 16)
+ return ERROR_INT("reduction not 4, 8 or 16", mainName, 1);
+
+ if (reduction == 4)
+ pixt = pixReduceRankBinaryCascade(pixs, 4, 4, 0, 0);
+ else if (reduction == 8)
+ pixt = pixReduceRankBinaryCascade(pixs, 4, 4, 2, 0);
+ else /* reduction == 16 */
+ pixt = pixReduceRankBinaryCascade(pixs, 4, 4, 2, 2);
+
+ /* Make a hit-miss sel */
+ if (reduction == 4)
+ selhm = pixGenerateSelBoundary(pixt, 2, 2, 20, 30, 1, 1, 0, 0, &pixp);
+ else if (reduction == 8)
+ selhm = pixGenerateSelBoundary(pixt, 1, 2, 6, 12, 1, 1, 0, 0, &pixp);
+ else /* reduction == 16 */
+ selhm = pixGenerateSelBoundary(pixt, 1, 1, 4, 8, 0, 0, 0, 0, &pixp);
+
+ /* Display the sel */
+ pixsel = pixDisplayHitMissSel(pixp, selhm, 7, HitColor, MissColor);
+ pixDisplay(pixsel, 1000, 300);
+ pixWrite("/tmp/lept/livre/pixsel1", pixsel, IFF_PNG);
+
+ /* Use the Sel to find all instances in the page */
+ pix = pixRead("tribune-page-4x.png"); /* 4x reduced */
+ if (reduction == 4)
+ pixr = pixClone(pix);
+ else if (reduction == 8)
+ pixr = pixReduceRankBinaryCascade(pix, 2, 0, 0, 0);
+ else /* reduction == 16 */
+ pixr = pixReduceRankBinaryCascade(pix, 2, 2, 0, 0);
+
+ startTimer();
+ pixhmt = pixHMT(NULL, pixr, selhm);
+ lept_stderr("Time to find patterns = %7.3f\n", stopTimer());
+
+ /* Color each instance at full res */
+ selGetParameters(selhm, NULL, NULL, &cy, &cx);
+ pixd1 = pixDisplayMatchedPattern(pixr, pixp, pixhmt,
+ cx, cy, 0x0000ff00, 1.0, 5);
+ pixWrite("/tmp/lept/livre/pixd11", pixd1, IFF_PNG);
+
+ /* Color each instance at 0.5 scale */
+ pixd2 = pixDisplayMatchedPattern(pixr, pixp, pixhmt,
+ cx, cy, 0x0000ff00, 0.5, 5);
+ pixWrite("/tmp/lept/livre/pixd12", pixd2, IFF_PNG);
+
+ /* Remove each instance from the input image */
+ pixd3 = pixCopy(NULL, pixr);
+ pixRemoveMatchedPattern(pixd3, pixp, pixhmt, cx, cy, 1);
+ pixWrite("/tmp/lept/livre/pixr1", pixd3, IFF_PNG);
+
+ pixa = pixaCreate(2);
+ pixaAddPix(pixa, pixs, L_CLONE);
+ pixaAddPix(pixa, pixsel, L_CLONE);
+ cols = (patno == 1) ? 1 : 2;
+ width = (patno == 1) ? 800 : 400;
+ pixd = pixaDisplayTiledAndScaled(pixa, 32, width, cols, 0, 30, 2);
+ pixWrite("/tmp/lept/livre/hmt.png", pixd, IFF_PNG);
+ pixDisplay(pixd, 1000, 600);
+
+ selDestroy(&selhm);
+ pixDestroy(&pixp);
+ pixDestroy(&pixsel);
+ pixDestroy(&pixhmt);
+ pixDestroy(&pixd1);
+ pixDestroy(&pixd2);
+ pixDestroy(&pixd3);
+ pixDestroy(&pixs);
+ pixDestroy(&pix);
+ pixDestroy(&pixt);
+ return 0;
+}
diff --git a/leptonica/prog/livre_makefigs.c b/leptonica/prog/livre_makefigs.c
new file mode 100644
index 00000000..8f7ed8df
--- /dev/null
+++ b/leptonica/prog/livre_makefigs.c
@@ -0,0 +1,107 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * livre_makefigs.c
+ *
+ * This makes all the figures in Chapter 18, "Document Image Applications",
+ * of the book "Mathematical morphology: from theory to applications",
+ * edited by Laurent Najman and hugues Talbot. Published by Hermes
+ * Scientific Publishing, Ltd, 2010.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char buf[256];
+l_int32 ignore;
+static char mainName[] = "livre_makefigs";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: livre_makefigs", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/livre");
+
+ /* Generate Figure 1 (page segmentation) */
+ ignore = system("livre_seedgen");
+ snprintf(buf, sizeof(buf),
+ "cp /tmp/lept/livre/seedgen.png /tmp/lept/livre/dia_fig1.png");
+ ignore = system(buf);
+
+ /* Generate Figures 2-5 (page segmentation) */
+ snprintf(buf, sizeof(buf), "livre_pageseg pageseg2.tif");
+ ignore = system(buf);
+ snprintf(buf, sizeof(buf),
+ "cp /tmp/lept/livre/segout.1.png /tmp/lept/livre/dia_fig2.png");
+ ignore = system(buf);
+ snprintf(buf, sizeof(buf),
+ "cp /tmp/lept/livre/segout.2.png /tmp/lept/livre/dia_fig3.png");
+ ignore = system(buf);
+ snprintf(buf, sizeof(buf),
+ "cp /tmp/lept/livre/segout.3.png /tmp/lept/livre/dia_fig4.png");
+ ignore = system(buf);
+ snprintf(buf, sizeof(buf),
+ "cp /tmp/lept/livre/segout.4.png /tmp/lept/livre/dia_fig5.png");
+ ignore = system(buf);
+
+ /* Generate Figure 6 (hmt sels for text orientation) */
+ ignore = system("livre_orient");
+ snprintf(buf, sizeof(buf),
+ "cp /tmp/lept/livre/orient.png /tmp/lept/livre/dia_fig6.png");
+ ignore = system(buf);
+
+ /* Generate Figure 7 (hmt sel for fancy "Tribune") */
+ ignore = system("livre_hmt 1 8");
+ snprintf(buf, sizeof(buf),
+ "cp /tmp/lept/livre/hmt.png /tmp/lept/livre/dia_fig7.png");
+ ignore = system(buf);
+
+ /* Generate Figure 8 (hmt sel for fancy "T") */
+ ignore = system("livre_hmt 2 4");
+ snprintf(buf, sizeof(buf),
+ "cp /tmp/lept/livre/hmt.png /tmp/lept/livre/dia_fig8.png");
+ ignore = system(buf);
+
+ /* Generate Figure 9 (tophat background cleaning) */
+ ignore = system("livre_tophat");
+ snprintf(buf, sizeof(buf),
+ "cp /tmp/lept/livre/tophat.jpg /tmp/lept/livre/dia_fig9.jpg");
+ ignore = system(buf);
+
+ /* Run livre_adapt to generate an expanded version of Figure 9 */
+ ignore = system("livre_adapt");
+
+
+ return 0;
+}
+
diff --git a/leptonica/prog/livre_orient.c b/leptonica/prog/livre_orient.c
new file mode 100644
index 00000000..c7ba784c
--- /dev/null
+++ b/leptonica/prog/livre_orient.c
@@ -0,0 +1,94 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * livre_orient.c
+ *
+ * This generates an image of the set of 4 HMT Sels that are
+ * used for counting ascenders and descenders to detect
+ * text orientation.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+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";
+
+int main(int argc,
+ char **argv)
+{
+PIX *pix1;
+SEL *sel1, *sel2, *sel3, *sel4;
+SELA *sela;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/livre");
+
+ sel1 = selCreateFromString(textsel1, 5, 6, NULL);
+ sel2 = selCreateFromString(textsel2, 5, 6, NULL);
+ sel3 = selCreateFromString(textsel3, 5, 6, NULL);
+ sel4 = selCreateFromString(textsel4, 5, 6, NULL);
+
+ sela = selaCreate(4);
+ selaAddSel(sela, sel1, "textsel1", L_INSERT);
+ selaAddSel(sela, sel2, "textsel2", L_INSERT);
+ selaAddSel(sela, sel3, "textsel3", L_INSERT);
+ selaAddSel(sela, sel4, "textsel4", L_INSERT);
+
+ pix1 = selaDisplayInPix(sela, 28, 3, 30, 4);
+ pixWrite("/tmp/lept/livre/orient.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 1200, 1200);
+
+ pixDestroy(&pix1);
+ selaDestroy(&sela);
+ return 0;
+}
+
diff --git a/leptonica/prog/livre_pageseg.c b/leptonica/prog/livre_pageseg.c
new file mode 100644
index 00000000..40d96fec
--- /dev/null
+++ b/leptonica/prog/livre_pageseg.c
@@ -0,0 +1,311 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * livre_pageseg.c
+ *
+ * This gives examples of the use of binary morphology for
+ * some simple and fast document segmentation operations.
+ *
+ * The operations are carried out at 2x reduction.
+ * For images scanned at 300 ppi, this is typically
+ * high enough resolution for accurate results.
+ *
+ * This generates several of the figures used in Chapter 18 of
+ * "Mathematical morphology: from theory to applications",
+ * edited by Laurent Najman and Hugues Talbot. Published by
+ * Hermes Scientific Publishing, Ltd, 2010.
+ *
+ * Use pageseg*.tif input images.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Control the display output */
+#define DFLAG 0
+
+
+l_int32 DoPageSegmentation(PIX *pixs, l_int32 which);
+
+int main(int argc,
+ char **argv)
+{
+char *filein;
+l_int32 i;
+PIX *pixs; /* input image should be at least 300 ppi */
+static char mainName[] = "livre_pageseg";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: livre_pageseg filein", mainName, 1);
+ filein = argv[1];
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pix not made", mainName, 1);
+
+ for (i = 1; i <= 4; i++)
+ DoPageSegmentation(pixs, i);
+ pixDestroy(&pixs);
+ return 0;
+}
+
+
+l_int32
+DoPageSegmentation(PIX *pixs, /* should be at least 300 ppi */
+ l_int32 which) /* 1, 2, 3, 4 */
+{
+char buf[256];
+l_int32 zero;
+BOXA *boxatm, *boxahm;
+PIX *pixr; /* image reduced to 150 ppi */
+PIX *pixhs; /* image of halftone seed, 150 ppi */
+PIX *pixm; /* image of mask of components, 150 ppi */
+PIX *pixhm1; /* image of halftone mask, 150 ppi */
+PIX *pixhm2; /* image of halftone mask, 300 ppi */
+PIX *pixht; /* image of halftone components, 150 ppi */
+PIX *pixnht; /* image without halftone components, 150 ppi */
+PIX *pixi; /* inverted image, 150 ppi */
+PIX *pixvws; /* image of vertical whitespace, 150 ppi */
+PIX *pixm1; /* image of closed textlines, 150 ppi */
+PIX *pixm2; /* image of refined text line mask, 150 ppi */
+PIX *pixm3; /* image of refined text line mask, 300 ppi */
+PIX *pixb1; /* image of text block mask, 150 ppi */
+PIX *pixb2; /* image of text block mask, 300 ppi */
+PIX *pixnon; /* image of non-text or halftone, 150 ppi */
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa;
+PIXCMAP *cmap;
+PTAA *ptaa;
+l_int32 ht_flag = 0;
+l_int32 ws_flag = 0;
+l_int32 text_flag = 0;
+l_int32 block_flag = 0;
+
+ PROCNAME("DoPageSegmentation");
+
+ if (which == 1)
+ ht_flag = 1;
+ else if (which == 2)
+ ws_flag = 1;
+ else if (which == 3)
+ text_flag = 1;
+ else if (which == 4)
+ block_flag = 1;
+ else
+ return ERROR_INT("invalid parameter: not in [1...4]", procName, 1);
+
+ pixa = pixaCreate(0);
+ lept_mkdir("lept/livre");
+
+ /* Reduce to 150 ppi */
+ pix1 = pixScaleToGray2(pixs);
+ if (ws_flag || ht_flag || block_flag) pixaAddPix(pixa, pix1, L_COPY);
+ if (which == 1)
+ pixWrite("/tmp/lept/livre/orig.gray.150.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ pixr = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
+
+ /* Get seed for halftone parts */
+ pix1 = pixReduceRankBinaryCascade(pixr, 4, 4, 3, 0);
+ pix2 = pixOpenBrick(NULL, pix1, 5, 5);
+ pixhs = pixExpandBinaryPower2(pix2, 8);
+ if (ht_flag) pixaAddPix(pixa, pixhs, L_COPY);
+ if (which == 1)
+ pixWrite("/tmp/lept/livre/htseed.150.png", pixhs, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Get mask for connected regions */
+ pixm = pixCloseSafeBrick(NULL, pixr, 4, 4);
+ if (ht_flag) pixaAddPix(pixa, pixm, L_COPY);
+ if (which == 1)
+ pixWrite("/tmp/lept/livre/ccmask.150.png", pixm, IFF_PNG);
+
+ /* Fill seed into mask to get halftone mask */
+ pixhm1 = pixSeedfillBinary(NULL, pixhs, pixm, 4);
+ if (ht_flag) pixaAddPix(pixa, pixhm1, L_COPY);
+ if (which == 1) pixWrite("/tmp/lept/livre/htmask.150.png", pixhm1, IFF_PNG);
+ pixhm2 = pixExpandBinaryPower2(pixhm1, 2);
+
+ /* Extract halftone stuff */
+ pixht = pixAnd(NULL, pixhm1, pixr);
+ if (which == 1) pixWrite("/tmp/lept/livre/ht.150.png", pixht, IFF_PNG);
+
+ /* Extract non-halftone stuff */
+ pixnht = pixXor(NULL, pixht, pixr);
+ if (text_flag) pixaAddPix(pixa, pixnht, L_COPY);
+ if (which == 1) pixWrite("/tmp/lept/livre/text.150.png", pixnht, IFF_PNG);
+ pixZero(pixht, &zero);
+ if (zero)
+ lept_stderr("No halftone parts found\n");
+ else
+ lept_stderr("Halftone parts found\n");
+
+ /* Get bit-inverted image */
+ pixi = pixInvert(NULL, pixnht);
+ if (ws_flag) pixaAddPix(pixa, pixi, L_COPY);
+ if (which == 1) pixWrite("/tmp/lept/livre/invert.150.png", pixi, IFF_PNG);
+
+ /* The whitespace mask will break textlines where there
+ * is a large amount of white space below or above.
+ * We can prevent this by identifying regions of the
+ * inverted image that have large horizontal (bigger than
+ * the separation between columns) and significant
+ * vertical extent (bigger than the separation between
+ * textlines), and subtracting this from the whitespace mask. */
+ pix1 = pixMorphCompSequence(pixi, "o80.60", 0);
+ pix2 = pixSubtract(NULL, pixi, pix1);
+ if (ws_flag) pixaAddPix(pixa, pix2, L_COPY);
+ pixDestroy(&pix1);
+
+ /* Identify vertical whitespace by opening inverted image */
+ pix3 = pixOpenBrick(NULL, pix2, 5, 1); /* removes thin vertical lines */
+ pixvws = pixOpenBrick(NULL, pix3, 1, 200); /* gets long vertical lines */
+ if (text_flag || ws_flag) pixaAddPix(pixa, pixvws, L_COPY);
+ if (which == 1) pixWrite("/tmp/lept/livre/vertws.150.png", pixvws, IFF_PNG);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Get proto (early processed) text line mask. */
+ /* First close the characters and words in the textlines */
+ pixm1 = pixCloseSafeBrick(NULL, pixnht, 30, 1);
+ if (text_flag) pixaAddPix(pixa, pixm1, L_COPY);
+ if (which == 1)
+ pixWrite("/tmp/lept/livre/textmask1.150.png", pixm1, IFF_PNG);
+
+ /* Next open back up the vertical whitespace corridors */
+ pixm2 = pixSubtract(NULL, pixm1, pixvws);
+ if (which == 1)
+ pixWrite("/tmp/lept/livre/textmask2.150.png", pixm2, IFF_PNG);
+
+ /* Do a small opening to remove noise */
+ pixOpenBrick(pixm2, pixm2, 3, 3);
+ if (text_flag) pixaAddPix(pixa, pixm2, L_COPY);
+ if (which == 1)
+ pixWrite("/tmp/lept/livre/textmask3.150.png", pixm2, IFF_PNG);
+ pixm3 = pixExpandBinaryPower2(pixm2, 2);
+
+ /* Join pixels vertically to make text block mask */
+ pixb1 = pixMorphSequence(pixm2, "c1.10 + o4.1", 0);
+ if (block_flag) pixaAddPix(pixa, pixb1, L_COPY);
+ if (which == 1)
+ pixWrite("/tmp/lept/livre/textblock1.150.png", pixb1, IFF_PNG);
+
+ /* Solidify the textblock mask and remove noise:
+ * (1) For each c.c., 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 */
+ pix1 = pixMorphSequenceByComponent(pixb1, "c30.30 + d3.3", 8, 0, 0, NULL);
+ pixCloseSafeBrick(pix1, pix1, 10, 1);
+ if (block_flag) pixaAddPix(pixa, pix1, L_COPY);
+ pix2 = pixSubtract(NULL, pix1, pixvws);
+ pix3 = pixSelectBySize(pix2, 25, 5, 8, L_SELECT_IF_BOTH,
+ L_SELECT_IF_GTE, NULL);
+ if (block_flag) pixaAddPix(pixa, pix3, L_COPY);
+ if (which == 1)
+ pixWrite("/tmp/lept/livre/textblock2.150.png", pix3, IFF_PNG);
+ pixb2 = pixExpandBinaryPower2(pix3, 2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Identify the outlines of each textblock */
+ ptaa = pixGetOuterBordersPtaa(pixb2);
+ pix1 = pixRenderRandomCmapPtaa(pixb2, ptaa, 1, 8, 1);
+ cmap = pixGetColormap(pix1);
+ pixcmapResetColor(cmap, 0, 130, 130, 130); /* set interior to gray */
+ if (which == 1)
+ pixWrite("/tmp/lept/livre/textblock3.300.png", pix1, IFF_PNG);
+ pixDisplayWithTitle(pix1, 480, 360, "textblock mask with outlines", DFLAG);
+ ptaaDestroy(&ptaa);
+ pixDestroy(&pix1);
+
+ /* Fill line mask (as seed) into the original */
+ pix1 = pixSeedfillBinary(NULL, pixm3, pixs, 8);
+ pixOr(pixm3, pixm3, pix1);
+ pixDestroy(&pix1);
+ if (which == 1)
+ pixWrite("/tmp/lept/livre/textmask.300.png", pixm3, IFF_PNG);
+ pixDisplayWithTitle(pixm3, 480, 360, "textline mask 4", DFLAG);
+
+ /* Fill halftone mask (as seed) into the original */
+ pix1 = pixSeedfillBinary(NULL, pixhm2, pixs, 8);
+ pixOr(pixhm2, pixhm2, pix1);
+ pixDestroy(&pix1);
+ if (which == 1)
+ pixWrite("/tmp/lept/livre/htmask.300.png", pixhm2, IFF_PNG);
+ pixDisplayWithTitle(pixhm2, 520, 390, "halftonemask 2", DFLAG);
+
+ /* Find objects that are neither text nor halftones */
+ pix1 = pixSubtract(NULL, pixs, pixm3); /* remove text pixels */
+ pixnon = pixSubtract(NULL, pix1, pixhm2); /* remove halftone pixels */
+ pixDestroy(&pix1);
+ if (which == 1)
+ pixWrite("/tmp/lept/livre/other.300.png", pixnon, IFF_PNG);
+ pixDisplayWithTitle(pixnon, 540, 420, "other stuff", DFLAG);
+
+ /* Write out b.b. for text line mask and halftone mask components */
+ boxatm = pixConnComp(pixm3, NULL, 4);
+ boxahm = pixConnComp(pixhm2, NULL, 8);
+ if (which == 1) {
+ boxaWrite("/tmp/lept/livre/textmask.boxa", boxatm);
+ boxaWrite("/tmp/lept/livre/htmask.boxa", boxahm);
+ }
+
+ pix1 = pixaDisplayTiledAndScaled(pixa, 8, 250, 4, 0, 25, 2);
+ pixDisplay(pix1, 0, 375 * (which - 1));
+ snprintf(buf, sizeof(buf), "/tmp/lept/livre/segout.%d.png", which);
+ pixWrite(buf, pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ /* clean up to test with valgrind */
+ pixDestroy(&pixr);
+ pixDestroy(&pixhs);
+ pixDestroy(&pixm);
+ pixDestroy(&pixhm1);
+ pixDestroy(&pixhm2);
+ pixDestroy(&pixht);
+ pixDestroy(&pixi);
+ pixDestroy(&pixnht);
+ pixDestroy(&pixvws);
+ pixDestroy(&pixm1);
+ pixDestroy(&pixm2);
+ pixDestroy(&pixm3);
+ pixDestroy(&pixb1);
+ pixDestroy(&pixb2);
+ pixDestroy(&pixnon);
+ boxaDestroy(&boxatm);
+ boxaDestroy(&boxahm);
+ return 0;
+}
+
diff --git a/leptonica/prog/livre_seedgen.c b/leptonica/prog/livre_seedgen.c
new file mode 100644
index 00000000..f6f3d384
--- /dev/null
+++ b/leptonica/prog/livre_seedgen.c
@@ -0,0 +1,75 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * livre_seedgen.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i;
+PIX *pixs, *pix1, *pix2, *pix3;
+PIXA *pixa;
+
+ setLeptDebugOK(1);
+ pixs = pixRead("pageseg2.tif");
+
+ startTimer();
+ for (i = 0; i < 100; i++) {
+ pix1 = pixReduceRankBinaryCascade(pixs, 1, 4, 4, 3);
+ pixDestroy(&pix1);
+ }
+ lept_stderr("Time: %8.4f sec\n", stopTimer() / 100.);
+
+ /* 4 2x rank reductions (levels 1, 4, 4, 3), followed by 5x5 opening */
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pix1 = pixReduceRankBinaryCascade(pixs, 1, 4, 0, 0);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixReduceRankBinaryCascade(pix1, 4, 3, 0, 0);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixOpenBrick(pix2, pix2, 5, 5);
+ pix3 = pixExpandBinaryReplicate(pix2, 2, 2);
+ pixaAddPix(pixa, pix3, L_INSERT);
+
+ /* Generate the output image */
+ lept_mkdir("lept/livre");
+ lept_stderr("Writing to: /tmp/lept/livre/seedgen.png\n");
+ pix1 = pixaDisplayTiledAndScaled(pixa, 8, 350, 4, 0, 25, 2);
+ pixWrite("/tmp/lept/livre/seedgen.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 1100, 0);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+ return 0;
+}
+
diff --git a/leptonica/prog/livre_tophat.c b/leptonica/prog/livre_tophat.c
new file mode 100644
index 00000000..fc5905c7
--- /dev/null
+++ b/leptonica/prog/livre_tophat.c
@@ -0,0 +1,75 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * livre_tophat.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pixsg, *pix1, *pix2;
+PIXA *pixa;
+static char mainName[] = "livre_tophat";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: livre_tophat", mainName, 1);
+ setLeptDebugOK(1);
+
+ /* Read the image in at 150 ppi. */
+ pixs = pixRead("brothers.150.jpg");
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixs, L_INSERT);
+
+ pixsg = pixConvertRGBToLuminance(pixs);
+
+ /* Black tophat (closing - original-image) and invert */
+ pix1 = pixTophat(pixsg, 15, 15, L_TOPHAT_BLACK);
+ pixInvert(pix1, pix1);
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Set black point at 200, white point at 245. */
+ pix2 = pixGammaTRC(NULL, pix1, 1.0, 200, 245);
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ /* Generate the output image */
+ lept_mkdir("lept/livre");
+ lept_stderr("Writing to: /tmp/lept/livre/tophat.jpg\n");
+ pix1 = pixaDisplayTiledAndScaled(pixa, 8, 350, 3, 0, 25, 2);
+ pixWrite("/tmp/lept/livre/tophat.jpg", pix1, IFF_JFIF_JPEG);
+ pixDisplay(pix1, 1200, 800);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixsg);
+ return 0;
+}
+
diff --git a/leptonica/prog/locminmax_reg.c b/leptonica/prog/locminmax_reg.c
new file mode 100644
index 00000000..422555eb
--- /dev/null
+++ b/leptonica/prog/locminmax_reg.c
@@ -0,0 +1,102 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * locminmax_reg.c
+ *
+ * Note: you can remove all minima that are touching the border, using:
+ * pix3 = pixRemoveBorderConnComps(pix1, 8);
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+void DoLocMinmax(L_REGPARAMS *rp, PIX *pixs, l_int32 minmax, l_int32 maxmin);
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j;
+l_float32 f;
+PIX *pix1, *pix2, *pix3;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pix1 = pixCreate(500, 500, 8);
+ for (i = 0; i < 500; i++) {
+ for (j = 0; j < 500; j++) {
+ f = 128.0 + 26.3 * sin(0.0438 * (l_float32)i);
+ f += 33.4 * cos(0.0712 * (l_float32)i);
+ f += 18.6 * sin(0.0561 * (l_float32)j);
+ f += 23.6 * cos(0.0327 * (l_float32)j);
+ pixSetPixel(pix1, j, i, (l_int32)f);
+ }
+ }
+ pix2 = pixRead("karen8.jpg");
+ pix3 = pixBlockconv(pix2, 10, 10);
+ DoLocMinmax(rp, pix1, 0, 0); /* 0 - 2 */
+ DoLocMinmax(rp, pix3, 50, 100); /* 3 - 5 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ return regTestCleanup(rp);
+}
+
+void
+DoLocMinmax(L_REGPARAMS *rp,
+ PIX *pixs,
+ l_int32 minmax,
+ l_int32 maxmin)
+{
+l_uint32 redval, greenval;
+PIX *pix1, *pix2, *pix3, *pixd;
+PIXA *pixa;
+
+ pixa = pixaCreate(0);
+ regTestWritePixAndCheck(rp, pixs, IFF_PNG); /* 0 */
+ pixaAddPix(pixa, pixs, L_COPY);
+ pixLocalExtrema(pixs, minmax, maxmin, &pix1, &pix2);
+ composeRGBPixel(255, 0, 0, &redval);
+ composeRGBPixel(0, 255, 0, &greenval);
+ pixd = pixConvertTo32(pixs);
+ pixPaintThroughMask(pixd, pix2, 0, 0, greenval);
+ pixPaintThroughMask(pixd, pix1, 0, 0, redval);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 1 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pix3 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 25, 2);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix3, 300, 0, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+}
diff --git a/leptonica/prog/logicops_reg.c b/leptonica/prog/logicops_reg.c
new file mode 100644
index 00000000..3bb2c203
--- /dev/null
+++ b/leptonica/prog/logicops_reg.c
@@ -0,0 +1,178 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * logicops_reg.c
+ *
+ * Regression test for pixel-wise logical operations, both in-place and
+ * generating new images. Implemented by rasterops.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pix1, *pix2, *pix3, *pix4;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("test1.png");
+
+
+ /* pixInvert */
+ pix1 = pixInvert(NULL, pixs);
+ pix2 = pixCreateTemplate(pixs); /* into pixd of same size */
+ pixInvert(pix2, pixs);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ regTestComparePix(rp, pix1, pix2); /* 1 */
+
+ pix3 = pixRead("marge.jpg"); /* into pixd of different size */
+ pixInvert(pix3, pixs);
+ regTestComparePix(rp, pix1, pix3); /* 2 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ pix1 = pixOpenBrick(NULL, pixs, 1, 9);
+ pix2 = pixDilateBrick(NULL, pixs, 1, 9);
+
+ /* pixOr */
+ pix3 = pixCreateTemplate(pixs);
+ pixOr(pix3, pixs, pix1); /* existing */
+ pix4 = pixOr(NULL, pixs, pix1); /* new */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3 */
+ regTestComparePix(rp, pix3, pix4); /* 4 */
+ pixCopy(pix4, pix1);
+ pixOr(pix4, pix4, pixs); /* in-place */
+ regTestComparePix(rp, pix3, pix4); /* 5 */
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ pix3 = pixCreateTemplate(pixs);
+ pixOr(pix3, pixs, pix2); /* existing */
+ pix4 = pixOr(NULL, pixs, pix2); /* new */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 6 */
+ regTestComparePix(rp, pix3, pix4); /* 7 */
+ pixCopy(pix4, pix2);
+ pixOr(pix4, pix4, pixs); /* in-place */
+ regTestComparePix(rp, pix3, pix4); /* 8 */
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* pixAnd */
+ pix3 = pixCreateTemplate(pixs);
+ pixAnd(pix3, pixs, pix1); /* existing */
+ pix4 = pixAnd(NULL, pixs, pix1); /* new */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 9 */
+ regTestComparePix(rp, pix3, pix4); /* 10 */
+ pixCopy(pix4, pix1);
+ pixAnd(pix4, pix4, pixs); /* in-place */
+ regTestComparePix(rp, pix3, pix4); /* 11 */
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ pix3 = pixCreateTemplate(pixs);
+ pixAnd(pix3, pixs, pix2); /* existing */
+ pix4 = pixAnd(NULL, pixs, pix2); /* new */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 12 */
+ regTestComparePix(rp, pix3, pix4); /* 13 */
+ pixCopy(pix4, pix2);
+ pixAnd(pix4, pix4, pixs); /* in-place */
+ regTestComparePix(rp, pix3, pix4); /* 14 */
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* pixXor */
+ pix3 = pixCreateTemplate(pixs);
+ pixXor(pix3, pixs, pix1); /* existing */
+ pix4 = pixXor(NULL, pixs, pix1); /* new */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 15 */
+ regTestComparePix(rp, pix3, pix4); /* 16 */
+ pixCopy(pix4, pix1);
+ pixXor(pix4, pix4, pixs); /* in-place */
+ regTestComparePix(rp, pix3, pix4); /* 17 */
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ pix3 = pixCreateTemplate(pixs);
+ pixXor(pix3, pixs, pix2); /* existing */
+ pix4 = pixXor(NULL, pixs, pix2); /* new */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 18 */
+ regTestComparePix(rp, pix3, pix4); /* 19 */
+ pixCopy(pix4, pix2);
+ pixXor(pix4, pix4, pixs); /* in-place */
+ regTestComparePix(rp, pix3, pix4); /* 20 */
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* pixSubtract */
+ pix3 = pixCreateTemplate(pixs);
+ pixSubtract(pix3, pixs, pix1); /* existing */
+ pix4 = pixSubtract(NULL, pixs, pix1); /* new */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 21 */
+ regTestComparePix(rp, pix3, pix4); /* 22 */
+ pixCopy(pix4, pix1);
+ pixSubtract(pix4, pixs, pix4); /* in-place */
+ regTestComparePix(rp, pix3, pix4); /* 23 */
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ pix3 = pixCreateTemplate(pixs);
+ pixSubtract(pix3, pixs, pix2); /* existing */
+ pix4 = pixSubtract(NULL, pixs, pix2); /* new */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 24 */
+ regTestComparePix(rp, pix3, pix4); /* 25 */
+ pixCopy(pix4, pix2);
+ pixSubtract(pix4, pixs, pix4); /* in-place */
+ regTestComparePix(rp, pix3, pix4); /* 26 */
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ pix4 = pixRead("marge.jpg");
+ pixSubtract(pix4, pixs, pixs); /* subtract from itself; should be empty */
+ pix3 = pixCreateTemplate(pixs);
+ regTestComparePix(rp, pix3, pix4); /* 27*/
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ pixSubtract(pixs, pixs, pixs); /* subtract from itself; should be empty */
+ pix3 = pixCreateTemplate(pixs);
+ regTestComparePix(rp, pix3, pixs); /* 28*/
+ pixDestroy(&pix3);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/lowaccess_reg.c b/leptonica/prog/lowaccess_reg.c
new file mode 100644
index 00000000..6a6ec26a
--- /dev/null
+++ b/leptonica/prog/lowaccess_reg.c
@@ -0,0 +1,304 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * lowaccess_reg.c
+ *
+ * Test low-level accessors
+ *
+ * Note that the gnu C++ compiler:
+ * * allows a non-void* ptr to be passed to a function f(void *ptr)
+ * * forbids a void* ptr to be passed to a function f(non-void *ptr)
+ * ('forbids' may be too strong: it issues a warning)
+ *
+ * For this reason, the l_getData*() and l_setData*() accessors
+ * now take a (void *)lineptr, but internally cast to (l_uint32 *)
+ * so that the addressing arithmetic works properly.
+ *
+ * By the same token, the GET_DATA_*() and SET_DATA_*() macro
+ * accessors now cast the input ptr to (l_uint32 *) for 1, 2 and 4 bpp.
+ * This allows them to take a (void *)lineptr.
+ *
+ * In this test, we reconstruct pixs in different ways, pretending
+ * that it is composed of pixels of sizes 1, 2, 4, 8, 16 and 32 bpp.
+ * We also add irrelevant high order bits to the values, testing that
+ * masking is done properly depending on the pixel size.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static void CompareResults(PIX *pixs, PIX *pix1, PIX *pix2,
+ l_int32 count1, l_int32 count2,
+ const char *descr, L_REGPARAMS *rp);
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, k, w, h, w2, w4, w8, w16, w32, wpl;
+l_int32 count1, count2, count3;
+l_uint32 val32, val1, val2;
+l_uint32 *data1, *line1, *data2, *line2;
+void **lines1, **linet1, **linet2;
+PIX *pixs, *pix1, *pix2;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn-fract.tif");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ data1 = pixGetData(pixs);
+ wpl = pixGetWpl(pixs);
+ lines1 = pixGetLinePtrs(pixs, NULL);
+
+ /* Get timing for the 3 different methods */
+ startTimer();
+ for (k = 0; k < 10; k++) {
+ count1 = 0;
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ if (GET_DATA_BIT(lines1[i], j))
+ count1++;
+ }
+ }
+ }
+ lept_stderr("Time with line ptrs = %5.3f sec, count1 = %d\n",
+ stopTimer(), count1);
+
+ startTimer();
+ for (k = 0; k < 10; k++) {
+ count2 = 0;
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl;
+ for (j = 0; j < w; j++) {
+ if (l_getDataBit(line1, j))
+ count2++;
+ }
+ }
+ }
+ lept_stderr("Time with l_get* = %5.3f sec, count2 = %d\n",
+ stopTimer(), count2);
+
+ startTimer();
+ for (k = 0; k < 10; k++) {
+ count3 = 0;
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ pixGetPixel(pixs, j, i, &val32);
+ count3 += val32;
+ }
+ }
+ }
+ lept_stderr("Time with pixGetPixel() = %5.3f sec, count3 = %d\n",
+ stopTimer(), count3);
+
+ pix1 = pixCreateTemplate(pixs);
+ linet1 = pixGetLinePtrs(pix1, NULL);
+ pix2 = pixCreateTemplate(pixs);
+ data2 = pixGetData(pix2);
+ linet2 = pixGetLinePtrs(pix2, NULL);
+
+ /* ------------------------------------------------- */
+ /* Test different methods for 1 bpp */
+ /* ------------------------------------------------- */
+ count1 = 0;
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ val1 = GET_DATA_BIT(lines1[i], j);
+ count1 += val1;
+ if (val1) SET_DATA_BIT(linet1[i], j);
+ }
+ }
+ count2 = 0;
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl;
+ line2 = data2 + i * wpl;
+ for (j = 0; j < w; j++) {
+ val2 = l_getDataBit(line1, j);
+ count2 += val2;
+ if (val2) l_setDataBit(line2, j);
+ }
+ }
+ CompareResults(pixs, pix1, pix2, count1, count2, "1 bpp", rp);
+
+ /* ------------------------------------------------- */
+ /* Test different methods for 2 bpp */
+ /* ------------------------------------------------- */
+ count1 = 0;
+ w2 = w / 2;
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w2; j++) {
+ val1 = GET_DATA_DIBIT(lines1[i], j);
+ count1 += val1;
+ val1 += 0xbbbbbbbc;
+ SET_DATA_DIBIT(linet1[i], j, val1);
+ }
+ }
+ count2 = 0;
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl;
+ line2 = data2 + i * wpl;
+ for (j = 0; j < w2; j++) {
+ val2 = l_getDataDibit(line1, j);
+ count2 += val2;
+ val2 += 0xbbbbbbbc;
+ l_setDataDibit(line2, j, val2);
+ }
+ }
+ CompareResults(pixs, pix1, pix2, count1, count2, "2 bpp", rp);
+
+ /* ------------------------------------------------- */
+ /* Test different methods for 4 bpp */
+ /* ------------------------------------------------- */
+ count1 = 0;
+ w4 = w / 4;
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w4; j++) {
+ val1 = GET_DATA_QBIT(lines1[i], j);
+ count1 += val1;
+ val1 += 0xbbbbbbb0;
+ SET_DATA_QBIT(linet1[i], j, val1);
+ }
+ }
+ count2 = 0;
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl;
+ line2 = data2 + i * wpl;
+ for (j = 0; j < w4; j++) {
+ val2 = l_getDataQbit(line1, j);
+ count2 += val2;
+ val2 += 0xbbbbbbb0;
+ l_setDataQbit(line2, j, val2);
+ }
+ }
+ CompareResults(pixs, pix1, pix2, count1, count2, "4 bpp", rp);
+
+ /* ------------------------------------------------- */
+ /* Test different methods for 8 bpp */
+ /* ------------------------------------------------- */
+ count1 = 0;
+ w8 = w / 8;
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w8; j++) {
+ val1 = GET_DATA_BYTE(lines1[i], j);
+ count1 += val1;
+ val1 += 0xbbbbbb00;
+ SET_DATA_BYTE(linet1[i], j, val1);
+ }
+ }
+ count2 = 0;
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl;
+ line2 = data2 + i * wpl;
+ for (j = 0; j < w8; j++) {
+ val2 = l_getDataByte(line1, j);
+ count2 += val2;
+ val2 += 0xbbbbbb00;
+ l_setDataByte(line2, j, val2);
+ }
+ }
+ CompareResults(pixs, pix1, pix2, count1, count2, "8 bpp", rp);
+
+ /* ------------------------------------------------- */
+ /* Test different methods for 16 bpp */
+ /* ------------------------------------------------- */
+ count1 = 0;
+ w16 = w / 16;
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w16; j++) {
+ val1 = GET_DATA_TWO_BYTES(lines1[i], j);
+ count1 += val1;
+ val1 += 0xbbbb0000;
+ SET_DATA_TWO_BYTES(linet1[i], j, val1);
+ }
+ }
+ count2 = 0;
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl;
+ line2 = data2 + i * wpl;
+ for (j = 0; j < w16; j++) {
+ val2 = l_getDataTwoBytes(line1, j);
+ count2 += val2;
+ val2 += 0xbbbb0000;
+ l_setDataTwoBytes(line2, j, val2);
+ }
+ }
+ CompareResults(pixs, pix1, pix2, count1, count2, "16 bpp", rp);
+
+ /* ------------------------------------------------- */
+ /* Test different methods for 32 bpp */
+ /* ------------------------------------------------- */
+ count1 = 0;
+ w32 = w / 32;
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w32; j++) {
+ val1 = GET_DATA_FOUR_BYTES(lines1[i], j);
+ count1 += val1 & 0xfff;
+ SET_DATA_FOUR_BYTES(linet1[i], j, val1);
+ }
+ }
+ count2 = 0;
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl;
+ line2 = data2 + i * wpl;
+ for (j = 0; j < w32; j++) {
+ val2 = l_getDataFourBytes(line1, j);
+ count2 += val2 & 0xfff;
+ l_setDataFourBytes(line2, j, val2);
+ }
+ }
+ CompareResults(pixs, pix1, pix2, count1, count2, "32 bpp", rp);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ lept_free(lines1);
+ lept_free(linet1);
+ lept_free(linet2);
+ return regTestCleanup(rp);
+}
+
+
+static void
+CompareResults(PIX *pixs,
+ PIX *pix1,
+ PIX *pix2,
+ l_int32 count1,
+ l_int32 count2,
+ const char *descr,
+ L_REGPARAMS *rp)
+{
+ lept_stderr("Compare set: %s; index starts at %d\n", descr, rp->index + 1);
+ regTestComparePix(rp, pixs, pix1);
+ regTestComparePix(rp, pixs, pix2);
+ regTestCompareValues(rp, count1, count2, 1);
+ pixClearAll(pix1);
+ pixClearAll(pix2);
+}
+
diff --git a/leptonica/prog/lowsat_reg.c b/leptonica/prog/lowsat_reg.c
new file mode 100644
index 00000000..6a01dea6
--- /dev/null
+++ b/leptonica/prog/lowsat_reg.c
@@ -0,0 +1,107 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * lowsat_reg.c
+ *
+ * Testing functions that identify and modify image pixels that
+ * have low saturation (i.e., are essentially gray).
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, w, h, wpl, val;
+l_uint32 gray32;
+l_uint32 *data, *line;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/lowsat");
+ pixa = pixaCreate(0);
+ pix1 = pixRead("zier.jpg");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix1, 0, 100, NULL, rp->display);
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Embed the image in a varying gray background */
+ pix2 = pixCreate(400, 580, 32);
+ data = pixGetData(pix2);
+ wpl = pixGetWpl(pix2);
+ for (i = 0; i < 580; i++) {
+ line = data + i * wpl;
+ val = 150 + 50 * i / 580;
+ for (j = 0; j < 400; j++) {
+ composeRGBPixel(val, val, val, &gray32);
+ line[j] = gray32;
+ }
+ }
+ pixRasterop(pix2, 70, 90, 270, 400, PIX_SRC, pix1, 0, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+ pixaAddPix(pixa, pix2, L_COPY);
+ pixDisplayWithTitle(pix2, 300, 100, NULL, rp->display);
+
+ /* Darken the gray pixels, leaving most of the
+ * the others unaffected. */
+ pix3 = pixDarkenGray(NULL, pix2, 220, 10);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 2 */
+ pixaAddPix(pixa, pix3, L_COPY);
+ pixDisplayWithTitle(pix3, 700, 100, "gray pixels are black", rp->display);
+
+ /* We can also generate a mask over the gray pixels,
+ * eliminating noise from very dark pixels morphologically. */
+ pix4 = pixMaskOverGrayPixels(pix2, 220, 10);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 3 */
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDisplayWithTitle(pix4, 1100, 100, "mask over gray pixels", rp->display);
+ pix5 = pixMorphSequence(pix4, "o20.20", 0); /* remove noise */
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 4 */
+ pixaAddPix(pixa, pix5, L_COPY);
+ pixDisplayWithTitle(pix5, 1500, 100, "clean mask over gray", rp->display);
+ pixInvert(pix5, pix5);
+ pix6 = pixConvertTo32(pix5);
+ pix7 = pixAddRGB(pix2, pix6);
+ regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 5 */
+ pixaAddPix(pixa, pix7, L_INSERT);
+ pixDisplayWithTitle(pix7, 1900, 100, NULL, rp->display);
+
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixaDestroy(&pixa);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/lucasta-frag.jpg b/leptonica/prog/lucasta-frag.jpg
new file mode 100644
index 00000000..5df491b5
--- /dev/null
+++ b/leptonica/prog/lucasta-frag.jpg
Binary files differ
diff --git a/leptonica/prog/lucasta.047.jpg b/leptonica/prog/lucasta.047.jpg
new file mode 100644
index 00000000..58328b10
--- /dev/null
+++ b/leptonica/prog/lucasta.047.jpg
Binary files differ
diff --git a/leptonica/prog/lucasta.1.300.tif b/leptonica/prog/lucasta.1.300.tif
new file mode 100644
index 00000000..b086ce19
--- /dev/null
+++ b/leptonica/prog/lucasta.1.300.tif
Binary files differ
diff --git a/leptonica/prog/lucasta.150.jpg b/leptonica/prog/lucasta.150.jpg
new file mode 100644
index 00000000..baf70b1f
--- /dev/null
+++ b/leptonica/prog/lucasta.150.jpg
Binary files differ
diff --git a/leptonica/prog/lyra.005.jpg b/leptonica/prog/lyra.005.jpg
new file mode 100644
index 00000000..70b15f84
--- /dev/null
+++ b/leptonica/prog/lyra.005.jpg
Binary files differ
diff --git a/leptonica/prog/lyra.036.jpg b/leptonica/prog/lyra.036.jpg
new file mode 100644
index 00000000..8c52244f
--- /dev/null
+++ b/leptonica/prog/lyra.036.jpg
Binary files differ
diff --git a/leptonica/prog/lyra.5.na b/leptonica/prog/lyra.5.na
new file mode 100644
index 00000000..29cb80a0
--- /dev/null
+++ b/leptonica/prog/lyra.5.na
@@ -0,0 +1,725 @@
+
+Numa Version 1
+Number of numbers = 721
+ [0] = 3.000000
+ [1] = 0.000000
+ [2] = 0.000000
+ [3] = 0.000000
+ [4] = 0.000000
+ [5] = 0.000000
+ [6] = 0.000000
+ [7] = 0.000000
+ [8] = 0.000000
+ [9] = 0.000000
+ [10] = 0.000000
+ [11] = 0.000000
+ [12] = 0.000000
+ [13] = 0.000000
+ [14] = 0.000000
+ [15] = 0.000000
+ [16] = 0.000000
+ [17] = 0.000000
+ [18] = 0.000000
+ [19] = 0.000000
+ [20] = 0.000000
+ [21] = 0.000000
+ [22] = 0.000000
+ [23] = 0.000000
+ [24] = 0.000000
+ [25] = 0.000000
+ [26] = 0.000000
+ [27] = 0.000000
+ [28] = 1.000000
+ [29] = 0.000000
+ [30] = 6.000000
+ [31] = 5.000000
+ [32] = 0.000000
+ [33] = 0.000000
+ [34] = 0.000000
+ [35] = 0.000000
+ [36] = 0.000000
+ [37] = 0.000000
+ [38] = 0.000000
+ [39] = 0.000000
+ [40] = 0.000000
+ [41] = 0.000000
+ [42] = 0.000000
+ [43] = 0.000000
+ [44] = 0.000000
+ [45] = 0.000000
+ [46] = 0.000000
+ [47] = 0.000000
+ [48] = 0.000000
+ [49] = 0.000000
+ [50] = 0.000000
+ [51] = 0.000000
+ [52] = 0.000000
+ [53] = 0.000000
+ [54] = 0.000000
+ [55] = 0.000000
+ [56] = 0.000000
+ [57] = 0.000000
+ [58] = 0.000000
+ [59] = 0.000000
+ [60] = 0.000000
+ [61] = 0.000000
+ [62] = 0.000000
+ [63] = 0.000000
+ [64] = 0.000000
+ [65] = 0.000000
+ [66] = 0.000000
+ [67] = 0.000000
+ [68] = 0.000000
+ [69] = 0.000000
+ [70] = 0.000000
+ [71] = 0.000000
+ [72] = 0.000000
+ [73] = 0.000000
+ [74] = 0.000000
+ [75] = 0.000000
+ [76] = 0.000000
+ [77] = 0.000000
+ [78] = 0.000000
+ [79] = 0.000000
+ [80] = 0.000000
+ [81] = 0.000000
+ [82] = 0.000000
+ [83] = 0.000000
+ [84] = 0.000000
+ [85] = 0.000000
+ [86] = 0.000000
+ [87] = 0.000000
+ [88] = 0.000000
+ [89] = 0.000000
+ [90] = 0.000000
+ [91] = 0.000000
+ [92] = 0.000000
+ [93] = 0.000000
+ [94] = 0.000000
+ [95] = 0.000000
+ [96] = 0.000000
+ [97] = 0.000000
+ [98] = 0.000000
+ [99] = 0.000000
+ [100] = 0.000000
+ [101] = 0.000000
+ [102] = 1.000000
+ [103] = 0.000000
+ [104] = 0.000000
+ [105] = 0.000000
+ [106] = 0.000000
+ [107] = 0.000000
+ [108] = 0.000000
+ [109] = 0.000000
+ [110] = 0.000000
+ [111] = 0.000000
+ [112] = 1.000000
+ [113] = 0.000000
+ [114] = 0.000000
+ [115] = 0.000000
+ [116] = 0.000000
+ [117] = 0.000000
+ [118] = 0.000000
+ [119] = 0.000000
+ [120] = 0.000000
+ [121] = 0.000000
+ [122] = 0.000000
+ [123] = 0.000000
+ [124] = 0.000000
+ [125] = 0.000000
+ [126] = 0.000000
+ [127] = 0.000000
+ [128] = 0.000000
+ [129] = 0.000000
+ [130] = 0.000000
+ [131] = 0.000000
+ [132] = 0.000000
+ [133] = 0.000000
+ [134] = 0.000000
+ [135] = 0.000000
+ [136] = 0.000000
+ [137] = 0.000000
+ [138] = 0.000000
+ [139] = 0.000000
+ [140] = 0.000000
+ [141] = 0.000000
+ [142] = 0.000000
+ [143] = 0.000000
+ [144] = 0.000000
+ [145] = 0.000000
+ [146] = 0.000000
+ [147] = 0.000000
+ [148] = 0.000000
+ [149] = 0.000000
+ [150] = 0.000000
+ [151] = 0.000000
+ [152] = 0.000000
+ [153] = 0.000000
+ [154] = 0.000000
+ [155] = 0.000000
+ [156] = 0.000000
+ [157] = 0.000000
+ [158] = 0.000000
+ [159] = 0.000000
+ [160] = 0.000000
+ [161] = 0.000000
+ [162] = 0.000000
+ [163] = 0.000000
+ [164] = 0.000000
+ [165] = 0.000000
+ [166] = 0.000000
+ [167] = 7.000000
+ [168] = 27.000000
+ [169] = 39.000000
+ [170] = 75.000000
+ [171] = 95.000000
+ [172] = 113.000000
+ [173] = 109.000000
+ [174] = 111.000000
+ [175] = 103.000000
+ [176] = 103.000000
+ [177] = 111.000000
+ [178] = 107.000000
+ [179] = 83.000000
+ [180] = 79.000000
+ [181] = 85.000000
+ [182] = 93.000000
+ [183] = 77.000000
+ [184] = 68.000000
+ [185] = 76.000000
+ [186] = 80.000000
+ [187] = 80.000000
+ [188] = 89.000000
+ [189] = 101.000000
+ [190] = 127.000000
+ [191] = 135.000000
+ [192] = 107.000000
+ [193] = 120.000000
+ [194] = 112.000000
+ [195] = 124.000000
+ [196] = 113.000000
+ [197] = 107.000000
+ [198] = 91.000000
+ [199] = 78.000000
+ [200] = 74.000000
+ [201] = 77.000000
+ [202] = 87.000000
+ [203] = 85.000000
+ [204] = 93.000000
+ [205] = 92.000000
+ [206] = 98.000000
+ [207] = 102.000000
+ [208] = 82.000000
+ [209] = 103.000000
+ [210] = 91.000000
+ [211] = 99.000000
+ [212] = 103.000000
+ [213] = 101.000000
+ [214] = 109.000000
+ [215] = 89.000000
+ [216] = 79.000000
+ [217] = 97.000000
+ [218] = 99.000000
+ [219] = 95.000000
+ [220] = 91.000000
+ [221] = 97.000000
+ [222] = 87.000000
+ [223] = 97.000000
+ [224] = 85.000000
+ [225] = 109.000000
+ [226] = 103.000000
+ [227] = 107.000000
+ [228] = 97.000000
+ [229] = 89.000000
+ [230] = 87.000000
+ [231] = 95.000000
+ [232] = 101.000000
+ [233] = 97.000000
+ [234] = 79.000000
+ [235] = 85.000000
+ [236] = 109.000000
+ [237] = 93.000000
+ [238] = 97.000000
+ [239] = 87.000000
+ [240] = 87.000000
+ [241] = 103.000000
+ [242] = 97.000000
+ [243] = 93.000000
+ [244] = 107.000000
+ [245] = 111.000000
+ [246] = 107.000000
+ [247] = 131.000000
+ [248] = 115.000000
+ [249] = 117.000000
+ [250] = 105.000000
+ [251] = 81.000000
+ [252] = 93.000000
+ [253] = 103.000000
+ [254] = 89.000000
+ [255] = 75.000000
+ [256] = 91.000000
+ [257] = 99.000000
+ [258] = 99.000000
+ [259] = 133.000000
+ [260] = 127.000000
+ [261] = 107.000000
+ [262] = 119.000000
+ [263] = 101.000000
+ [264] = 107.000000
+ [265] = 101.000000
+ [266] = 89.000000
+ [267] = 97.000000
+ [268] = 91.000000
+ [269] = 97.000000
+ [270] = 101.000000
+ [271] = 107.000000
+ [272] = 101.000000
+ [273] = 97.000000
+ [274] = 99.000000
+ [275] = 97.000000
+ [276] = 99.000000
+ [277] = 105.000000
+ [278] = 103.000000
+ [279] = 103.000000
+ [280] = 87.000000
+ [281] = 89.000000
+ [282] = 103.000000
+ [283] = 87.000000
+ [284] = 97.000000
+ [285] = 103.000000
+ [286] = 105.000000
+ [287] = 107.000000
+ [288] = 117.000000
+ [289] = 121.000000
+ [290] = 91.000000
+ [291] = 111.000000
+ [292] = 100.000000
+ [293] = 102.000000
+ [294] = 83.000000
+ [295] = 95.000000
+ [296] = 85.000000
+ [297] = 95.000000
+ [298] = 109.000000
+ [299] = 115.000000
+ [300] = 113.000000
+ [301] = 103.000000
+ [302] = 119.000000
+ [303] = 111.000000
+ [304] = 103.000000
+ [305] = 107.000000
+ [306] = 93.000000
+ [307] = 102.000000
+ [308] = 104.000000
+ [309] = 100.000000
+ [310] = 82.000000
+ [311] = 85.000000
+ [312] = 108.000000
+ [313] = 120.000000
+ [314] = 99.000000
+ [315] = 111.000000
+ [316] = 99.000000
+ [317] = 103.000000
+ [318] = 111.000000
+ [319] = 101.000000
+ [320] = 113.000000
+ [321] = 107.000000
+ [322] = 99.000000
+ [323] = 105.000000
+ [324] = 85.000000
+ [325] = 89.000000
+ [326] = 95.000000
+ [327] = 81.000000
+ [328] = 101.000000
+ [329] = 105.000000
+ [330] = 113.000000
+ [331] = 95.000000
+ [332] = 93.000000
+ [333] = 109.000000
+ [334] = 121.000000
+ [335] = 109.000000
+ [336] = 115.000000
+ [337] = 113.000000
+ [338] = 114.000000
+ [339] = 118.000000
+ [340] = 103.000000
+ [341] = 114.000000
+ [342] = 105.000000
+ [343] = 107.000000
+ [344] = 103.000000
+ [345] = 101.000000
+ [346] = 97.000000
+ [347] = 91.000000
+ [348] = 93.000000
+ [349] = 95.000000
+ [350] = 105.000000
+ [351] = 107.000000
+ [352] = 101.000000
+ [353] = 119.000000
+ [354] = 111.000000
+ [355] = 95.000000
+ [356] = 113.000000
+ [357] = 97.000000
+ [358] = 119.000000
+ [359] = 99.000000
+ [360] = 111.000000
+ [361] = 97.000000
+ [362] = 77.000000
+ [363] = 93.000000
+ [364] = 97.000000
+ [365] = 91.000000
+ [366] = 95.000000
+ [367] = 95.000000
+ [368] = 103.000000
+ [369] = 99.000000
+ [370] = 93.000000
+ [371] = 99.000000
+ [372] = 99.000000
+ [373] = 97.000000
+ [374] = 95.000000
+ [375] = 85.000000
+ [376] = 95.000000
+ [377] = 105.000000
+ [378] = 99.000000
+ [379] = 105.000000
+ [380] = 93.000000
+ [381] = 107.000000
+ [382] = 103.000000
+ [383] = 95.000000
+ [384] = 91.000000
+ [385] = 95.000000
+ [386] = 109.000000
+ [387] = 105.000000
+ [388] = 91.000000
+ [389] = 103.000000
+ [390] = 93.000000
+ [391] = 95.000000
+ [392] = 95.000000
+ [393] = 99.000000
+ [394] = 105.000000
+ [395] = 107.000000
+ [396] = 111.000000
+ [397] = 101.000000
+ [398] = 101.000000
+ [399] = 93.000000
+ [400] = 99.000000
+ [401] = 103.000000
+ [402] = 111.000000
+ [403] = 101.000000
+ [404] = 97.000000
+ [405] = 95.000000
+ [406] = 95.000000
+ [407] = 113.000000
+ [408] = 101.000000
+ [409] = 115.000000
+ [410] = 103.000000
+ [411] = 85.000000
+ [412] = 93.000000
+ [413] = 85.000000
+ [414] = 93.000000
+ [415] = 95.000000
+ [416] = 97.000000
+ [417] = 91.000000
+ [418] = 87.000000
+ [419] = 75.000000
+ [420] = 93.000000
+ [421] = 83.000000
+ [422] = 83.000000
+ [423] = 85.000000
+ [424] = 97.000000
+ [425] = 101.000000
+ [426] = 107.000000
+ [427] = 113.000000
+ [428] = 109.000000
+ [429] = 99.000000
+ [430] = 103.000000
+ [431] = 99.000000
+ [432] = 107.000000
+ [433] = 91.000000
+ [434] = 101.000000
+ [435] = 91.000000
+ [436] = 111.000000
+ [437] = 113.000000
+ [438] = 81.000000
+ [439] = 87.000000
+ [440] = 91.000000
+ [441] = 93.000000
+ [442] = 97.000000
+ [443] = 105.000000
+ [444] = 105.000000
+ [445] = 89.000000
+ [446] = 87.000000
+ [447] = 93.000000
+ [448] = 97.000000
+ [449] = 83.000000
+ [450] = 85.000000
+ [451] = 67.000000
+ [452] = 81.000000
+ [453] = 103.000000
+ [454] = 99.000000
+ [455] = 71.000000
+ [456] = 77.000000
+ [457] = 83.000000
+ [458] = 85.000000
+ [459] = 79.000000
+ [460] = 89.000000
+ [461] = 101.000000
+ [462] = 83.000000
+ [463] = 87.000000
+ [464] = 81.000000
+ [465] = 81.000000
+ [466] = 73.000000
+ [467] = 73.000000
+ [468] = 79.000000
+ [469] = 77.000000
+ [470] = 75.000000
+ [471] = 95.000000
+ [472] = 101.000000
+ [473] = 103.000000
+ [474] = 97.000000
+ [475] = 95.000000
+ [476] = 99.000000
+ [477] = 93.000000
+ [478] = 95.000000
+ [479] = 103.000000
+ [480] = 101.000000
+ [481] = 101.000000
+ [482] = 107.000000
+ [483] = 81.000000
+ [484] = 91.000000
+ [485] = 103.000000
+ [486] = 91.000000
+ [487] = 79.000000
+ [488] = 77.000000
+ [489] = 97.000000
+ [490] = 91.000000
+ [491] = 85.000000
+ [492] = 91.000000
+ [493] = 71.000000
+ [494] = 85.000000
+ [495] = 71.000000
+ [496] = 83.000000
+ [497] = 93.000000
+ [498] = 85.000000
+ [499] = 89.000000
+ [500] = 87.000000
+ [501] = 109.000000
+ [502] = 89.000000
+ [503] = 75.000000
+ [504] = 89.000000
+ [505] = 75.000000
+ [506] = 87.000000
+ [507] = 93.000000
+ [508] = 87.000000
+ [509] = 81.000000
+ [510] = 91.000000
+ [511] = 91.000000
+ [512] = 91.000000
+ [513] = 89.000000
+ [514] = 101.000000
+ [515] = 105.000000
+ [516] = 111.000000
+ [517] = 99.000000
+ [518] = 93.000000
+ [519] = 105.000000
+ [520] = 103.000000
+ [521] = 101.000000
+ [522] = 87.000000
+ [523] = 93.000000
+ [524] = 89.000000
+ [525] = 91.000000
+ [526] = 79.000000
+ [527] = 59.000000
+ [528] = 13.000000
+ [529] = 9.000000
+ [530] = 7.000000
+ [531] = 5.000000
+ [532] = 5.000000
+ [533] = 1.000000
+ [534] = 0.000000
+ [535] = 0.000000
+ [536] = 0.000000
+ [537] = 0.000000
+ [538] = 0.000000
+ [539] = 0.000000
+ [540] = 0.000000
+ [541] = 0.000000
+ [542] = 0.000000
+ [543] = 0.000000
+ [544] = 0.000000
+ [545] = 0.000000
+ [546] = 0.000000
+ [547] = 0.000000
+ [548] = 0.000000
+ [549] = 0.000000
+ [550] = 0.000000
+ [551] = 1.000000
+ [552] = 0.000000
+ [553] = 0.000000
+ [554] = 0.000000
+ [555] = 0.000000
+ [556] = 0.000000
+ [557] = 0.000000
+ [558] = 0.000000
+ [559] = 0.000000
+ [560] = 0.000000
+ [561] = 0.000000
+ [562] = 0.000000
+ [563] = 0.000000
+ [564] = 0.000000
+ [565] = 0.000000
+ [566] = 0.000000
+ [567] = 0.000000
+ [568] = 0.000000
+ [569] = 0.000000
+ [570] = 0.000000
+ [571] = 0.000000
+ [572] = 0.000000
+ [573] = 0.000000
+ [574] = 0.000000
+ [575] = 0.000000
+ [576] = 0.000000
+ [577] = 0.000000
+ [578] = 0.000000
+ [579] = 0.000000
+ [580] = 0.000000
+ [581] = 0.000000
+ [582] = 0.000000
+ [583] = 0.000000
+ [584] = 0.000000
+ [585] = 0.000000
+ [586] = 0.000000
+ [587] = 0.000000
+ [588] = 0.000000
+ [589] = 0.000000
+ [590] = 0.000000
+ [591] = 0.000000
+ [592] = 0.000000
+ [593] = 0.000000
+ [594] = 0.000000
+ [595] = 5.000000
+ [596] = 17.000000
+ [597] = 7.000000
+ [598] = 7.000000
+ [599] = 0.000000
+ [600] = 0.000000
+ [601] = 0.000000
+ [602] = 0.000000
+ [603] = 0.000000
+ [604] = 0.000000
+ [605] = 0.000000
+ [606] = 0.000000
+ [607] = 0.000000
+ [608] = 0.000000
+ [609] = 0.000000
+ [610] = 0.000000
+ [611] = 0.000000
+ [612] = 0.000000
+ [613] = 0.000000
+ [614] = 1.000000
+ [615] = 5.000000
+ [616] = 43.000000
+ [617] = 87.000000
+ [618] = 87.000000
+ [619] = 83.000000
+ [620] = 95.000000
+ [621] = 93.000000
+ [622] = 91.000000
+ [623] = 101.000000
+ [624] = 87.000000
+ [625] = 89.000000
+ [626] = 87.000000
+ [627] = 91.000000
+ [628] = 86.000000
+ [629] = 81.000000
+ [630] = 71.000000
+ [631] = 87.000000
+ [632] = 77.000000
+ [633] = 91.000000
+ [634] = 87.000000
+ [635] = 79.000000
+ [636] = 79.000000
+ [637] = 89.000000
+ [638] = 93.000000
+ [639] = 87.000000
+ [640] = 89.000000
+ [641] = 101.000000
+ [642] = 95.000000
+ [643] = 99.000000
+ [644] = 83.000000
+ [645] = 83.000000
+ [646] = 71.000000
+ [647] = 71.000000
+ [648] = 81.000000
+ [649] = 84.000000
+ [650] = 98.000000
+ [651] = 101.000000
+ [652] = 81.000000
+ [653] = 83.000000
+ [654] = 90.000000
+ [655] = 91.000000
+ [656] = 80.000000
+ [657] = 85.000000
+ [658] = 91.000000
+ [659] = 79.000000
+ [660] = 99.000000
+ [661] = 83.000000
+ [662] = 81.000000
+ [663] = 102.000000
+ [664] = 105.000000
+ [665] = 89.000000
+ [666] = 86.000000
+ [667] = 84.000000
+ [668] = 78.000000
+ [669] = 86.000000
+ [670] = 101.000000
+ [671] = 90.000000
+ [672] = 82.000000
+ [673] = 91.000000
+ [674] = 93.000000
+ [675] = 93.000000
+ [676] = 95.000000
+ [677] = 95.000000
+ [678] = 97.000000
+ [679] = 89.000000
+ [680] = 90.000000
+ [681] = 90.000000
+ [682] = 104.000000
+ [683] = 90.000000
+ [684] = 83.000000
+ [685] = 84.000000
+ [686] = 96.000000
+ [687] = 86.000000
+ [688] = 86.000000
+ [689] = 74.000000
+ [690] = 76.000000
+ [691] = 86.000000
+ [692] = 86.000000
+ [693] = 87.000000
+ [694] = 100.000000
+ [695] = 90.000000
+ [696] = 84.000000
+ [697] = 92.000000
+ [698] = 90.000000
+ [699] = 83.000000
+ [700] = 79.000000
+ [701] = 79.000000
+ [702] = 71.000000
+ [703] = 81.000000
+ [704] = 79.000000
+ [705] = 89.000000
+ [706] = 85.000000
+ [707] = 83.000000
+ [708] = 95.000000
+ [709] = 87.000000
+ [710] = 73.000000
+ [711] = 74.000000
+ [712] = 72.000000
+ [713] = 67.000000
+ [714] = 74.000000
+ [715] = 80.000000
+ [716] = 78.000000
+ [717] = 80.000000
+ [718] = 82.000000
+ [719] = 0.000000
+ [720] = 0.000000
+
diff --git a/leptonica/prog/makefile.static b/leptonica/prog/makefile.static
new file mode 100644
index 00000000..d85c42b1
--- /dev/null
+++ b/leptonica/prog/makefile.static
@@ -0,0 +1,1236 @@
+#/*====================================================================*
+# - 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)
+#
+# For a nodebug version: make
+# For a debug version: make DEBUG=yes
+# For a shared library version: make SHARED=yes
+# With nonstandard library directories:
+# make EXTRALIBS="-L<nonstandard-lib-dir>"
+# With nonstandard header directories
+# make EXTRAINCLUDES="-I<nonstandard-incl-dir>"
+#
+# To remove all executables: make clean
+#
+# ========================================================================
+# IMPORTANT: This Makefile is set up to link with liblept.a and liblept.so
+# that were built using using src/makefile (i.e., with 'make' in src).
+# If instead you built the library with configure/make/makeinstall,
+# you must do this here:
+#
+# (1) Change LIB_NODEBUG_DIR:
+# LIB_NODEBUG_DIR = /usr/local/lib [or wherever you installed
+# liblept.a]
+# (2) Edit ALL_LIBS to include the imaging libraries on your system,
+# as found in config_auto.h. For example, if you have the
+# jpeg, png, tiff and gif libraries, set
+# ALL_LIBS = $(LEPTLIB) -ltiff -ljpeg -lpng -lgif -lz -lm
+# ========================================================================
+#
+# To link and run programs using shared (dynamic linked) libraries,
+# you must do one of the following two things:
+#
+# (a) make sure your LD_LIBRARY_PATH variable points to the
+# directory in which the *.so files are placed; e.g.
+# export LD_LIBRARY_PATH=../lib/shared:$LD_LIBRARY_PATH
+# or
+# (b) have the *.so files (or links to them) in this directory
+#
+# On cygwin, remove -fPIC from CC. I believe that these files --
+# convertfilestops.c, jbcorrelation.c, jbrankhaus.c, maketile.c,
+# and htmlviewer.c -- which use posix directory functions, will
+# now work properly.
+#
+# See below for enabling gif read/write.
+
+
+# Libraries are built into a binary tree with its root in the
+# parent directory
+ROOT_DIR = ..
+
+LIB_NODEBUG_DIR = $(ROOT_DIR)/lib/nodebug
+LIB_DEBUG_DIR = $(ROOT_DIR)/lib/debug
+LIB_SHARED_DIR = $(ROOT_DIR)/lib/shared
+
+# Include files are found within the same tree
+IMAGELIB_INCL = $(ROOT_DIR)/src
+
+CC = gcc -std=c99 -U__STRICT_ANSI__ -D__USE_MINGW_ANSI_STDIO
+#CC = gcc -std=c89 -U__STRICT_ANSI__ -D__USE_MINGW_ANSI_STDIO
+#CC = gcc -std=gnu99 -D_POSIX_C_SOURCE=200809L -DANSI -fPIC
+#CC = gcc -std=gnu89 -DANSI -fPIC
+#CC = gcc -std=gnu89 -Werror -DANSI -fPIC
+#CC = g++ -Werror -fPIC
+
+ifdef SHARED
+ LIB_DIR = $(LIB_SHARED_DIR)
+ OPTIMIZE = -O2
+else
+ ifdef DEBUG
+ LIB_DIR = $(LIB_DEBUG_DIR)
+ OPTIMIZE = -g
+ else
+ LIB_DIR = $(LIB_NODEBUG_DIR)
+ OPTIMIZE = -O2
+ endif
+endif
+
+OPTIONS =
+
+INCLUDES = -I$(IMAGELIB_INCL) -I/usr/X11R6/include $(EXTRAINCLUDES)
+#INCLUDES = -I$(IMAGELIB_INCL) -I/usr/X11R6/include -I/usr/local/include $(EXTRAINCLUDES)
+
+CFLAGS = $(OPTIMIZE) $(OPTIONS)
+
+# Use -D_STANDARD_C to stub out the non-posix GNU functions fmemopen()
+# and open_memstream()
+CPPFLAGS = $(INCLUDES)
+
+LDFLAGS += -L$(LIB_DIR) -L/usr/X11R6/lib -L/usr/lib $(EXTRALIBS)
+#LDFLAGS += -L$(LIB_DIR) -L/usr/X11R6/lib -L/usr/local/lib -L/usr/lib $(EXTRALIBS)
+
+ifdef SHARED
+ LEPTLIB = $(LIB_DIR)/liblept.so
+else
+ LEPTLIB = $(LIB_DIR)/liblept.a
+endif
+
+# To enable webp read/write:
+# (1) In src/environ.h: #define HAVE_LIBWEBP
+# (2) Install libwebp
+# (3) Include -lwebp in ALL_LIBS
+# To enable webpanim read/write:
+# (1) In src/environ.h: #define HAVE_LIBWEBP_ANIM
+# (2) Install libwebp with
+# ./configure --enable-everything (to get mux/demux libs)
+# Modify makefile.unix to add -fPIC:
+# CFLAGS = -O3 -DNDEBUG $(EXTRA_FLAGS) -fPIC
+# (3) Include -lwebpmux in ALL_LIBS
+# To enable gif read/write:
+# (1) In src/environ.h: #define HAVE_LIBGIF
+# (2) Install libgif
+# (3) Include -lgif in ALL_LIBS
+
+# Be sure LD_LIBRARY_PATH includes the appropriate library directories, such
+# as /usr/local/include, in which libwebp.so and/or libgif.so are installed
+# Use or modify an appropriate line below for ALL_LIBS:
+ALL_LIBS = $(LEPTLIB) -ltiff -ljpeg -lpng -lz -lm
+#ALL_LIBS = $(LEPTLIB) -ltiff -ljpeg -lpng -lwebp -lz -lm
+#ALL_LIBS = $(LEPTLIB) -ltiff -ljpeg -lpng -lwebp -lwebpmux -lz -lm
+#ALL_LIBS = $(LEPTLIB) -ltiff -ljpeg -lpng -lwebp -lopenjp2 -lz -lm
+#ALL_LIBS = $(LEPTLIB) -ltiff -ljpeg -lpng -lwebp -lopenjp2 -lgif -lz -lm
+#ALL_LIBS = $(LEPTLIB) -ltiff -ljpeg -lpng -lwebp -lwebpmux -lopenjp2 -lgif -lz -lm
+
+#########################################################################
+
+SRC = adaptmap_reg.c adaptnorm_reg.c affine_reg.c \
+ alltests_reg.c alphaops_reg.c alphaxform_reg.c \
+ baseline_reg.c bilateral1_reg.c bilateral2_reg.c \
+ bilinear_reg.c binarize_reg.c \
+ binmorph1_reg.c binmorph2_reg.c binmorph3_reg.c \
+ binmorph4_reg.c binmorph5_reg.c binmorph6_reg.c \
+ blackwhite_reg.c blend1_reg.c blend2_reg.c \
+ blend3_reg.c blend4_reg.c blend5_reg.c \
+ boxa1_reg.c boxa2_reg.c boxa3_reg.c boxa4_reg.c \
+ bytea_reg.c ccbord_reg.c ccthin1_reg.c ccthin2_reg.c \
+ checkerboard_reg.c circle_reg.c \
+ cmapquant_reg.c colorcontent_reg.c \
+ colorfill_reg.c coloring_reg.c colorize_reg.c \
+ colormask_reg.c colormorph_reg.c colorquant_reg.c \
+ colorseg_reg.c colorspace_reg.c \
+ compare_reg.c compfilter_reg.c \
+ conncomp_reg.c conversion_reg.c convolve_reg.c \
+ crop_reg.c dewarp_reg.c distance_reg.c \
+ dither_reg.c dna_reg.c \
+ dwamorph1_reg.c dwamorph2_reg.c \
+ edge_reg.c encoding_reg.c enhance_reg.c equal_reg.c \
+ expand_reg.c extrema_reg.c falsecolor_reg.c \
+ fhmtauto_reg.c files_reg.c \
+ findcorners_reg.c findpattern_reg.c \
+ flipdetect_reg.c fmorphauto_reg.c \
+ fpix1_reg.c fpix2_reg.c \
+ genfonts_reg.c gifio_reg.c \
+ grayfill_reg.c graymorph1_reg.c \
+ graymorph2_reg.c grayquant_reg.c \
+ hardlight_reg.c hash_reg.c heap_reg.c \
+ insert_reg.c ioformats_reg.c \
+ iomisc_reg.c italic_reg.c \
+ jbclass_reg.c jp2kio_reg.c jpegio_reg.c kernel_reg.c \
+ label_reg.c lineremoval_reg.c locminmax_reg.c \
+ logicops_reg.c lowaccess_reg.c lowsat_reg.c \
+ maze_reg.c morphseq_reg.c mtiff_reg.c \
+ multitype_reg.c nearline_reg.c newspaper_reg.c \
+ numa1_reg.c numa2_reg.c numa3_reg.c \
+ overlap_reg.c pageseg_reg.c paint_reg.c paintmask_reg.c \
+ pdfio1_reg.c pdfio2_reg.c pdfseg_reg.c \
+ pixa1_reg.c pixa2_reg.c \
+ pixadisp_reg.c pixalloc_reg.c \
+ pixcomp_reg.c pixmem_reg.c \
+ pixserial_reg.c pixtile_reg.c \
+ pngio_reg.c pnmio_reg.c \
+ projection_reg.c projective_reg.c \
+ psio_reg.c psioseg_reg.c \
+ pta_reg.c ptra1_reg.c ptra2_reg.c quadtree_reg.c \
+ rankbin_reg.c rankhisto_reg.c rank_reg.c \
+ rasteropip_reg.c rasterop_reg.c rectangle_reg.c \
+ rotate1_reg.c rotate2_reg.c rotateorth_reg.c \
+ scale_reg.c seedspread_reg.c selio_reg.c \
+ shear1_reg.c shear2_reg.c skew_reg.c \
+ smallpix_reg.c smoothedge_reg.c \
+ speckle_reg.c splitcomp_reg.c \
+ string_reg.c subpixel_reg.c \
+ texturefill_reg.c threshnorm_reg.c \
+ translate_reg.c warper_reg.c \
+ watershed_reg.c webpanimio_reg.c webpio_reg.c \
+ wordboxes_reg.c writetext_reg.c xformbox_reg.c \
+ adaptmap_dark.c arabic_lines.c arithtest.c \
+ autogentest1.c autogentest2.c barcodetest.c \
+ binarizefiles.c binarize_set.c bincompare.c \
+ blendcmaptest.c buffertest.c \
+ ccbordtest.c cctest1.c \
+ cleanpdf.c colorsegtest.c comparepages.c \
+ comparepixa.c comparetest.c \
+ concatpdf.c contrasttest.c \
+ convertfilestopdf.c convertfilestops.c \
+ convertformat.c \
+ convertsegfilestopdf.c convertsegfilestops.c \
+ converttogray.c converttopdf.c converttops.c \
+ cornertest.c corrupttest.c \
+ croptext.c deskew_it.c \
+ dewarprules.c dewarptest1.c dewarptest2.c \
+ dewarptest3.c dewarptest4.c dewarptest5.c \
+ digitprep1.c displayboxa.c displayboxes_on_pixa.c \
+ displaypix.c displaypixa.c dwalineargen.c \
+ fcombautogen.c fhmtautogen.c \
+ fileinfo.c findbinding.c find_colorregions.c \
+ findpattern1.c findpattern2.c findpattern3.c \
+ fmorphautogen.c \
+ fpixcontours.c gammatest.c \
+ graphicstest.c graymorphtest.c \
+ histoduptest.c histotest.c \
+ htmlviewer.c imagetops.c \
+ jbcorrelation.c jbrankhaus.c jbwords.c \
+ lightcolortest.c listtest.c \
+ livre_adapt.c livre_hmt.c livre_makefigs.c livre_orient.c \
+ livre_pageseg.c livre_seedgen.c livre_tophat.c \
+ maketile.c maptest.c messagetest.c misctest1.c \
+ modifyhuesat.c morphtest1.c \
+ numaranktest.c otsutest1.c otsutest2.c \
+ pagesegtest1.c pagesegtest2.c \
+ partifytest.c partitiontest.c percolatetest.c \
+ pixaatest.c pixafileinfo.c plottest.c \
+ printimage.c printsplitimage.c printtiff.c \
+ rasteroptest.c rbtreetest.c \
+ recog_bootnum1.c recog_bootnum2.c recog_bootnum3.c \
+ recogsort.c recogtest1.c recogtest2.c recogtest3.c \
+ recogtest4.c recogtest5.c recogtest6.c recogtest7.c \
+ reducetest.c removecmap.c \
+ renderfonts.c replacebytes.c \
+ rotatefastalt.c rotate_it.c \
+ rotateorthtest1.c rotatetest1.c \
+ runlengthtest.c scaleandtile.c \
+ scale_it.c scaletest1.c scaletest2.c \
+ seedfilltest.c settest.c sharptest.c \
+ sheartest.c showedges.c \
+ skewtest.c sorttest.c splitimage2pdf.c \
+ sudokutest.c textorient.c \
+ tiffpdftest.c trctest.c \
+ underlinetest.c warpertest.c wordsinorder.c \
+ writemtiff.c xtractprotos.c yuvtest.c
+
+# Remove the .o files from these on clean
+SRC2 = autogen.137.c dwalinear.3.c dwalinearlow.3.c
+
+######################################################################
+
+all: $(SRC:%.c=%)
+
+# Jan 2018 (even smaller set of nine utility programs)
+#debian: convertfilestopdf convertfilestops convertformat \
+# convertsegfilestopdf convertsegfilestops \
+# converttopdf converttops fileinfo xtractprotos
+
+# Jan 2014 (minimal set of utility programs)
+#debian: convertfilestopdf convertfilestops convertformat \
+# convertsegfilestopdf convertsegfilestops \
+# converttopdf converttops fileinfo \
+# printimage printsplitimage printtiff \
+# splitimage2pdf xtractprotos
+
+# Jan 2014 (could have been in Jan 2014 debian, but left off)
+# grayfill_reg histotest_reg sudokutest watershedtest
+
+# Jan 2012
+#debian: binarize_reg binmorph2_reg ccthin2_reg colorquant_reg \
+# colorseg_reg convolve_reg dewarp_reg \
+# dwamorph1_reg dwamorph2_reg \
+# distance_reg enhance_reg ioformats_reg \
+# maze_reg paintmask_reg \
+# rotate1_reg rotate2_reg scale_reg \
+# seedspread_reg splitcomp_reg threshnorm_reg \
+# warper_reg convertfilestopdf convertfilestops \
+# convertjpegfilestopdf converttops dewarptest1 \
+# fcombautogen fhmtautogen fileinfo \
+# fmorphautogen pdfiotest \
+# printimage printsplitimage splitimage2pdf \
+# sudokutest watershedtest xtractprotos
+
+
+# Jan 2011
+#debian: binarize_reg cctest1 ccthin1_reg \
+# colormorphtest colorquant_reg colorspacetest \
+# comparetest convertfilestopdf convertfilestops \
+# convertformat converttops dewarp_reg \
+# distance_reg dithertest fileinfo \
+# flipdetect_reg fmorphauto_reg gammatest \
+# grayfill_reg graymorph1_reg grayquant_reg \
+# hardlight_reg ioformats_reg jbcorrelation \
+# kernel_reg lineremoval maze_reg \
+# pagesegtest1 paint_reg paintmask_reg \
+# printimage printsplitimage printtiff \
+# rank_reg ranktest scale_reg \
+# skewtest splitcomp_reg warper_reg \
+# watershedtest xtractprotos
+
+######################################################################
+
+adaptmap_reg: adaptmap_reg.o $(LEPTLIB)
+ $(CC) -o adaptmap_reg adaptmap_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+adaptnorm_reg: adaptnorm_reg.o $(LEPTLIB)
+ $(CC) -o adaptnorm_reg adaptnorm_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+affine_reg: affine_reg.o $(LEPTLIB)
+ $(CC) -o affine_reg affine_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+alltests_reg: alltests_reg.o $(LEPTLIB)
+ $(CC) -o alltests_reg alltests_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+alphaops_reg: alphaops_reg.o $(LEPTLIB)
+ $(CC) -o alphaops_reg alphaops_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+alphaxform_reg: alphaxform_reg.o $(LEPTLIB)
+ $(CC) -o alphaxform_reg alphaxform_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+baseline_reg: baseline_reg.o $(LEPTLIB)
+ $(CC) -o baseline_reg baseline_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+bilateral1_reg: bilateral1_reg.o $(LEPTLIB)
+ $(CC) -o bilateral1_reg bilateral1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+bilateral2_reg: bilateral2_reg.o $(LEPTLIB)
+ $(CC) -o bilateral2_reg bilateral2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+bilinear_reg: bilinear_reg.o $(LEPTLIB)
+ $(CC) -o bilinear_reg bilinear_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+binarize_reg: binarize_reg.o $(LEPTLIB)
+ $(CC) -o binarize_reg binarize_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+binmorph1_reg: binmorph1_reg.o $(LEPTLIB)
+ $(CC) -o binmorph1_reg binmorph1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+binmorph2_reg: binmorph2_reg.o $(LEPTLIB)
+ $(CC) -o binmorph2_reg binmorph2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+binmorph3_reg: binmorph3_reg.o $(LEPTLIB)
+ $(CC) -o binmorph3_reg binmorph3_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+binmorph4_reg: binmorph4_reg.o $(LEPTLIB)
+ $(CC) -o binmorph4_reg binmorph4_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+binmorph5_reg: binmorph5_reg.o $(LEPTLIB)
+ $(CC) -o binmorph5_reg binmorph5_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+binmorph6_reg: binmorph6_reg.o $(LEPTLIB)
+ $(CC) -o binmorph6_reg binmorph6_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+blackwhite_reg: blackwhite_reg.o $(LEPTLIB)
+ $(CC) -o blackwhite_reg blackwhite_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+blend1_reg: blend1_reg.o $(LEPTLIB)
+ $(CC) -o blend1_reg blend1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+blend2_reg: blend2_reg.o $(LEPTLIB)
+ $(CC) -o blend2_reg blend2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+blend3_reg: blend3_reg.o $(LEPTLIB)
+ $(CC) -o blend3_reg blend3_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+blend4_reg: blend4_reg.o $(LEPTLIB)
+ $(CC) -o blend4_reg blend4_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+blend5_reg: blend5_reg.o $(LEPTLIB)
+ $(CC) -o blend5_reg blend5_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+boxa1_reg: boxa1_reg.o $(LEPTLIB)
+ $(CC) -o boxa1_reg boxa1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+boxa2_reg: boxa2_reg.o $(LEPTLIB)
+ $(CC) -o boxa2_reg boxa2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+boxa3_reg: boxa3_reg.o $(LEPTLIB)
+ $(CC) -o boxa3_reg boxa3_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+boxa4_reg: boxa4_reg.o $(LEPTLIB)
+ $(CC) -o boxa4_reg boxa4_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+bytea_reg: bytea_reg.o $(LEPTLIB)
+ $(CC) -o bytea_reg bytea_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+ccbord_reg: ccbord_reg.o $(LEPTLIB)
+ $(CC) -o ccbord_reg ccbord_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+ccthin1_reg: ccthin1_reg.o $(LEPTLIB)
+ $(CC) -o ccthin1_reg ccthin1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+ccthin2_reg: ccthin2_reg.o $(LEPTLIB)
+ $(CC) -o ccthin2_reg ccthin2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+checkerboard_reg: checkerboard_reg.o $(LEPTLIB)
+ $(CC) -o checkerboard_reg checkerboard_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+circle_reg: circle_reg.o $(LEPTLIB)
+ $(CC) -o circle_reg circle_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+cmapquant_reg: cmapquant_reg.o $(LEPTLIB)
+ $(CC) -o cmapquant_reg cmapquant_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+colorcontent_reg: colorcontent_reg.o $(LEPTLIB)
+ $(CC) -o colorcontent_reg colorcontent_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+colorfill_reg: colorfill_reg.o $(LEPTLIB)
+ $(CC) -o colorfill_reg colorfill_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+coloring_reg: coloring_reg.o $(LEPTLIB)
+ $(CC) -o coloring_reg coloring_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+colorize_reg: colorize_reg.o $(LEPTLIB)
+ $(CC) -o colorize_reg colorize_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+colormask_reg: colormask_reg.o $(LEPTLIB)
+ $(CC) -o colormask_reg colormask_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+colormorph_reg: colormorph_reg.o $(LEPTLIB)
+ $(CC) -o colormorph_reg colormorph_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+colorquant_reg: colorquant_reg.o $(LEPTLIB)
+ $(CC) -o colorquant_reg colorquant_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+colorseg_reg: colorseg_reg.o $(LEPTLIB)
+ $(CC) -o colorseg_reg colorseg_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+colorspace_reg: colorspace_reg.o $(LEPTLIB)
+ $(CC) -o colorspace_reg colorspace_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+compare_reg: compare_reg.o $(LEPTLIB)
+ $(CC) -o compare_reg compare_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+compfilter_reg: compfilter_reg.o $(LEPTLIB)
+ $(CC) -o compfilter_reg compfilter_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+conncomp_reg: conncomp_reg.o $(LEPTLIB)
+ $(CC) -o conncomp_reg conncomp_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+conversion_reg: conversion_reg.o $(LEPTLIB)
+ $(CC) -o conversion_reg conversion_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+convolve_reg: convolve_reg.o $(LEPTLIB)
+ $(CC) -o convolve_reg convolve_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+crop_reg: crop_reg.o $(LEPTLIB)
+ $(CC) -o crop_reg crop_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+dewarp_reg: dewarp_reg.o $(LEPTLIB)
+ $(CC) -o dewarp_reg dewarp_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+distance_reg: distance_reg.o $(LEPTLIB)
+ $(CC) -o distance_reg distance_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+dither_reg: dither_reg.o $(LEPTLIB)
+ $(CC) -o dither_reg dither_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+dna_reg: dna_reg.o $(LEPTLIB)
+ $(CC) -o dna_reg dna_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+dwamorph1_reg: dwamorph1_reg.o dwalinear.3.o dwalinearlow.3.o $(LEPTLIB)
+ $(CC) -o dwamorph1_reg dwamorph1_reg.o dwalinear.3.o dwalinearlow.3.o $(ALL_LIBS) $(EXTRALIBS)
+
+dwamorph2_reg: dwamorph2_reg.o dwalinear.3.o dwalinearlow.3.o $(LEPTLIB)
+ $(CC) -o dwamorph2_reg dwamorph2_reg.o dwalinear.3.o dwalinearlow.3.o $(ALL_LIBS) $(EXTRALIBS)
+
+edge_reg: edge_reg.o $(LEPTLIB)
+ $(CC) -o edge_reg edge_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+encoding_reg: encoding_reg.o $(LEPTLIB)
+ $(CC) -o encoding_reg encoding_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+enhance_reg: enhance_reg.o $(LEPTLIB)
+ $(CC) -o enhance_reg enhance_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+equal_reg: equal_reg.o $(LEPTLIB)
+ $(CC) -o equal_reg equal_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+expand_reg: expand_reg.o $(LEPTLIB)
+ $(CC) -o expand_reg expand_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+extrema_reg: extrema_reg.o $(LEPTLIB)
+ $(CC) -o extrema_reg extrema_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+falsecolor_reg: falsecolor_reg.o $(LEPTLIB)
+ $(CC) -o falsecolor_reg falsecolor_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+fhmtauto_reg: fhmtauto_reg.o $(LEPTLIB)
+ $(CC) -o fhmtauto_reg fhmtauto_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+files_reg: files_reg.o $(LEPTLIB)
+ $(CC) -o files_reg files_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+findcorners_reg: findcorners_reg.o $(LEPTLIB)
+ $(CC) -o findcorners_reg findcorners_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+findpattern_reg: findpattern_reg.o $(LEPTLIB)
+ $(CC) -o findpattern_reg findpattern_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+flipdetect_reg: flipdetect_reg.o $(LEPTLIB)
+ $(CC) -o flipdetect_reg flipdetect_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+fmorphauto_reg: fmorphauto_reg.o $(LEPTLIB)
+ $(CC) -o fmorphauto_reg fmorphauto_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+fpix1_reg: fpix1_reg.o $(LEPTLIB)
+ $(CC) -o fpix1_reg fpix1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+fpix2_reg: fpix2_reg.o $(LEPTLIB)
+ $(CC) -o fpix2_reg fpix2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+genfonts_reg: genfonts_reg.o $(LEPTLIB)
+ $(CC) -o genfonts_reg genfonts_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+gifio_reg: gifio_reg.o $(LEPTLIB)
+ $(CC) -o gifio_reg gifio_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+grayfill_reg: grayfill_reg.o $(LEPTLIB)
+ $(CC) -o grayfill_reg grayfill_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+graymorph1_reg: graymorph1_reg.o $(LEPTLIB)
+ $(CC) -o graymorph1_reg graymorph1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+graymorph2_reg: graymorph2_reg.o $(LEPTLIB)
+ $(CC) -o graymorph2_reg graymorph2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+grayquant_reg: grayquant_reg.o $(LEPTLIB)
+ $(CC) -o grayquant_reg grayquant_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+hardlight_reg: hardlight_reg.o $(LEPTLIB)
+ $(CC) -o hardlight_reg hardlight_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+hash_reg: hash_reg.o $(LEPTLIB)
+ $(CC) -o hash_reg hash_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+heap_reg: heap_reg.o $(LEPTLIB)
+ $(CC) -o heap_reg heap_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+insert_reg: insert_reg.o $(LEPTLIB)
+ $(CC) -o insert_reg insert_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+ioformats_reg: ioformats_reg.o $(LEPTLIB)
+ $(CC) -o ioformats_reg ioformats_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+iomisc_reg: iomisc_reg.o $(LEPTLIB)
+ $(CC) -o iomisc_reg iomisc_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+italic_reg: italic_reg.o $(LEPTLIB)
+ $(CC) -o italic_reg italic_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+jbclass_reg: jbclass_reg.o $(LEPTLIB)
+ $(CC) -o jbclass_reg jbclass_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+jp2kio_reg: jp2kio_reg.o $(LEPTLIB)
+ $(CC) -o jp2kio_reg jp2kio_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+jpegio_reg: jpegio_reg.o $(LEPTLIB)
+ $(CC) -o jpegio_reg jpegio_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+kernel_reg: kernel_reg.o $(LEPTLIB)
+ $(CC) -o kernel_reg kernel_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+label_reg: label_reg.o $(LEPTLIB)
+ $(CC) -o label_reg label_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+lineremoval_reg: lineremoval_reg.o $(LEPTLIB)
+ $(CC) -o lineremoval_reg lineremoval_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+locminmax_reg: locminmax_reg.o $(LEPTLIB)
+ $(CC) -o locminmax_reg locminmax_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+logicops_reg: logicops_reg.o $(LEPTLIB)
+ $(CC) -o logicops_reg logicops_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+lowsat_reg: lowsat_reg.o $(LEPTLIB)
+ $(CC) -o lowsat_reg lowsat_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+lowaccess_reg: lowaccess_reg.o $(LEPTLIB)
+ $(CC) -o lowaccess_reg lowaccess_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+maze_reg: maze_reg.o $(LEPTLIB)
+ $(CC) -o maze_reg maze_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+morphseq_reg: morphseq_reg.o $(LEPTLIB)
+ $(CC) -o morphseq_reg morphseq_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+mtiff_reg: mtiff_reg.o $(LEPTLIB)
+ $(CC) -o mtiff_reg mtiff_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+multitype_reg: multitype_reg.o $(LEPTLIB)
+ $(CC) -o multitype_reg multitype_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+nearline_reg: nearline_reg.o $(LEPTLIB)
+ $(CC) -o nearline_reg nearline_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+newspaper_reg: newspaper_reg.o $(LEPTLIB)
+ $(CC) -o newspaper_reg newspaper_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+numa1_reg: numa1_reg.o $(LEPTLIB)
+ $(CC) -o numa1_reg numa1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+numa2_reg: numa2_reg.o $(LEPTLIB)
+ $(CC) -o numa2_reg numa2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+numa3_reg: numa3_reg.o $(LEPTLIB)
+ $(CC) -o numa3_reg numa3_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+overlap_reg: overlap_reg.o $(LEPTLIB)
+ $(CC) -o overlap_reg overlap_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pageseg_reg: pageseg_reg.o $(LEPTLIB)
+ $(CC) -o pageseg_reg pageseg_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+paint_reg: paint_reg.o $(LEPTLIB)
+ $(CC) -o paint_reg paint_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+paintmask_reg: paintmask_reg.o $(LEPTLIB)
+ $(CC) -o paintmask_reg paintmask_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pdfio1_reg: pdfio1_reg.o $(LEPTLIB)
+ $(CC) -o pdfio1_reg pdfio1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pdfio2_reg: pdfio2_reg.o $(LEPTLIB)
+ $(CC) -o pdfio2_reg pdfio2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pdfseg_reg: pdfseg_reg.o $(LEPTLIB)
+ $(CC) -o pdfseg_reg pdfseg_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pixa1_reg: pixa1_reg.o $(LEPTLIB)
+ $(CC) -o pixa1_reg pixa1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pixa2_reg: pixa2_reg.o $(LEPTLIB)
+ $(CC) -o pixa2_reg pixa2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pixadisp_reg: pixadisp_reg.o $(LEPTLIB)
+ $(CC) -o pixadisp_reg pixadisp_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pixalloc_reg: pixalloc_reg.o $(LEPTLIB)
+ $(CC) -o pixalloc_reg pixalloc_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pixcomp_reg: pixcomp_reg.o $(LEPTLIB)
+ $(CC) -o pixcomp_reg pixcomp_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pixmem_reg: pixmem_reg.o $(LEPTLIB)
+ $(CC) -o pixmem_reg pixmem_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pixserial_reg: pixserial_reg.o $(LEPTLIB)
+ $(CC) -o pixserial_reg pixserial_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pixtile_reg: pixtile_reg.o $(LEPTLIB)
+ $(CC) -o pixtile_reg pixtile_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pngio_reg: pngio_reg.o $(LEPTLIB)
+ $(CC) -o pngio_reg pngio_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pnmio_reg: pnmio_reg.o $(LEPTLIB)
+ $(CC) -o pnmio_reg pnmio_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+projection_reg: projection_reg.o $(LEPTLIB)
+ $(CC) -o projection_reg projection_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+projective_reg: projective_reg.o $(LEPTLIB)
+ $(CC) -o projective_reg projective_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+psio_reg: psio_reg.o $(LEPTLIB)
+ $(CC) -o psio_reg psio_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+psioseg_reg: psioseg_reg.o $(LEPTLIB)
+ $(CC) -o psioseg_reg psioseg_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+pta_reg: pta_reg.o $(LEPTLIB)
+ $(CC) -o pta_reg pta_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+ptra1_reg: ptra1_reg.o $(LEPTLIB)
+ $(CC) -o ptra1_reg ptra1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+ptra2_reg: ptra2_reg.o $(LEPTLIB)
+ $(CC) -o ptra2_reg ptra2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+quadtree_reg: quadtree_reg.o $(LEPTLIB)
+ $(CC) -o quadtree_reg quadtree_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+rank_reg: rank_reg.o $(LEPTLIB)
+ $(CC) -o rank_reg rank_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+rankbin_reg: rankbin_reg.o $(LEPTLIB)
+ $(CC) -o rankbin_reg rankbin_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+rankhisto_reg: rankhisto_reg.o $(LEPTLIB)
+ $(CC) -o rankhisto_reg rankhisto_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+rasterop_reg: rasterop_reg.o $(LEPTLIB)
+ $(CC) -o rasterop_reg rasterop_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+rasteropip_reg: rasteropip_reg.o $(LEPTLIB)
+ $(CC) -o rasteropip_reg rasteropip_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+rectangle_reg: rectangle_reg.o $(LEPTLIB)
+ $(CC) -o rectangle_reg rectangle_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+rotate1_reg: rotate1_reg.o $(LEPTLIB)
+ $(CC) -o rotate1_reg rotate1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+rotate2_reg: rotate2_reg.o $(LEPTLIB)
+ $(CC) -o rotate2_reg rotate2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+rotateorth_reg: rotateorth_reg.o $(LEPTLIB)
+ $(CC) -o rotateorth_reg rotateorth_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+scale_reg: scale_reg.o $(LEPTLIB)
+ $(CC) -o scale_reg scale_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+seedspread_reg: seedspread_reg.o $(LEPTLIB)
+ $(CC) -o seedspread_reg seedspread_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+selio_reg: selio_reg.o $(LEPTLIB)
+ $(CC) -o selio_reg selio_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+shear1_reg: shear1_reg.o $(LEPTLIB)
+ $(CC) -o shear1_reg shear1_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+shear2_reg: shear2_reg.o $(LEPTLIB)
+ $(CC) -o shear2_reg shear2_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+skew_reg: skew_reg.o $(LEPTLIB)
+ $(CC) -o skew_reg skew_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+smallpix_reg: smallpix_reg.o $(LEPTLIB)
+ $(CC) -o smallpix_reg smallpix_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+smoothedge_reg: smoothedge_reg.o $(LEPTLIB)
+ $(CC) -o smoothedge_reg smoothedge_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+speckle_reg: speckle_reg.o $(LEPTLIB)
+ $(CC) -o speckle_reg speckle_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+splitcomp_reg: splitcomp_reg.o $(LEPTLIB)
+ $(CC) -o splitcomp_reg splitcomp_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+string_reg: string_reg.o $(LEPTLIB)
+ $(CC) -o string_reg string_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+subpixel_reg: subpixel_reg.o $(LEPTLIB)
+ $(CC) -o subpixel_reg subpixel_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+texturefill_reg: texturefill_reg.o $(LEPTLIB)
+ $(CC) -o texturefill_reg texturefill_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+threshnorm_reg: threshnorm_reg.o $(LEPTLIB)
+ $(CC) -o threshnorm_reg threshnorm_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+translate_reg: translate_reg.o $(LEPTLIB)
+ $(CC) -o translate_reg translate_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+warper_reg: warper_reg.o $(LEPTLIB)
+ $(CC) -o warper_reg warper_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+watershed_reg: watershed_reg.o $(LEPTLIB)
+ $(CC) -o watershed_reg watershed_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+webpanimio_reg: webpanimio_reg.o $(LEPTLIB)
+ $(CC) -o webpanimio_reg webpanimio_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+webpio_reg: webpio_reg.o $(LEPTLIB)
+ $(CC) -o webpio_reg webpio_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+wordboxes_reg: wordboxes_reg.o $(LEPTLIB)
+ $(CC) -o wordboxes_reg wordboxes_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+writetext_reg: writetext_reg.o $(LEPTLIB)
+ $(CC) -o writetext_reg writetext_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+xformbox_reg: xformbox_reg.o $(LEPTLIB)
+ $(CC) -o xformbox_reg xformbox_reg.o $(ALL_LIBS) $(EXTRALIBS)
+
+adaptmap_dark: adaptmap_dark.o $(LEPTLIB)
+ $(CC) -o adaptmap_dark adaptmap_dark.o $(ALL_LIBS) $(EXTRALIBS)
+
+arabic_lines: arabic_lines.o $(LEPTLIB)
+ $(CC) -o arabic_lines arabic_lines.o $(ALL_LIBS) $(EXTRALIBS)
+
+arithtest: arithtest.o $(LEPTLIB)
+ $(CC) -o arithtest arithtest.o $(ALL_LIBS) $(EXTRALIBS)
+
+autogentest1: autogentest1.o $(LEPTLIB)
+ $(CC) -o autogentest1 autogentest1.o $(ALL_LIBS) $(EXTRALIBS)
+
+autogentest2: autogentest2.o autogen.137.o $(LEPTLIB)
+ $(CC) -o autogentest2 autogentest2.o autogen.137.o $(ALL_LIBS) $(EXTRALIBS)
+
+barcodetest: barcodetest.o $(LEPTLIB)
+ $(CC) -o barcodetest barcodetest.o $(ALL_LIBS) $(EXTRALIBS)
+
+binarizefiles: binarizefiles.o $(LEPTLIB)
+ $(CC) -o binarizefiles binarizefiles.o $(ALL_LIBS) $(EXTRALIBS)
+
+binarize_set: binarize_set.o $(LEPTLIB)
+ $(CC) -o binarize_set binarize_set.o $(ALL_LIBS) $(EXTRALIBS)
+
+bincompare: bincompare.o $(LEPTLIB)
+ $(CC) -o bincompare bincompare.o $(ALL_LIBS) $(EXTRALIBS)
+
+blendcmaptest: blendcmaptest.o $(LEPTLIB)
+ $(CC) -o blendcmaptest blendcmaptest.o $(ALL_LIBS) $(EXTRALIBS)
+
+buffertest: buffertest.o $(LEPTLIB)
+ $(CC) -o buffertest buffertest.o $(ALL_LIBS) $(EXTRALIBS)
+
+ccbordtest: ccbordtest.o $(LEPTLIB)
+ $(CC) -o ccbordtest ccbordtest.o $(ALL_LIBS) $(EXTRALIBS)
+
+cctest1: cctest1.o $(LEPTLIB)
+ $(CC) -o cctest1 cctest1.o $(ALL_LIBS) $(EXTRALIBS)
+
+cleanpdf: cleanpdf.o $(LEPTLIB)
+ $(CC) -o cleanpdf cleanpdf.o $(ALL_LIBS) $(EXTRALIBS)
+
+colorsegtest: colorsegtest.o $(LEPTLIB)
+ $(CC) -o colorsegtest colorsegtest.o $(ALL_LIBS) $(EXTRALIBS)
+
+comparepages: comparepages.o $(LEPTLIB)
+ $(CC) -o comparepages comparepages.o $(ALL_LIBS) $(EXTRALIBS)
+
+comparepixa: comparepixa.o $(LEPTLIB)
+ $(CC) -o comparepixa comparepixa.o $(ALL_LIBS) $(EXTRALIBS)
+
+comparetest: comparetest.o $(LEPTLIB)
+ $(CC) -o comparetest comparetest.o $(ALL_LIBS) $(EXTRALIBS)
+
+concatpdf: concatpdf.o $(LEPTLIB)
+ $(CC) -o concatpdf concatpdf.o $(ALL_LIBS) $(EXTRALIBS)
+
+contrasttest: contrasttest.o $(LEPTLIB)
+ $(CC) -o contrasttest contrasttest.o $(ALL_LIBS) $(EXTRALIBS)
+
+convertfilestopdf: convertfilestopdf.o $(LEPTLIB)
+ $(CC) -o convertfilestopdf convertfilestopdf.o $(ALL_LIBS) $(EXTRALIBS)
+
+convertfilestops: convertfilestops.o $(LEPTLIB)
+ $(CC) -o convertfilestops convertfilestops.o $(ALL_LIBS) $(EXTRALIBS)
+
+convertformat: convertformat.o $(LEPTLIB)
+ $(CC) -o convertformat convertformat.o $(ALL_LIBS) $(EXTRALIBS)
+
+convertsegfilestopdf: convertsegfilestopdf.o $(LEPTLIB)
+ $(CC) -o convertsegfilestopdf convertsegfilestopdf.o $(ALL_LIBS) $(EXTRALIBS)
+
+convertsegfilestops: convertsegfilestops.o $(LEPTLIB)
+ $(CC) -o convertsegfilestops convertsegfilestops.o $(ALL_LIBS) $(EXTRALIBS)
+
+converttogray: converttogray.o $(LEPTLIB)
+ $(CC) -o converttogray converttogray.o $(ALL_LIBS) $(EXTRALIBS)
+
+converttopdf: converttopdf.o $(LEPTLIB)
+ $(CC) -o converttopdf converttopdf.o $(ALL_LIBS) $(EXTRALIBS)
+
+converttops: converttops.o $(LEPTLIB)
+ $(CC) -o converttops converttops.o $(ALL_LIBS) $(EXTRALIBS)
+
+cornertest: cornertest.o $(LEPTLIB)
+ $(CC) -o cornertest cornertest.o $(ALL_LIBS) $(EXTRALIBS)
+
+corrupttest: corrupttest.o $(LEPTLIB)
+ $(CC) -o corrupttest corrupttest.o $(ALL_LIBS) $(EXTRALIBS)
+
+croptext: croptext.o $(LEPTLIB)
+ $(CC) -o croptext croptext.o $(ALL_LIBS) $(EXTRALIBS)
+
+deskew_it: deskew_it.o $(LEPTLIB)
+ $(CC) -o deskew_it deskew_it.o $(ALL_LIBS) $(EXTRALIBS)
+
+dewarprules: dewarprules.o $(LEPTLIB)
+ $(CC) -o dewarprules dewarprules.o $(ALL_LIBS) $(EXTRALIBS)
+
+dewarptest1: dewarptest1.o $(LEPTLIB)
+ $(CC) -o dewarptest1 dewarptest1.o $(ALL_LIBS) $(EXTRALIBS)
+
+dewarptest2: dewarptest2.o $(LEPTLIB)
+ $(CC) -o dewarptest2 dewarptest2.o $(ALL_LIBS) $(EXTRALIBS)
+
+dewarptest3: dewarptest3.o $(LEPTLIB)
+ $(CC) -o dewarptest3 dewarptest3.o $(ALL_LIBS) $(EXTRALIBS)
+
+dewarptest4: dewarptest4.o $(LEPTLIB)
+ $(CC) -o dewarptest4 dewarptest4.o $(ALL_LIBS) $(EXTRALIBS)
+
+dewarptest5: dewarptest5.o $(LEPTLIB)
+ $(CC) -o dewarptest5 dewarptest5.o $(ALL_LIBS) $(EXTRALIBS)
+
+digitprep1: digitprep1.o $(LEPTLIB)
+ $(CC) -o digitprep1 digitprep1.o $(ALL_LIBS) $(EXTRALIBS)
+
+displayboxa: displayboxa.o $(LEPTLIB)
+ $(CC) -o displayboxa displayboxa.o $(ALL_LIBS) $(EXTRALIBS)
+
+displayboxes_on_pixa: displayboxes_on_pixa.o $(LEPTLIB)
+ $(CC) -o displayboxes_on_pixa displayboxes_on_pixa.o $(ALL_LIBS) $(EXTRALIBS)
+
+displaypix: displaypix.o $(LEPTLIB)
+ $(CC) -o displaypix displaypix.o $(ALL_LIBS) $(EXTRALIBS)
+
+displaypixa: displaypixa.o $(LEPTLIB)
+ $(CC) -o displaypixa displaypixa.o $(ALL_LIBS) $(EXTRALIBS)
+
+dwalineargen: dwalineargen.o $(LEPTLIB)
+ $(CC) -o dwalineargen dwalineargen.o $(ALL_LIBS) $(EXTRALIBS)
+
+fhmtautogen: fhmtautogen.o $(LEPTLIB)
+ $(CC) -o fhmtautogen fhmtautogen.o $(ALL_LIBS) $(EXTRALIBS)
+
+fileinfo: fileinfo.o $(LEPTLIB)
+ $(CC) -o fileinfo fileinfo.o $(ALL_LIBS) $(EXTRALIBS)
+
+findbinding: findbinding.o $(LEPTLIB)
+ $(CC) -o findbinding findbinding.o $(ALL_LIBS) $(EXTRALIBS)
+
+find_colorregions: find_colorregions.o $(LEPTLIB)
+ $(CC) -o find_colorregions find_colorregions.o $(ALL_LIBS) $(EXTRALIBS)
+
+findpattern1: findpattern1.o $(LEPTLIB)
+ $(CC) -o findpattern1 findpattern1.o $(ALL_LIBS) $(EXTRALIBS)
+
+findpattern2: findpattern2.o $(LEPTLIB)
+ $(CC) -o findpattern2 findpattern2.o $(ALL_LIBS) $(EXTRALIBS)
+
+findpattern3: findpattern3.o $(LEPTLIB)
+ $(CC) -o findpattern3 findpattern3.o $(ALL_LIBS) $(EXTRALIBS)
+
+fcombautogen: fcombautogen.o $(LEPTLIB)
+ $(CC) -o fcombautogen fcombautogen.o $(ALL_LIBS) $(EXTRALIBS)
+
+fmorphautogen: fmorphautogen.o $(LEPTLIB)
+ $(CC) -o fmorphautogen fmorphautogen.o $(ALL_LIBS) $(EXTRALIBS)
+
+fpixcontours: fpixcontours.o $(LEPTLIB)
+ $(CC) -o fpixcontours fpixcontours.o $(ALL_LIBS) $(EXTRALIBS)
+
+gammatest: gammatest.o $(LEPTLIB)
+ $(CC) -o gammatest gammatest.o $(ALL_LIBS) $(EXTRALIBS)
+
+graphicstest: graphicstest.o $(LEPTLIB)
+ $(CC) -o graphicstest graphicstest.o $(ALL_LIBS) $(EXTRALIBS)
+
+graymorphtest: graymorphtest.o $(LEPTLIB)
+ $(CC) -o graymorphtest graymorphtest.o $(ALL_LIBS) $(EXTRALIBS)
+
+histoduptest: histoduptest.o $(LEPTLIB)
+ $(CC) -o histoduptest histoduptest.o $(ALL_LIBS) $(EXTRALIBS)
+
+histotest: histotest.o $(LEPTLIB)
+ $(CC) -o histotest histotest.o $(ALL_LIBS) $(EXTRALIBS)
+
+htmlviewer: htmlviewer.o $(LEPTLIB)
+ $(CC) -o htmlviewer htmlviewer.o $(ALL_LIBS) $(EXTRALIBS)
+
+imagetops: imagetops.o $(LEPTLIB)
+ $(CC) -o imagetops imagetops.o $(ALL_LIBS) $(EXTRALIBS)
+
+jbcorrelation: jbcorrelation.o $(LEPTLIB)
+ $(CC) -o jbcorrelation jbcorrelation.o $(ALL_LIBS) $(EXTRALIBS)
+
+jbrankhaus: jbrankhaus.o $(LEPTLIB)
+ $(CC) -o jbrankhaus jbrankhaus.o $(ALL_LIBS) $(EXTRALIBS)
+
+jbwords: jbwords.o $(LEPTLIB)
+ $(CC) -o jbwords jbwords.o $(ALL_LIBS) $(EXTRALIBS)
+
+lightcolortest: lightcolortest.o $(LEPTLIB)
+ $(CC) -o lightcolortest lightcolortest.o $(ALL_LIBS) $(EXTRALIBS)
+
+listtest: listtest.o $(LEPTLIB)
+ $(CC) -o listtest listtest.o $(ALL_LIBS) $(EXTRALIBS)
+
+livre_adapt: livre_adapt.o $(LEPTLIB)
+ $(CC) -o livre_adapt livre_adapt.o $(ALL_LIBS) $(EXTRALIBS)
+
+livre_hmt: livre_hmt.o $(LEPTLIB)
+ $(CC) -o livre_hmt livre_hmt.o $(ALL_LIBS) $(EXTRALIBS)
+
+livre_makefigs: livre_makefigs.o $(LEPTLIB)
+ $(CC) -o livre_makefigs livre_makefigs.o $(ALL_LIBS) $(EXTRALIBS)
+
+livre_orient: livre_orient.o $(LEPTLIB)
+ $(CC) -o livre_orient livre_orient.o $(ALL_LIBS) $(EXTRALIBS)
+
+livre_pageseg: livre_pageseg.o $(LEPTLIB)
+ $(CC) -o livre_pageseg livre_pageseg.o $(ALL_LIBS) $(EXTRALIBS)
+
+livre_seedgen: livre_seedgen.o $(LEPTLIB)
+ $(CC) -o livre_seedgen livre_seedgen.o $(ALL_LIBS) $(EXTRALIBS)
+
+livre_tophat: livre_tophat.o $(LEPTLIB)
+ $(CC) -o livre_tophat livre_tophat.o $(ALL_LIBS) $(EXTRALIBS)
+
+maketile: maketile.o $(LEPTLIB)
+ $(CC) -o maketile maketile.o $(ALL_LIBS) $(EXTRALIBS)
+
+maptest: maptest.o $(LEPTLIB)
+ $(CC) -o maptest maptest.o $(ALL_LIBS) $(EXTRALIBS)
+
+messagetest: messagetest.o $(LEPTLIB)
+ $(CC) -o messagetest messagetest.o $(ALL_LIBS) $(EXTRALIBS)
+
+misctest1: misctest1.o $(LEPTLIB)
+ $(CC) -o misctest1 misctest1.o $(ALL_LIBS) $(EXTRALIBS)
+
+modifyhuesat: modifyhuesat.o $(LEPTLIB)
+ $(CC) -o modifyhuesat modifyhuesat.o $(ALL_LIBS) $(EXTRALIBS)
+
+morphtest1: morphtest1.o $(LEPTLIB)
+ $(CC) -o morphtest1 morphtest1.o $(ALL_LIBS) $(EXTRALIBS)
+
+numaranktest: numaranktest.o $(LEPTLIB)
+ $(CC) -o numaranktest numaranktest.o $(ALL_LIBS) $(EXTRALIBS)
+
+otsutest1: otsutest1.o $(LEPTLIB)
+ $(CC) -o otsutest1 otsutest1.o $(ALL_LIBS) $(EXTRALIBS)
+
+otsutest2: otsutest2.o $(LEPTLIB)
+ $(CC) -o otsutest2 otsutest2.o $(ALL_LIBS) $(EXTRALIBS)
+
+pagesegtest1: pagesegtest1.o $(LEPTLIB)
+ $(CC) -o pagesegtest1 pagesegtest1.o $(ALL_LIBS) $(EXTRALIBS)
+
+pagesegtest2: pagesegtest2.o $(LEPTLIB)
+ $(CC) -o pagesegtest2 pagesegtest2.o $(ALL_LIBS) $(EXTRALIBS)
+
+partifytest: partifytest.o $(LEPTLIB)
+ $(CC) -o partifytest partifytest.o $(ALL_LIBS) $(EXTRALIBS)
+
+partitiontest: partitiontest.o $(LEPTLIB)
+ $(CC) -o partitiontest partitiontest.o $(ALL_LIBS) $(EXTRALIBS)
+
+percolatetest: percolatetest.o $(LEPTLIB)
+ $(CC) -o percolatetest percolatetest.o $(ALL_LIBS) $(EXTRALIBS)
+
+pixaatest: pixaatest.o $(LEPTLIB)
+ $(CC) -o pixaatest pixaatest.o $(ALL_LIBS) $(EXTRALIBS)
+
+pixafileinfo: pixafileinfo.o $(LEPTLIB)
+ $(CC) -o pixafileinfo pixafileinfo.o $(ALL_LIBS) $(EXTRALIBS)
+
+plottest: plottest.o $(LEPTLIB)
+ $(CC) -o plottest plottest.o $(ALL_LIBS) $(EXTRALIBS)
+
+printimage: printimage.o $(LEPTLIB)
+ $(CC) -o printimage printimage.o $(ALL_LIBS) $(EXTRALIBS)
+
+printsplitimage: printsplitimage.o $(LEPTLIB)
+ $(CC) -o printsplitimage printsplitimage.o $(ALL_LIBS) $(EXTRALIBS)
+
+printtiff: printtiff.o $(LEPTLIB)
+ $(CC) -o printtiff printtiff.o $(ALL_LIBS) $(EXTRALIBS)
+
+rasteroptest: rasteroptest.o $(LEPTLIB)
+ $(CC) -o rasteroptest rasteroptest.o $(ALL_LIBS) $(EXTRALIBS)
+
+rbtreetest: rbtreetest.o $(LEPTLIB)
+ $(CC) -o rbtreetest rbtreetest.o $(ALL_LIBS) $(EXTRALIBS)
+
+recog_bootnum1: recog_bootnum1.o $(LEPTLIB)
+ $(CC) -o recog_bootnum1 recog_bootnum1.o $(ALL_LIBS) $(EXTRALIBS)
+
+recog_bootnum2: recog_bootnum2.o $(LEPTLIB)
+ $(CC) -o recog_bootnum2 recog_bootnum2.o $(ALL_LIBS) $(EXTRALIBS)
+
+recog_bootnum3: recog_bootnum3.o $(LEPTLIB)
+ $(CC) -o recog_bootnum3 recog_bootnum3.o $(ALL_LIBS) $(EXTRALIBS)
+
+recogsort: recogsort.o $(LEPTLIB)
+ $(CC) -o recogsort recogsort.o $(ALL_LIBS) $(EXTRALIBS)
+
+recogtest1: recogtest1.o $(LEPTLIB)
+ $(CC) -o recogtest1 recogtest1.o $(ALL_LIBS) $(EXTRALIBS)
+
+recogtest2: recogtest2.o $(LEPTLIB)
+ $(CC) -o recogtest2 recogtest2.o $(ALL_LIBS) $(EXTRALIBS)
+
+recogtest3: recogtest3.o $(LEPTLIB)
+ $(CC) -o recogtest3 recogtest3.o $(ALL_LIBS) $(EXTRALIBS)
+
+recogtest4: recogtest4.o $(LEPTLIB)
+ $(CC) -o recogtest4 recogtest4.o $(ALL_LIBS) $(EXTRALIBS)
+
+recogtest5: recogtest5.o $(LEPTLIB)
+ $(CC) -o recogtest5 recogtest5.o $(ALL_LIBS) $(EXTRALIBS)
+
+recogtest6: recogtest6.o $(LEPTLIB)
+ $(CC) -o recogtest6 recogtest6.o $(ALL_LIBS) $(EXTRALIBS)
+
+recogtest7: recogtest7.o $(LEPTLIB)
+ $(CC) -o recogtest7 recogtest7.o $(ALL_LIBS) $(EXTRALIBS)
+
+reducetest: reducetest.o $(LEPTLIB)
+ $(CC) -o reducetest reducetest.o $(ALL_LIBS) $(EXTRALIBS)
+
+removecmap: removecmap.o $(LEPTLIB)
+ $(CC) -o removecmap removecmap.o $(ALL_LIBS) $(EXTRALIBS)
+
+renderfonts: renderfonts.o $(LEPTLIB)
+ $(CC) -o renderfonts renderfonts.o $(ALL_LIBS) $(EXTRALIBS)
+
+replacebytes: replacebytes.o $(LEPTLIB)
+ $(CC) -o replacebytes replacebytes.o $(ALL_LIBS) $(EXTRALIBS)
+
+rotatefastalt: rotatefastalt.o $(LEPTLIB)
+ $(CC) -o rotatefastalt rotatefastalt.o $(ALL_LIBS) $(EXTRALIBS)
+
+rotate_it: rotate_it.o $(LEPTLIB)
+ $(CC) -o rotate_it rotate_it.o $(ALL_LIBS) $(EXTRALIBS)
+
+rotateorthtest1: rotateorthtest1.o $(LEPTLIB)
+ $(CC) -o rotateorthtest1 rotateorthtest1.o $(ALL_LIBS) $(EXTRALIBS)
+
+rotatetest1: rotatetest1.o $(LEPTLIB)
+ $(CC) -o rotatetest1 rotatetest1.o $(ALL_LIBS) $(EXTRALIBS)
+
+runlengthtest: runlengthtest.o $(LEPTLIB)
+ $(CC) -o runlengthtest runlengthtest.o $(ALL_LIBS) $(EXTRALIBS)
+
+scaleandtile: scaleandtile.o $(LEPTLIB)
+ $(CC) -o scaleandtile scaleandtile.o $(ALL_LIBS) $(EXTRALIBS)
+
+scale_it: scale_it.o $(LEPTLIB)
+ $(CC) -o scale_it scale_it.o $(ALL_LIBS) $(EXTRALIBS)
+
+scaletest1: scaletest1.o $(LEPTLIB)
+ $(CC) -o scaletest1 scaletest1.o $(ALL_LIBS) $(EXTRALIBS)
+
+scaletest2: scaletest2.o $(LEPTLIB)
+ $(CC) -o scaletest2 scaletest2.o $(ALL_LIBS) $(EXTRALIBS)
+
+seedfilltest: seedfilltest.o $(LEPTLIB)
+ $(CC) -o seedfilltest seedfilltest.o $(ALL_LIBS) $(EXTRALIBS)
+
+settest: settest.o $(LEPTLIB)
+ $(CC) -o settest settest.o $(ALL_LIBS) $(EXTRALIBS)
+
+sharptest: sharptest.o $(LEPTLIB)
+ $(CC) -o sharptest sharptest.o $(ALL_LIBS) $(EXTRALIBS)
+
+sheartest: sheartest.o $(LEPTLIB)
+ $(CC) -o sheartest sheartest.o $(ALL_LIBS) $(EXTRALIBS)
+
+showedges: showedges.o $(LEPTLIB)
+ $(CC) -o showedges showedges.o $(ALL_LIBS) $(EXTRALIBS)
+
+skewtest: skewtest.o $(LEPTLIB)
+ $(CC) -o skewtest skewtest.o $(ALL_LIBS) $(EXTRALIBS)
+
+sorttest: sorttest.o $(LEPTLIB)
+ $(CC) -o sorttest sorttest.o $(ALL_LIBS) $(EXTRALIBS)
+
+splitimage2pdf: splitimage2pdf.o $(LEPTLIB)
+ $(CC) -o splitimage2pdf splitimage2pdf.o $(ALL_LIBS) $(EXTRALIBS)
+
+sudokutest: sudokutest.o $(LEPTLIB)
+ $(CC) -o sudokutest sudokutest.o $(ALL_LIBS) $(EXTRALIBS)
+
+textorient: textorient.o $(LEPTLIB)
+ $(CC) -o textorient textorient.o $(ALL_LIBS) $(EXTRALIBS)
+
+tiffpdftest: tiffpdftest.o $(LEPTLIB)
+ $(CC) -o tiffpdftest tiffpdftest.o $(ALL_LIBS) $(EXTRALIBS)
+
+trctest: trctest.o $(LEPTLIB)
+ $(CC) -o trctest trctest.o $(ALL_LIBS) $(EXTRALIBS)
+
+underlinetest: underlinetest.o $(LEPTLIB)
+ $(CC) -o underlinetest underlinetest.o $(ALL_LIBS) $(EXTRALIBS)
+
+warpertest: warpertest.o $(LEPTLIB)
+ $(CC) -o warpertest warpertest.o $(ALL_LIBS) $(EXTRALIBS)
+
+wordsinorder: wordsinorder.o $(LEPTLIB)
+ $(CC) -o wordsinorder wordsinorder.o $(ALL_LIBS) $(EXTRALIBS)
+
+writemtiff: writemtiff.o $(LEPTLIB)
+ $(CC) -o writemtiff writemtiff.o $(ALL_LIBS) $(EXTRALIBS)
+
+xtractprotos: xtractprotos.o $(LEPTLIB)
+ $(CC) -o xtractprotos xtractprotos.o $(ALL_LIBS)
+ cp xtractprotos ../src
+
+yuvtest: yuvtest.o $(LEPTLIB)
+ $(CC) -o yuvtest yuvtest.o $(ALL_LIBS) $(EXTRALIBS)
+
+###########################################################
+
+clean:
+ -@ for file in $(SRC:%.c=%) ; do \
+ rm -f $$file; \
+ done ;
+ -@ for file in $(SRC:%.c=%.o) ; do \
+ rm -f $$file; \
+ done ;
+ -@ for file in $(SRC2:%.c=%.o) ; do \
+ rm -f $$file; \
+ done ;
+
+###########################################################
+
+install:
+ mkdir -p $(DESTDIR)/usr/bin/leptonica
+ -@ for file in $(SRC:%.c=%) ; do \
+ install $$file $(DESTDIR)/usr/bin/leptonica; \
+ done ;
+
+###########################################################
+
+depend:
+ $(BIN)/makedepend -DNO_PROTOS $(CPPFLAGS) $(SRC)
+
+dependprotos:
+ $(BIN)/makedepend $(CPPFLAGS) $(SRC)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
diff --git a/leptonica/prog/maketile.c b/leptonica/prog/maketile.c
new file mode 100644
index 00000000..8aaa7484
--- /dev/null
+++ b/leptonica/prog/maketile.c
@@ -0,0 +1,118 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * maketile.c
+ *
+ * Generates a single image tiling of all images of a specific depth
+ * in a directory. The tiled images are scaled by a specified
+ * isotropic scale factor. One can also specify the approximate width
+ * of the output image file, and the background color that is between
+ * the tiled images.
+ *
+ * Input: dirin: directory that has image files
+ * depth (use 32 for RGB)
+ * scale factor
+ * width (approx. width of output tiled image)
+ * background (0 for white, 1 for black)
+ * fileout: output tiled image file
+ *
+ * Note: this program is Unix only; it will not compile under cygwin.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *dirin, *fileout, *fname, *fullname;
+l_int32 depth, width, background, i, nfiles;
+l_float32 scale;
+SARRAY *safiles;
+PIX *pix, *pixt, *pixd;
+PIXA *pixa;
+static char mainName[] = "maketile";
+
+ if (argc != 7)
+ return ERROR_INT(
+ "Syntax: maketile dirin depth scale width background fileout",
+ mainName, 1);
+ dirin = argv[1];
+ depth = atoi(argv[2]);
+ scale = atof(argv[3]);
+ width = atoi(argv[4]);
+ background = atoi(argv[5]);
+ fileout = argv[6];
+ setLeptDebugOK(1);
+
+ /* capture the filenames in the input directory; ignore directories */
+ if ((safiles = getFilenamesInDirectory(dirin)) == NULL)
+ return ERROR_INT("safiles not made", mainName, 1);
+
+ /* capture images with the requisite depth */
+ nfiles = sarrayGetCount(safiles);
+ pixa = pixaCreate(nfiles);
+ for (i = 0; i < nfiles; i++) {
+ fname = sarrayGetString(safiles, i, L_NOCOPY);
+ fullname = genPathname(dirin, fname);
+ pix = pixRead(fullname);
+ lept_free(fullname);
+ if (!pix)
+ continue;
+ if (pixGetDepth(pix) != depth) {
+ pixDestroy(&pix);
+ continue;
+ }
+ if (pixGetHeight(pix) > 5000) {
+ lept_stderr("%s too tall\n", fname);
+ continue;
+ }
+ pixt = pixScale(pix, scale, scale);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ pixDestroy(&pix);
+/* lept_stderr("%d..", i); */
+ }
+ lept_stderr("\n");
+
+ /* tile them */
+ pixd = pixaDisplayTiled(pixa, width, background, 15);
+
+ if (depth < 8)
+ pixWrite(fileout, pixd, IFF_PNG);
+ else
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+ sarrayDestroy(&safiles);
+ return 0;
+}
+
diff --git a/leptonica/prog/map.057.jpg b/leptonica/prog/map.057.jpg
new file mode 100644
index 00000000..7681e0c4
--- /dev/null
+++ b/leptonica/prog/map.057.jpg
Binary files differ
diff --git a/leptonica/prog/map1.jpg b/leptonica/prog/map1.jpg
new file mode 100644
index 00000000..09fb85ec
--- /dev/null
+++ b/leptonica/prog/map1.jpg
Binary files differ
diff --git a/leptonica/prog/maptest.c b/leptonica/prog/maptest.c
new file mode 100644
index 00000000..b1ac52df
--- /dev/null
+++ b/leptonica/prog/maptest.c
@@ -0,0 +1,426 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * maptest.c
+ *
+ * Tests map function for RGB (uint32) keys and count (int32) values.
+ * The underlying rbtree takes 64 bit keys and values, so it also works
+ * transparently with 32 bit keys and values.
+ *
+ * We take a colormapped image and use the map to accumulate a
+ * histogram of the colors, using the 32-bit rgb value as the key.
+ * The value is the number of pixels with that color that we have seen.
+ *
+ * Also:
+ * * test the forward and backward iterators on the map
+ * * build an inverse colormap table using a map.
+ * * test RGB histogram and counting functions in pix4.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static L_AMAP *BuildMapHistogram(PIX *pix, l_int32 factor, l_int32 print);
+static void DisplayMapHistogram(L_AMAP *m, PIXCMAP *cmap,
+ const char *rootname);
+static void DisplayMapRGBHistogram(L_AMAP *m, const char *rootname);
+static void TestMapIterator1(L_AMAP *m, l_int32 print);
+static void TestMapIterator2(L_AMAP *m, l_int32 print);
+static void TestMapIterator3(L_AMAP *m, l_int32 print);
+static void TestMapIterator4(L_AMAP *m, l_int32 print);
+static void TestMapIterator5(L_AMAP *m, l_int32 print);
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i, n, w, h, ncolors;
+l_uint32 val32;
+L_AMAP *m;
+NUMA *na;
+PIX *pix;
+PIXCMAP *cmap;
+RB_TYPE key, value;
+RB_TYPE *pval;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/map");
+
+ pix = pixRead("weasel8.240c.png");
+ pixGetDimensions(pix, &w, &h, NULL);
+ lept_stderr("Image area in pixels: %d\n", w * h);
+ cmap = pixGetColormap(pix);
+
+ /* Build the histogram, stored in a map. Then compute
+ * and display the histogram as the number of pixels vs
+ * the colormap index */
+ m = BuildMapHistogram(pix, 1, FALSE);
+ TestMapIterator1(m, FALSE);
+ TestMapIterator2(m, FALSE);
+ DisplayMapHistogram(m, cmap, "/tmp/lept/map/map1");
+ l_amapDestroy(&m);
+
+ /* Ditto, but just with a few pixels */
+ m = BuildMapHistogram(pix, 14, TRUE);
+ DisplayMapHistogram(m, cmap, "/tmp/lept/map/map2");
+ l_amapDestroy(&m);
+
+ /* Do in-order tranversals, using the iterators */
+ m = BuildMapHistogram(pix, 7, FALSE);
+ TestMapIterator1(m, TRUE);
+ TestMapIterator2(m, TRUE);
+ l_amapDestroy(&m);
+
+ /* Do in-order tranversals, with iterators and destroying the map */
+ m = BuildMapHistogram(pix, 7, FALSE);
+ TestMapIterator3(m, TRUE);
+ lept_free(m);
+ m = BuildMapHistogram(pix, 7, FALSE);
+ TestMapIterator4(m, TRUE);
+ lept_free(m);
+
+ /* Do in-order tranversals, with iterators and reversing the map */
+ m = BuildMapHistogram(pix, 7, FALSE);
+ TestMapIterator5(m, TRUE);
+ l_amapDestroy(&m);
+
+ /* Build a histogram the old-fashioned way */
+ na = pixGetCmapHistogram(pix, 1);
+ numaWrite("/tmp/lept/map/map2.na", na);
+ gplotSimple1(na, GPLOT_PNG, "/tmp/lept/map/map3", NULL);
+ numaDestroy(&na);
+
+ /* Build a separate map from (rgb) --> colormap index ... */
+ m = l_amapCreate(L_UINT_TYPE);
+ n = pixcmapGetCount(cmap);
+ for (i = 0; i < n; i++) {
+ pixcmapGetColor32(cmap, i, &val32);
+ key.utype = val32;
+ value.itype = i;
+ l_amapInsert(m, key, value);
+ }
+ /* ... and test the map */
+ for (i = 0; i < n; i++) {
+ pixcmapGetColor32(cmap, i, &val32);
+ key.utype = val32;
+ pval = l_amapFind(m, key);
+ if (pval && (i != pval->itype))
+ lept_stderr("i = %d != val = %llx\n", i, pval->itype);
+ }
+ l_amapDestroy(&m);
+ pixDestroy(&pix);
+
+ /* Build and display a real RGB histogram */
+ pix = pixRead("wyom.jpg");
+ m = pixGetColorAmapHistogram(pix, 1);
+ DisplayMapRGBHistogram(m, "/tmp/lept/map/map4");
+ pixNumColors(pix, 1, &ncolors);
+ lept_stderr(" Using pixNumColors: %d\n", ncolors);
+ pixCountRGBColors(pix, 1, &ncolors);
+ lept_stderr(" Using pixCountRGBColors: %d\n", ncolors);
+ l_amapDestroy(&m);
+ pixDestroy(&pix);
+
+ return 0;
+}
+
+static L_AMAP *
+BuildMapHistogram(PIX *pix,
+ l_int32 factor,
+ l_int32 print)
+{
+l_int32 i, j, w, h, wpl, val;
+l_uint32 val32;
+l_uint32 *data, *line;
+L_AMAP *m;
+PIXCMAP *cmap;
+RB_TYPE key, value;
+RB_TYPE *pval;
+
+ lept_stderr("\n --------------- Begin building map --------------\n");
+ m = l_amapCreate(L_UINT_TYPE);
+ data = pixGetData(pix);
+ wpl = pixGetWpl(pix);
+ cmap = pixGetColormap(pix);
+ pixGetDimensions(pix, &w, &h, NULL);
+ for (i = 0; i < h; i += factor) {
+ line = data + i * wpl;
+ for (j = 0; j < w; j += factor) {
+ val = GET_DATA_BYTE(line, j);
+ pixcmapGetColor32(cmap, val, &val32);
+ key.utype = val32;
+ pval = l_amapFind(m, key);
+ if (!pval)
+ value.itype = 1;
+ else
+ value.itype = 1 + pval->itype;
+ if (print)
+ lept_stderr("key = %llx, val = %lld\n", key.utype, value.itype);
+ l_amapInsert(m, key, value);
+ }
+ }
+ lept_stderr("Size: %d\n", l_amapSize(m));
+ if (print)
+ l_rbtreePrint(stderr, m);
+ lept_stderr(" ----------- End Building map -----------------\n");
+
+ return m;
+}
+
+
+static void
+DisplayMapHistogram(L_AMAP *m,
+ PIXCMAP *cmap,
+ const char *rootname)
+{
+char buf[128];
+l_int32 i, n, ival;
+l_uint32 val32;
+NUMA *na;
+RB_TYPE key;
+RB_TYPE *pval;
+
+ n = pixcmapGetCount(cmap);
+ na = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ pixcmapGetColor32(cmap, i, &val32);
+ key.utype = val32;
+ pval = l_amapFind(m, key);
+ if (pval) {
+ ival = pval->itype;
+ numaAddNumber(na, ival);
+ }
+ }
+ gplotSimple1(na, GPLOT_PNG, rootname, NULL);
+ snprintf(buf, sizeof(buf), "%s.png", rootname);
+ l_fileDisplay(buf, 700, 0, 1.0);
+ numaDestroy(&na);
+ return;
+}
+
+static void
+DisplayMapRGBHistogram(L_AMAP *m,
+ const char *rootname)
+{
+char buf[128];
+l_int32 ncolors, npix, ival, maxn, maxn2;
+l_uint32 maxcolor;
+L_AMAP_NODE *n;
+NUMA *na;
+
+ lept_stderr("\n --------------- Display RGB histogram ------------\n");
+ na = numaCreate(0);
+ ncolors = npix = 0;
+ maxn = 0;
+ maxcolor = 0;
+ n = l_amapGetFirst(m);
+ while (n) {
+ ncolors++;
+ ival = n->value.itype;
+ if (ival > maxn) {
+ maxn = ival;
+ maxcolor = n->key.utype;
+ }
+ numaAddNumber(na, ival);
+ npix += ival;
+ n = l_amapGetNext(n);
+ }
+ lept_stderr(" Num colors = %d, Num pixels = %d\n", ncolors, npix);
+ lept_stderr(" Color %x has count %d\n", maxcolor, maxn);
+ maxn2 = amapGetCountForColor(m, maxcolor);
+ if (maxn != maxn2)
+ lept_stderr(" Error: maxn2 = %d; not equal to %d\n", maxn, maxn2);
+ gplotSimple1(na, GPLOT_PNG, rootname, NULL);
+ snprintf(buf, sizeof(buf), "%s.png", rootname);
+ l_fileDisplay(buf, 1400, 0, 1.0);
+ numaDestroy(&na);
+ return;
+}
+
+static void
+TestMapIterator1(L_AMAP *m,
+ l_int32 print) /* forward iterator; fixed tree */
+{
+l_int32 count, npix, ival;
+l_uint32 ukey;
+L_AMAP_NODE *n;
+
+ n = l_amapGetFirst(m);
+ count = 0;
+ npix = 0;
+ lept_stderr("\n ---------- Begin forward iter listing -----------\n");
+ while (n) {
+ count++;
+ ukey = n->key.utype;
+ ival = n->value.itype;
+ npix += ival;
+ if (print)
+ lept_stderr("key = %x, val = %d\n", ukey, ival);
+ n = l_amapGetNext(n);
+ }
+ lept_stderr("Count from iterator: %d\n", count);
+ lept_stderr("Number of pixels: %d\n", npix);
+ lept_stderr(" ------------ End forward iter listing -----------\n");
+ return;
+}
+
+static void
+TestMapIterator2(L_AMAP *m,
+ l_int32 print) /* reverse iterator; fixed tree */
+{
+l_int32 count, npix, ival;
+l_uint32 ukey;
+L_AMAP_NODE *n;
+
+ n = l_amapGetLast(m);
+ count = 0;
+ npix = 0;
+ lept_stderr("\n ---------- Begin reverse iter listing -----------\n");
+ while (n) {
+ count++;
+ ukey = n->key.utype;
+ ival = n->value.itype;
+ npix += ival;
+ if (print)
+ lept_stderr("key = %x, val = %d\n", ukey, ival);
+ n = l_amapGetPrev(n);
+ }
+ lept_stderr("Count from iterator: %d\n", count);
+ lept_stderr("Number of pixels: %d\n", npix);
+ lept_stderr(" ------------ End reverse iter listing -----------\n");
+ return;
+}
+
+static void
+TestMapIterator3(L_AMAP *m,
+ l_int32 print) /* forward iterator; delete the tree */
+{
+l_int32 count, npix, ival;
+l_uint32 ukey;
+L_AMAP_NODE *n, *nn;
+
+ n = l_amapGetFirst(m);
+ count = 0;
+ npix = 0;
+ lept_stderr("\n ------ Begin forward iter; delete tree ---------\n");
+ while (n) {
+ nn = l_amapGetNext(n);
+ count++;
+ ukey = n->key.utype;
+ ival = n->value.itype;
+ npix += ival;
+ if (print)
+ lept_stderr("key = %x, val = %d\n", ukey, ival);
+ l_amapDelete(m, n->key);
+ n = nn;
+ }
+ lept_stderr("Count from iterator: %d\n", count);
+ lept_stderr("Number of pixels: %d\n", npix);
+ lept_stderr(" ------ End forward iter; delete tree ---------\n");
+ return;
+}
+
+static void
+TestMapIterator4(L_AMAP *m,
+ l_int32 print) /* reverse iterator; delete the tree */
+{
+l_int32 count, npix, ival;
+l_uint32 ukey;
+L_AMAP_NODE *n, *np;
+
+ n = l_amapGetLast(m);
+ count = 0;
+ npix = 0;
+ lept_stderr("\n ------- Begin reverse iter; delete tree --------\n");
+ while (n) {
+ np = l_amapGetPrev(n);
+ count++;
+ ukey = n->key.utype;
+ ival = n->value.itype;
+ npix += ival;
+ if (print)
+ lept_stderr("key = %x, val = %d\n", ukey, ival);
+ l_amapDelete(m, n->key);
+ n = np;
+ }
+ lept_stderr("Count from iterator: %d\n", count);
+ lept_stderr("Number of pixels: %d\n", npix);
+ lept_stderr(" ------- End reverse iter; delete tree --------\n");
+ return;
+}
+
+static void
+TestMapIterator5(L_AMAP *m,
+ l_int32 print) /* reverse iterator; rebuild the tree */
+{
+l_int32 count, npix, ival;
+l_uint32 ukey;
+L_AMAP *m2;
+L_AMAP_NODE *n, *np;
+
+ m2 = l_amapCreate(L_UINT_TYPE);
+ n = l_amapGetLast(m);
+ count = npix = 0;
+ lept_stderr("\n ------- Begin reverse iter; rebuild tree --------\n");
+ while (n) {
+ np = l_amapGetPrev(n);
+ count++;
+ ukey = n->key.utype;
+ ival = n->value.itype;
+ l_amapInsert(m2, n->key, n->value);
+ npix += ival;
+ if (print)
+ lept_stderr("key = %x, val = %d\n", ukey, ival);
+ l_amapDelete(m, n->key);
+ n = np;
+ }
+ m->root = m2->root;
+ lept_free(m2);
+ lept_stderr("Count from iterator: %d\n", count);
+ lept_stderr("Number of pixels: %d\n", npix);
+ count = npix = 0;
+ n = l_amapGetLast(m);
+ while (n) {
+ np = l_amapGetPrev(n);
+ count++;
+ ukey = n->key.utype;
+ ival = n->value.itype;
+ npix += ival;
+ if (print)
+ lept_stderr("key = %x, val = %d\n", ukey, ival);
+ n = np;
+ }
+ lept_stderr("Count from iterator: %d\n", count);
+ lept_stderr("Number of pixels: %d\n", npix);
+ lept_stderr(" ------- End reverse iter; rebuild tree --------\n");
+ return;
+}
+
+
diff --git a/leptonica/prog/marge.jpg b/leptonica/prog/marge.jpg
new file mode 100644
index 00000000..2a14582e
--- /dev/null
+++ b/leptonica/prog/marge.jpg
Binary files differ
diff --git a/leptonica/prog/maze_reg.c b/leptonica/prog/maze_reg.c
new file mode 100644
index 00000000..9bbda980
--- /dev/null
+++ b/leptonica/prog/maze_reg.c
@@ -0,0 +1,110 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Tests the functions in maze.c: binary and gray maze search
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+#define NPATHS 6
+static const l_int32 x0[NPATHS] = {42, 73, 73, 42, 324, 471};
+static const l_int32 y0[NPATHS] = {117, 319, 319, 117, 170, 201};
+static const l_int32 x1[NPATHS] = {419, 419, 233, 326, 418, 128};
+static const l_int32 y1[NPATHS] = {383, 383, 112, 168, 371, 341};
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, w, h;
+PIX *pixm, *pixs, *pixg, *pixt, *pixd;
+PIXA *pixa;
+PIXAA *paa;
+PTA *pta;
+PTAA *ptaa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ paa = pixaaCreate(2);
+
+ /* ---------------- Shortest path in binary maze ---------------- */
+ /* Generate the maze */
+ pixa = pixaCreate(0);
+ pixm = generateBinaryMaze(200, 200, 20, 20, 0.65, 0.25);
+ pixd = pixExpandBinaryReplicate(pixm, 3, 3);
+ pixaAddPix(pixa, pixd, L_INSERT);
+
+ /* Find the shortest path between two points */
+ pta = pixSearchBinaryMaze(pixm, 20, 20, 170, 170, NULL);
+ pixt = pixDisplayPta(NULL, pixm, pta);
+ pixd = pixScaleBySampling(pixt, 3., 3.);
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 0 */
+ ptaDestroy(&pta);
+ pixDestroy(&pixt);
+ pixDestroy(&pixm);
+
+ /* ---------------- Shortest path in gray maze ---------------- */
+ pixg = pixRead("test8.jpg");
+ pixGetDimensions(pixg, &w, &h, NULL);
+ ptaa = ptaaCreate(NPATHS);
+ for (i = 0; i < NPATHS; i++) {
+ if (x0[i] >= w || x1[i] >= w || y0[i] >= h || y1[i] >= h) {
+ lept_stderr("path %d extends beyond image; skipping\n", i);
+ continue;
+ }
+ pta = pixSearchGrayMaze(pixg, x0[i], y0[i], x1[i], y1[i], NULL);
+ ptaaAddPta(ptaa, pta, L_INSERT);
+ }
+
+ pixt = pixDisplayPtaa(pixg, ptaa);
+ pixd = pixScaleBySampling(pixt, 2., 2.);
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 1 */
+ ptaaDestroy(&ptaa);
+ pixDestroy(&pixg);
+ pixDestroy(&pixt);
+
+ /* Bundle it all up */
+ pixd = pixaaDisplayByPixa(paa, 3, 1.0, 20, 40, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaaDestroy(&paa);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/messagetest.c b/leptonica/prog/messagetest.c
new file mode 100644
index 00000000..73133681
--- /dev/null
+++ b/leptonica/prog/messagetest.c
@@ -0,0 +1,181 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/* Test the message severity system. */
+
+/* There are three parts:
+ * o The first part demonstrates the message severity functionality.
+ * o The second part demonstrates a combination of message severity control
+ * and redirect of output to stderr (in this case to dev null).
+ * o The third part shows that the naked fprintf() is not affected by the
+ * callback handler, and the default handler is restored with NULL input.
+ *
+ * Notes on the message severity functionality
+ * --------------------------------------------
+ *
+ * The program prints info, warning, and error messages at the initial
+ * run-time severity, which defaults to L_SEVERITY_INFO. Then it resets the
+ * severity to the value specified by an environment variable (or failing
+ * that, specified by one of 7 severity control variables) and prints three
+ * more info, warning, and error messages.
+ *
+ * Which messages actually print depend on the compile-time definitions of the
+ * MINIMUM_SEVERITY and DEFAULT_SEVERITY identifiers and the run-time
+ * definition of the LEPT_MSG_SEVERITY environment variable. For example:
+ *
+ * These commands... --> ...print these messages
+ * ============================== ====================================
+ * $ make
+ *
+ * $ ./print --> info, warn, error, info, warn, error
+ * $ LEPT_MSG_SEVERITY=0 ./print --> info, warn, error, info, warn, error
+ * $ LEPT_MSG_SEVERITY=5 ./print --> info, warn, error, error
+ * $ LEPT_MSG_SEVERITY=6 ./print --> info, warn, error
+ *
+ *
+ * $ make clean ; make DEFINES='-D DEFAULT_SEVERITY=L_SEVERITY_WARNING'
+ *
+ * $ ./print --> warn, error, warn, error
+ * $ LEPT_MSG_SEVERITY=0 ./print --> warn, error, info, warn, error
+ * $ LEPT_MSG_SEVERITY=5 ./print --> warn, error, error
+ * $ LEPT_MSG_SEVERITY=6 ./print --> warn, error
+ *
+ *
+ * $ make clean ; make DEFINES='-D MINIMUM_SEVERITY=L_SEVERITY_WARNING'
+ *
+ * $ ./print --> warn, error, warn, error
+ * $ LEPT_MSG_SEVERITY=0 ./print --> warn, error, warn, error
+ * $ LEPT_MSG_SEVERITY=5 ./print --> warn, error, error
+ * $ LEPT_MSG_SEVERITY=6 ./print --> warn, error
+ *
+ *
+ * $ make clean ; make DEFINES='-D NO_CONSOLE_IO'
+ *
+ * $ ./print --> (no messages)
+ * $ LEPT_MSG_SEVERITY=0 ./print --> (no messages)
+ * $ LEPT_MSG_SEVERITY=5 ./print --> (no messages)
+ * $ LEPT_MSG_SEVERITY=6 ./print --> (no messages)
+ *
+ * Note that in the first and second cases, code is generated to print all six
+ * messages, while in the third and fourth cases, code is not generated to
+ * print info or all messages, respectively. This allows the run-time overhead
+ * and code space of the print statements to be removed from the library, if
+ * desired.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void TestMessageControl(l_int32 severity);
+void TestStderrRedirect();
+
+ /* dev null callback for stderr redirect */
+static void send_to_devnull(const char *msg) {}
+
+int main ()
+{
+ /* Part 1: all output to stderr */
+ lept_stderr("\nSeverity tests\n");
+ TestMessageControl(L_SEVERITY_EXTERNAL);
+ TestMessageControl(L_SEVERITY_INFO);
+ TestMessageControl(L_SEVERITY_WARNING);
+ TestMessageControl(L_SEVERITY_ERROR);
+ TestMessageControl(L_SEVERITY_NONE);
+
+ /* Part 2: test combination of severity and redirect */
+ lept_stderr("\nRedirect Tests\n\n");
+ setMsgSeverity(L_SEVERITY_INFO);
+ TestStderrRedirect();
+ setMsgSeverity(L_SEVERITY_WARNING);
+ TestStderrRedirect();
+ setMsgSeverity(L_SEVERITY_ERROR);
+ TestStderrRedirect();
+ setMsgSeverity(L_SEVERITY_NONE);
+ TestStderrRedirect();
+
+ /* Part 3: test of naked fprintf and output with callback handler.
+ * All lines should print except for line 4. */
+ lept_stderr("1. text\n");
+ lept_stderr("2. text\n");
+ leptSetStderrHandler(send_to_devnull);
+ lept_stderr("3. text\n");
+ lept_stderr("4. text\n");
+ leptSetStderrHandler(NULL);
+ lept_stderr("5. text\n");
+ lept_stderr("6. text\n");
+
+ return 0;
+}
+
+void TestMessageControl(l_int32 severity)
+{
+l_int32 orig_severity;
+
+ setMsgSeverity(DEFAULT_SEVERITY);
+ fputc ('\n', stderr);
+
+ /* Print a set of messages with the default setting */
+ L_INFO ("First message\n", "messagetest");
+ L_WARNING ("First message\n", "messagetest");
+ L_ERROR ("First message\n", "messagetest");
+
+ /* Set the run-time severity to the value specified by the
+ LEPT_MSG_SEVERITY environment variable. If the variable
+ is not defined, set the run-time severity to the input value */
+ orig_severity = setMsgSeverity(severity);
+
+ /* Print messages allowed by the new severity setting */
+ L_INFO ("Second message\n", "messagetest");
+ L_WARNING ("Second message\n", "messagetest");
+ L_ERROR ("Second message\n", "messagetest");
+};
+
+void TestStderrRedirect() {
+PIX *pix1;
+
+ /* Output to stderr works */
+ L_INFO("test output 1 to stderr\n", "messagetest");
+ L_WARNING("test output 1 to stderr\n", "messagetest");
+ L_ERROR("test output 1 to stderr\n", "messagetest");
+ pix1 = pixRead("doesn't_exist");
+ /* There is no "test output 2" */
+ leptSetStderrHandler(send_to_devnull);
+ L_INFO("test output 2 to stderr\n", "messagetest");
+ L_WARNING("test output 2 to stderr\n", "messagetest");
+ L_ERROR("test output 2 to stderr\n", "messagetest");
+ pix1 = pixRead("doesn't_exist");
+ leptSetStderrHandler(NULL);
+ /* Output is restored to stderr */
+ L_INFO("test output 3 to stderr\n", "messagetest");
+ L_WARNING("test output 3 to stderr\n", "messagetest");
+ L_ERROR("test output 3 to stderr\n", "messagetest");
+ pix1 = pixRead("doesn't_exist");
+ lept_stderr("---------------------------------\n");
+}
+
diff --git a/leptonica/prog/minisblack.tif b/leptonica/prog/minisblack.tif
new file mode 100644
index 00000000..1db57855
--- /dev/null
+++ b/leptonica/prog/minisblack.tif
Binary files differ
diff --git a/leptonica/prog/miniswhite.tif b/leptonica/prog/miniswhite.tif
new file mode 100644
index 00000000..46d7e4f4
--- /dev/null
+++ b/leptonica/prog/miniswhite.tif
Binary files differ
diff --git a/leptonica/prog/misctest1.c b/leptonica/prog/misctest1.c
new file mode 100644
index 00000000..543af538
--- /dev/null
+++ b/leptonica/prog/misctest1.c
@@ -0,0 +1,331 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * misctest1.c
+ * * Combine two grayscale images using a mask
+ * * Combine two binary images using a mask
+ * * Do a restricted seedfill
+ * * Colorize a grayscale image
+ * * Convert color to gray
+ * * Extract text lines
+ * * Plot box side locations and dimension of a boxa
+ * * Extract and display rank sized components
+ * * Extract parts of an image using a boxa
+ * * Display pixaa in row major order by component pixa.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define SHOW 0
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w, h, bx, by, bw, bh, i, j;
+BOX *box1, *box2;
+BOXA *boxa1, *boxa2, *boxae, *boxao;
+PIX *pixs, *pix1, *pix2, *pix3, *pixg, *pixb, *pixd, *pixc;
+PIX *pixm, *pixm2, *pixd2, *pixs2;
+PIXA *pixa1, *pixa2;
+PIXAA *paa;
+PIXCMAP *cmap, *cmapg;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/misc");
+ paa = pixaaCreate(0);
+
+ /* Combine two grayscale images using a mask */
+ lept_stderr("Combine two grayscale images using a mask\n");
+ pixa1 = pixaCreate(0);
+ pixd = pixRead("feyn.tif");
+ pixs = pixRead("rabi.png");
+ pixm = pixRead("pageseg2-seed.png");
+ pixd2 = pixScaleToGray2(pixd);
+ pixs2 = pixScaleToGray2(pixs);
+ pixaAddPix(pixa1, pixd2, L_COPY);
+ pixaAddPix(pixa1, pixs2, L_INSERT);
+ pixaAddPix(pixa1, pixm, L_COPY);
+ pixCombineMaskedGeneral(pixd2, pixs2, pixm, 100, 100);
+ pixaAddPix(pixa1, pixd2, L_INSERT);
+ pixDisplayWithTitle(pixd2, 100, 100, NULL, SHOW);
+ pixaaAddPixa(paa, pixa1, L_INSERT);
+
+ /* Combine two binary images using a mask */
+ lept_stderr("Combine two binary images using a mask\n");
+ pixa1 = pixaCreate(0);
+ pixm2 = pixExpandBinaryReplicate(pixm, 2, 2);
+ pix1 = pixCopy(NULL, pixd);
+ pixCombineMaskedGeneral(pixd, pixs, pixm2, 200, 200);
+ pixaAddPix(pixa1, pixd, L_COPY);
+ pixDisplayWithTitle(pixd, 700, 100, NULL, SHOW);
+ pixCombineMasked(pix1, pixs, pixm2);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixaaAddPixa(paa, pixa1, L_INSERT);
+ pixDestroy(&pixd);
+ pixDestroy(&pixs);
+ pixDestroy(&pixm);
+ pixDestroy(&pixm2);
+
+ /* Do a restricted seedfill */
+ lept_stderr("Do a restricted seedfill\n");
+ pixa1 = pixaCreate(0);
+ pixs = pixRead("pageseg2-seed.png");
+ pixm = pixRead("pageseg2-mask.png");
+ pixd = pixSeedfillBinaryRestricted(NULL, pixs, pixm, 8, 50, 175);
+ pixaAddPix(pixa1, pixs, L_INSERT);
+ pixaAddPix(pixa1, pixm, L_INSERT);
+ pixaAddPix(pixa1, pixd, L_INSERT);
+ pixaaAddPixa(paa, pixa1, L_INSERT);
+ pix1 = pixaaDisplayByPixa(paa, 10, 0.5, 40, 40, 2);
+ pixWrite("/tmp/lept/misc/mos1.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 100, 100);
+ pixaaDestroy(&paa);
+ pixDestroy(&pix1);
+
+ /* Colorize a grayscale image */
+ lept_stderr("Colorize a grayscale image\n");
+ paa = pixaaCreate(0);
+ pixa1 = pixaCreate(0);
+ pixs = pixRead("lucasta.150.jpg");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixb = pixThresholdToBinary(pixs, 128);
+ boxa1 = pixConnComp(pixb, &pixa2, 8);
+ pixaAddPix(pixa1, pixs, L_COPY);
+ cmap = pixcmapGrayToColor(0x6f90c0);
+ pixSetColormap(pixs, cmap);
+ pixaAddPix(pixa1, pixs, L_COPY);
+ pixc = pixaDisplayRandomCmap(pixa2, w, h);
+ pixcmapResetColor(pixGetColormap(pixc), 0, 255, 255, 255);
+ pixaAddPix(pixa1, pixc, L_INSERT);
+ pixaaAddPixa(paa, pixa1, L_INSERT);
+ pixDestroy(&pixs);
+ pixDestroy(&pixb);
+ boxaDestroy(&boxa1);
+ pixaDestroy(&pixa2);
+
+ /* Convert color to gray */
+ lept_stderr("Convert color to gray\n");
+ pixa1 = pixaCreate(0);
+ pixs = pixRead("weasel4.16c.png");
+ pixaAddPix(pixa1, pixs, L_INSERT);
+ pixc = pixConvertTo32(pixs);
+ pix1 = pixConvertRGBToGray(pixc, 3., 7., 5.); /* bad weights */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pix2 = pixConvertRGBToGrayFast(pixc);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixg = pixCopy(NULL, pixs);
+ cmap = pixGetColormap(pixs);
+ cmapg = pixcmapColorToGray(cmap, 4., 6., 3.);
+ pixSetColormap(pixg, cmapg);
+ pixaAddPix(pixa1, pixg, L_INSERT);
+ pixaaAddPixa(paa, pixa1, L_INSERT);
+ pixDestroy(&pixc);
+
+ pix1 = pixaaDisplayByPixa(paa, 10, 1.0, 20, 20, 0);
+ pixWrite("/tmp/lept/misc/mos2.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 400, 100);
+ pixaaDestroy(&paa);
+ pixDestroy(&pix1);
+
+ /* Extract text lines */
+ lept_stderr("Extract text lines\n");
+ pix1 = pixRead("feyn.tif");
+ pixa1 = pixExtractTextlines(pix1, 150, 150, 0, 0, 5, 5, NULL);
+ boxa1 = pixaGetBoxa(pixa1, L_CLONE);
+ boxaWrite("/tmp/lept/misc/lines1.ba", boxa1);
+ pix2 = pixaDisplayRandomCmap(pixa1, 0, 0);
+ pixcmapResetColor(pixGetColormap(pix2), 0, 255, 255, 255);
+ pixDisplay(pix2, 400, 0);
+ pixWrite("/tmp/lept/misc/lines1.png", pix2, IFF_PNG);
+ boxaDestroy(&boxa1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa1);
+
+ pix1 = pixRead("arabic.png");
+ pixa1 = pixExtractTextlines(pix1, 150, 150, 0, 0, 5, 5, NULL);
+ pix2 = pixaDisplayRandomCmap(pixa1, 0, 0);
+ pixcmapResetColor(pixGetColormap(pix2), 0, 255, 255, 255);
+ pixDisplay(pix2, 400, 400);
+ pixWrite("/tmp/lept/misc/lines2.png", pix2, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa1);
+
+ pix1 = pixRead("arabic2.png");
+ pixa1 = pixExtractTextlines(pix1, 150, 150, 0, 0, 5, 5, NULL);
+ pix2 = pixaDisplayRandomCmap(pixa1, 0, 0);
+ pixcmapResetColor(pixGetColormap(pix2), 0, 255, 255, 255);
+ pixDisplay(pix2, 400, 800);
+ pixWrite("/tmp/lept/misc/lines3.png", pix2, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa1);
+
+ /* Plot box side locations and dimensions of a boxa */
+ lept_stderr("Plot box side locations and dimensions of a boxa\n");
+ pixa1 = pixaCreate(0);
+ boxa1 = boxaRead("boxa2.ba");
+ boxaSplitEvenOdd(boxa1, 0, &boxae, &boxao);
+ boxaPlotSides(boxae, "1-sides-even", NULL, NULL, NULL, NULL, &pix1);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ boxaPlotSides(boxao, "1-sides-odd", NULL, NULL, NULL, NULL, &pix1);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ boxaPlotSizes(boxae, "1-sizes-even", NULL, NULL, &pix1);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ boxaPlotSizes(boxao, "1-sizes-odd", NULL, NULL, &pix1);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ boxaDestroy(&boxae);
+ boxaDestroy(&boxao);
+ boxaDestroy(&boxa1);
+ boxa1 = boxaRead("boxa3.ba");
+ boxaSplitEvenOdd(boxa1, 0, &boxae, &boxao);
+ boxaPlotSides(boxae, "2-sides-even", NULL, NULL, NULL, NULL, &pix1);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ boxaPlotSides(boxao, "2-sides-odd", NULL, NULL, NULL, NULL, &pix1);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ boxaPlotSizes(boxae, "2-sizes-even", NULL, NULL, &pix1);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ boxaPlotSizes(boxao, "2-sizes-odd", NULL, NULL, &pix1);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ boxaDestroy(&boxae);
+ boxaDestroy(&boxao);
+ boxaDestroy(&boxa1);
+ pix1 = pixaDisplayTiledInRows(pixa1, 32, 1500, 1.0, 0, 30, 2);
+ pixWrite("/tmp/lept/misc/boxaplots.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 800, 0);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+
+ /* Extract and display rank sized components */
+ lept_stderr("Extract and display rank sized components\n");
+ pixs = pixRead("rabi-tiny.png");
+ pixa1 = pixaCreate(0);
+ for (i = 1; i <= 5; i++) {
+ pixaAddPix(pixa1, pixs, L_COPY);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixd = pixCreate(w, h, 32);
+ pixSetAll(pixd);
+ for (j = 0; j < 6; j++) {
+ pix1 = pixSelectComponentBySize(pixs, j, i, 8, &box1);
+ pix2 = pixConvertTo32(pix1);
+ boxGetGeometry(box1, &bx, &by, &bw, &bh);
+ pixRasterop(pixd, bx, by, bw, bh, PIX_SRC, pix2, 0, 0);
+ box2 = boxAdjustSides(NULL, box1, -2, 2, -2, 2);
+ pixRenderBoxArb(pixd, box2, 2, 255, 0, 0);
+ pixaAddPix(pixa1, pixd, L_COPY);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ }
+ pixDestroy(&pixd);
+ }
+ pix3 = pixaDisplayTiledAndScaled(pixa1, 32, 300, 7, 0, 30, 2);
+ pixWrite("/tmp/lept/misc/comps.png", pix3, IFF_PNG);
+ pixDisplay(pix3, 600, 300);
+ pixaDestroy(&pixa1);
+ pixDestroy(&pixs);
+ pixDestroy(&pix3);
+
+ /* Extract parts of an image using a boxa */
+ lept_stderr("Extract parts of an image using a boxa\n");
+ pix1 = pixRead("feyn-fract.tif");
+ boxa1 = pixConnCompBB(pix1, 4);
+ boxa2 = boxaSelectBySize(boxa1, 0, 28, L_SELECT_HEIGHT, L_SELECT_IF_GT,
+ NULL),
+ pix2 = pixCopyWithBoxa(pix1, boxa2, L_SET_WHITE);
+ pixWrite("/tmp/lept/misc/tallcomps.png", pix2, IFF_PNG);
+ pixDisplay(pix2, 600, 600);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+
+ /* Display pixaa in row major order by component pixa. */
+ lept_stderr("Display pixaa in row major order by component pixa\n");
+ pix1 = pixRead("char.tif");
+ paa = pixaaCreate(100);
+ for (i = 0; i < 50; i++) {
+ pixa1 = pixaCreate(100);
+ for (j = 0; j < 125 - 2 * i; j++)
+ pixaAddPix(pixa1, pix1, L_COPY);
+ pixaaAddPixa(paa, pixa1, L_INSERT);
+ }
+ pix2 = pixaaDisplayByPixa(paa, 50, 1.0, 10, 5, 0);
+ pixWrite("/tmp/lept/misc/display.png", pix2, IFF_PNG);
+ pixDisplay(pix2, 100, 100);
+ pixaaDestroy(&paa);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Test the set and clear block functions in cmapped pix */
+ lept_stderr("Test the set and clear block functions in cmapped pix\n");
+ lept_stderr("******************************************************\n");
+ lept_stderr("* Testing error checking: ignore two reported errors *\n");
+ pix1 = pixRead("weasel4.11c.png");
+ pixa1 = pixaCreate(0);
+ pix2 = pixCopy(NULL, pix1);
+ pixClearAll(pix2);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pix2 = pixCopy(NULL, pix1);
+ pixSetAll(pix2); /* error */
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pix2 = pixCopy(NULL, pix1);
+ pixSetAllArbitrary(pix2, 4);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pix2 = pixCopy(NULL, pix1);
+ pixSetAllArbitrary(pix2, 11); /* warning */
+ pixaAddPix(pixa1, pix2, L_INSERT);
+
+ box1 = boxCreate(20, 20, 30, 30);
+ pix2 = pixCopy(NULL, pix1);
+ pixClearInRect(pix2, box1);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pix2 = pixCopy(NULL, pix1);
+ pixSetInRect(pix2, box1); /* error */
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pix2 = pixCopy(NULL, pix1);
+ pixSetInRectArbitrary(pix2, box1, 4);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pix2 = pixCopy(NULL, pix1);
+ pixSetInRectArbitrary(pix2, box1, 12); /* warning */
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ lept_stderr("******************************************************\n");
+
+ pix3 = pixaDisplayTiledInColumns(pixa1, 10, 1.0, 15, 2);
+ pixWrite("/tmp/lept/misc/setting.png", pix3, IFF_PNG);
+ pixDisplay(pix3, 500, 100);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa1);
+ return 0;
+}
diff --git a/leptonica/prog/modifyhuesat.c b/leptonica/prog/modifyhuesat.c
new file mode 100644
index 00000000..b1f25592
--- /dev/null
+++ b/leptonica/prog/modifyhuesat.c
@@ -0,0 +1,107 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * modifyhuesat.c
+ *
+ * modifyhuesat filein nhue dhue nsat dsat fileout
+ *
+ * where nhue and nsat are odd
+ *
+ * This gives a rectangle of nhue x nsat output images,
+ * where the center image is not modified.
+ *
+ * Example: modifyhuesat test24.jpg 5 0.2 5 0.2 /tmp/junkout.jpg
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 i, j, w, d, nhue, nsat, tilewidth;
+l_float32 scale, dhue, dsat, delhue, delsat;
+PIX *pixs, *pixt1, *pixt2, *pixd;
+PIXA *pixa;
+static char mainName[] = "modifyhuesat";
+
+ if (argc != 7)
+ return ERROR_INT(
+ " Syntax: modifyhuesat filein nhue dhue nsat dsat fileout",
+ mainName, 1);
+ filein = argv[1];
+ nhue = atoi(argv[2]);
+ dhue = atof(argv[3]);
+ nsat = atoi(argv[4]);
+ dsat = atof(argv[5]);
+ fileout = argv[6];
+ if (nhue % 2 == 0) {
+ nhue++;
+ lept_stderr("nhue must be odd; raised to %d\n", nhue);
+ }
+ if (nsat % 2 == 0) {
+ nsat++;
+ lept_stderr("nsat must be odd; raised to %d\n", nsat);
+ }
+
+ setLeptDebugOK(1);
+ if ((pixt1 = pixRead(filein)) == NULL)
+ return ERROR_INT("pixt1 not read", mainName, 1);
+ pixGetDimensions(pixt1, &w, NULL, NULL);
+ scale = 250.0 / (l_float32)w;
+ pixt2 = pixScale(pixt1, scale, scale);
+ pixs = pixConvertTo32(pixt2);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ pixGetDimensions(pixs, &w, NULL, &d);
+ pixa = pixaCreate(nhue * nsat);
+ for (i = 0; i < nsat; i++) {
+ delsat = (i - nsat / 2.0) * dsat;
+ pixt1 = pixModifySaturation(NULL, pixs, delsat);
+ for (j = 0; j < nhue; j++) {
+ delhue = (j - nhue / 2.0) * dhue;
+ pixt2 = pixModifyHue(NULL, pixt1, delhue);
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ }
+ pixDestroy(&pixt1);
+ }
+
+ tilewidth = L_MIN(w, 1500 / nsat);
+ pixd = pixaDisplayTiledAndScaled(pixa, d, tilewidth, nsat, 0, 25, 3);
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ return 0;
+}
+
diff --git a/leptonica/prog/morphseq_reg.c b/leptonica/prog/morphseq_reg.c
new file mode 100644
index 00000000..00389cce
--- /dev/null
+++ b/leptonica/prog/morphseq_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * morphseq_reg.c
+ *
+ * Simple regression test for binary morph sequence (interpreter),
+ * showing display mode and rejection of invalid sequence components.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define SEQUENCE1 "O1.3 + C3.1 + R22 + D2.2 + X4"
+#define SEQUENCE2 "O2.13 + C5.23 + R22 + X4"
+#define SEQUENCE3 "e3.3 + d3.3 + tw5.5"
+#define SEQUENCE4 "O3.3 + C3.3"
+#define SEQUENCE5 "O5.5 + C5.5"
+#define BAD_SEQUENCE "O1.+D8 + E2.4 + e.4 + r25 + R + R.5 + X + x5 + y7.3"
+
+#define DISPLAY_SEPARATION 0 /* use 250 to get images displayed */
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pixg, *pixc, *pixd;
+static char mainName[] = "morphseq_reg";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: morphseq_reg", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept");
+ pixs = pixRead("feyn.tif");
+
+ /* 1 bpp */
+ pixd = pixMorphSequence(pixs, SEQUENCE1, -1);
+ pixDestroy(&pixd);
+ pixd = pixMorphSequence(pixs, SEQUENCE1, DISPLAY_SEPARATION);
+ pixWrite("/tmp/lept/morphseq1.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+
+ pixd = pixMorphCompSequence(pixs, SEQUENCE2, -2);
+ pixDestroy(&pixd);
+ pixd = pixMorphCompSequence(pixs, SEQUENCE2, DISPLAY_SEPARATION);
+ pixWrite("/tmp/lept/morphseq2.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+
+ lept_stderr("\n ------------------ Error messages -----------------\n");
+ lept_stderr(" ------------ DWA v23 Sel doesn't exist -----------\n");
+ lept_stderr(" ---------------------------------------------------\n");
+ pixd = pixMorphSequenceDwa(pixs, SEQUENCE2, -3);
+ pixDestroy(&pixd);
+ pixd = pixMorphSequenceDwa(pixs, SEQUENCE2, DISPLAY_SEPARATION);
+ pixWrite("/tmp/lept/morphseq3.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+
+ pixd = pixMorphCompSequenceDwa(pixs, SEQUENCE2, -4);
+ pixDestroy(&pixd);
+ pixd = pixMorphCompSequenceDwa(pixs, SEQUENCE2, DISPLAY_SEPARATION);
+ pixWrite("/tmp/lept/morphseq4.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+
+ /* 8 bpp */
+ pixg = pixScaleToGray(pixs, 0.25);
+ pixd = pixGrayMorphSequence(pixg, SEQUENCE3, -5, 150);
+ pixDestroy(&pixd);
+ pixd = pixGrayMorphSequence(pixg, SEQUENCE3, DISPLAY_SEPARATION, 150);
+ pixWrite("/tmp/lept/morphseq5.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+
+ pixd = pixGrayMorphSequence(pixg, SEQUENCE4, -6, 300);
+ pixWrite("/tmp/lept/morphseq6.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+
+ /* 32 bpp */
+ pixc = pixRead("wyom.jpg");
+ pixd = pixColorMorphSequence(pixc, SEQUENCE5, -7, 150);
+ pixDestroy(&pixd);
+ pixd = pixColorMorphSequence(pixc, SEQUENCE5, DISPLAY_SEPARATION, 450);
+ pixWrite("/tmp/lept/morphseq7.png", pixd, IFF_PNG);
+ pixDestroy(&pixc);
+ pixDestroy(&pixd);
+
+ /* Syntax error handling */
+ lept_stderr("\n ----------------- Error messages ------------------\n");
+ lept_stderr(" ---------------- Invalid sequence -----------------\n");
+ lept_stderr(" ---------------------------------------------------\n");
+ pixd = pixMorphSequence(pixs, BAD_SEQUENCE, 50); /* fails; returns null */
+ pixd = pixGrayMorphSequence(pixg, BAD_SEQUENCE, 50, 0); /* this fails */
+
+ pixDestroy(&pixg);
+ pixDestroy(&pixs);
+ return 0;
+}
diff --git a/leptonica/prog/morphtemplate1.txt b/leptonica/prog/morphtemplate1.txt
new file mode 100644
index 00000000..5a769fd5
--- /dev/null
+++ b/leptonica/prog/morphtemplate1.txt
@@ -0,0 +1,224 @@
+/*====================================================================*
+ - 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>", ...};
+
+/*!
+--- * \brief pixMorphDwa_*()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] operation L_MORPH_DILATE, L_MORPH_ERODE,
+ * L_MORPH_OPEN, L_MORPH_CLOSE
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+--- * \brief pixFMorphopGen_*()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] operation L_MORPH_DILATE, L_MORPH_ERODE,
+ * L_MORPH_OPEN, L_MORPH_CLOSE
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/prog/morphtemplate2.txt b/leptonica/prog/morphtemplate2.txt
new file mode 100644
index 00000000..6b876774
--- /dev/null
+++ b/leptonica/prog/morphtemplate2.txt
@@ -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;
+l_int32 j, pwpls;
+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/leptonica/prog/morphtest1.c b/leptonica/prog/morphtest1.c
new file mode 100644
index 00000000..7c61f4c0
--- /dev/null
+++ b/leptonica/prog/morphtest1.c
@@ -0,0 +1,140 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * morphtest1.c
+ *
+ * - Timing test for rasterop-based morphological operations
+ * - Example repository of binary morph operations
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define NTIMES 100
+#define IMAGE_SIZE 8. /* megapixels */
+#define SEL_SIZE 9
+#define BASIC_OPS 1. /* 1 for erosion/dilation; 2 for open/close */
+#define CPU_SPEED 866. /* MHz: set it for the machine you're using */
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, index;
+l_float32 cputime, epo;
+char *filein, *fileout;
+PIX *pixs, *pixd;
+SEL *sel;
+SELA *sela;
+static char mainName[] = "morphtest1";
+
+ if (argc != 3)
+ return ERROR_INT(" Syntax: morphtest1 filein fileout", mainName, 1);
+ filein = argv[1];
+ fileout = argv[2];
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pix not made", mainName, 1);
+ sela = selaAddBasic(NULL);
+
+ /* ------------------------ Timing -------------------------------*/
+#if 1
+ selaFindSelByName(sela, "sel_9h", &index, &sel);
+ selWriteStream(stderr, sel);
+ pixd = pixCreateTemplate(pixs);
+
+ startTimer();
+ for (i = 0; i < NTIMES; i++) {
+ pixDilate(pixd, pixs, sel);
+/* if ((i % 10) == 0) lept_stderr("%d iters\n", i); */
+ }
+ cputime = stopTimer();
+ /* Get the elementary pixel operations/sec */
+ epo = BASIC_OPS * SEL_SIZE * NTIMES * IMAGE_SIZE /(cputime * CPU_SPEED);
+
+ lept_stderr("Time: %7.3f sec\n", cputime);
+ lept_stderr("Speed: %7.3f epo/cycle\n", epo);
+ pixWrite(fileout, pixd, IFF_PNG);
+ pixDestroy(&pixd);
+#endif
+
+ /* ------------------ Example operation from repository --------------*/
+#if 1
+ /* Select a structuring element */
+ selaFindSelByName(sela, "sel_50h", &index, &sel);
+ selWriteStream(stderr, sel);
+
+ /* Do these operations. See below for other ops
+ * that can be substituted here. */
+ pixd = pixOpen(NULL, pixs, sel);
+ pixXor(pixd, pixd, pixs);
+ pixWrite(fileout, pixd, IFF_PNG);
+ pixDestroy(&pixd);
+#endif
+
+ pixDestroy(&pixs);
+ return 0;
+}
+
+
+/* ==================================================================== */
+
+/* -------------------------------------------------------------------- *
+ * Repository for selecting various operations *
+ * that might be used *
+ * -------------------------------------------------------------------- */
+#if 0
+ pixd = pixCreateTemplate(pixs);
+
+ pixd = pixDilate(NULL, pixs, sel);
+ pixd = pixErode(NULL, pixs, sel);
+ pixd = pixOpen(NULL, pixs, sel);
+ pixd = pixClose(NULL, pixs, sel);
+
+ pixDilate(pixd, pixs, sel);
+ pixErode(pixd, pixs, sel);
+ pixOpen(pixd, pixs, sel);
+ pixClose(pixd, pixs, sel);
+
+ pixAnd(pixd, pixd, pixs);
+ pixOr(pixd, pixd, pixs);
+ pixXor(pixd, pixd, pixs);
+ pixSubtract(pixd, pixd, pixs);
+ pixInvert(pixd, pixs);
+
+ pixd = pixAnd(NULL, pixd, pixs);
+ pixd = pixOr(NULL, pixd, pixs);
+ pixd = pixXor(NULL, pixd, pixs);
+ pixd = pixSubtract(NULL, pixd, pixs);
+ pixd = pixInvert(NULL, pixs);
+
+ pixInvert(pixs, pixs);
+#endif /* 0 */
+
diff --git a/leptonica/prog/mtiff_reg.c b/leptonica/prog/mtiff_reg.c
new file mode 100644
index 00000000..1441b017
--- /dev/null
+++ b/leptonica/prog/mtiff_reg.c
@@ -0,0 +1,378 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * mtiff_reg.c
+ *
+ * Tests tiff I/O for:
+ *
+ * - multipage tiff read/write
+ * - writing special tiff tags to file [not tested here]
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+#include <string.h>
+
+static const char *weasel_rev = "/tmp/lept/tiff/weasel_rev.tif";
+static const char *weasel_rev_rev = "/tmp/lept/tiff/weasel_rev_rev.tif";
+static const char *weasel_orig = "/tmp/lept/tiff/weasel_orig.tif";
+
+int main(int argc,
+ char **argv)
+{
+l_uint8 *data;
+char *fname, *filename;
+const char *str;
+char buf[512];
+l_int32 i, n, npages, equal, success;
+size_t offset, size;
+FILE *fp;
+PIX *pix1, *pix2;
+PIXA *pixa, *pixa1, *pixa2, *pixa3;
+SARRAY *sa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "mtiff_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/tiff");
+
+ /* ---------------------- Test multipage I/O -----------------------*/
+ /* This puts every image file in the directory with a string
+ * match to "weasel8" into a multipage tiff file.
+ * Images with 1 bpp are coded as g4; the others as zip.
+ * It then reads back into a pix and displays. */
+ writeMultipageTiff(".", "weasel8.", "/tmp/lept/tiff/weasel8.tif");
+ regTestCheckFile(rp, "/tmp/lept/tiff/weasel8.tif"); /* 0 */
+ pixa = pixaReadMultipageTiff("/tmp/lept/tiff/weasel8.tif");
+ pix1 = pixaDisplayTiledInRows(pixa, 1, 1200, 0.5, 0, 15, 4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pix1 = pixaDisplayTiledInRows(pixa, 8, 1200, 0.8, 0, 15, 4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix1, 0, 200, NULL, rp->display);
+ pixDestroy(&pix1);
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.2, 0, 15, 4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pix1, 0, 400, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ /* This uses the offset method for linearizing overhead of
+ * reading from a multi-image tiff file. */
+ offset = 0;
+ n = 0;
+ pixa = pixaCreate(8);
+ do {
+ pix1 = pixReadFromMultipageTiff("/tmp/lept/tiff/weasel8.tif", &offset);
+ if (!pix1) continue;
+ pixaAddPix(pixa, pix1, L_INSERT);
+ if (rp->display)
+ lept_stderr("offset = %ld\n", (unsigned long)offset);
+ n++;
+ } while (offset != 0);
+ if (rp->display) lept_stderr("Num images = %d\n", n);
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.2, 0, 15, 4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix1, 0, 600, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ /* This uses the offset method for linearizing overhead of
+ * reading from a multi-image tiff file in memory. */
+ offset = 0;
+ n = 0;
+ pixa = pixaCreate(8);
+ data = l_binaryRead("/tmp/lept/tiff/weasel8.tif", &size);
+ do {
+ pix1 = pixReadMemFromMultipageTiff(data, size, &offset);
+ if (!pix1) continue;
+ pixaAddPix(pixa, pix1, L_INSERT);
+ if (rp->display)
+ lept_stderr("offset = %ld\n", (unsigned long)offset);
+ n++;
+ } while (offset != 0);
+ if (rp->display) lept_stderr("Num images = %d\n", n);
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.2, 0, 15, 4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix1, 0, 800, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+ lept_free(data);
+ regTestCompareFiles(rp, 3, 4); /* 6 */
+ regTestCompareFiles(rp, 3, 5); /* 7 */
+
+ /* This makes a 1000 image tiff file and gives timing
+ * for writing and reading. Reading uses both the offset method
+ * for returning individual pix and atomic pixaReadMultipageTiff()
+ * method for returning a pixa of all the images. Reading time
+ * is linear in the number of images, but the writing time is
+ * quadratic, and the actual wall clock time is significantly
+ * more than the printed value. */
+ pix1 = pixRead("char.tif");
+ startTimer();
+ pixWriteTiff("/tmp/lept/tiff/junkm.tif", pix1, IFF_TIFF_G4, "w");
+ for (i = 1; i < 1000; i++) {
+ pixWriteTiff("/tmp/lept/tiff/junkm.tif", pix1, IFF_TIFF_G4, "a");
+ }
+ regTestCheckFile(rp, "/tmp/lept/tiff/junkm.tif"); /* 8 */
+ pixDestroy(&pix1);
+ if (rp->display) {
+ lept_stderr("\n1000 image file: /tmp/lept/tiff/junkm.tif\n");
+ lept_stderr("Time to write 1000 images: %7.3f sec\n", stopTimer());
+ }
+
+ startTimer();
+ offset = 0;
+ n = 0;
+ do {
+ pix1 = pixReadFromMultipageTiff("/tmp/lept/tiff/junkm.tif", &offset);
+ if (!pix1) continue;
+ if (rp->display && (n % 100 == 0))
+ lept_stderr("offset = %ld\n", (unsigned long)offset);
+ pixDestroy(&pix1);
+ n++;
+ } while (offset != 0);
+ regTestCompareValues(rp, 1000, n, 0); /* 9 */
+ if (rp->display)
+ lept_stderr("Time to read %d images: %6.3f sec\n", n, stopTimer());
+
+ startTimer();
+ pixa = pixaReadMultipageTiff("/tmp/lept/tiff/junkm.tif");
+ lept_stderr("Time to read %d images and return a pixa: %6.3f sec\n",
+ pixaGetCount(pixa), stopTimer());
+ pix1 = pixaDisplayTiledInRows(pixa, 8, 1500, 0.8, 0, 15, 4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10 */
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ /* This does the following sequence of operations:
+ * (1) makes pixa1 and writes a multipage tiff file from it
+ * (2) reads that file into memory
+ * (3) generates pixa2 from the data in memory
+ * (4) tiff compresses pixa2 back to memory
+ * (5) generates pixa3 by uncompressing the memory data
+ * (6) compares pixa3 with pixa1 */
+ pix1 = pixRead("weasel8.240c.png"); /* (1) */
+ pixa1 = pixaCreate(10);
+ for (i = 0; i < 10; i++)
+ pixaAddPix(pixa1, pix1, L_COPY);
+ pixDestroy(&pix1);
+ pixaWriteMultipageTiff("/tmp/lept/tiff/junkm2.tif", pixa1);
+ regTestCheckFile(rp, "/tmp/lept/tiff/junkm2.tif"); /* 11 */
+ data = l_binaryRead("/tmp/lept/tiff/junkm2.tif", &size); /* (2) */
+ pixa2 = pixaCreate(10); /* (3) */
+ offset = 0;
+ n = 0;
+ do {
+ pix1 = pixReadMemFromMultipageTiff(data, size, &offset);
+ pixaAddPix(pixa2, pix1, L_INSERT);
+ n++;
+ } while (offset != 0);
+ regTestCompareValues(rp, 10, n, 0); /* 12 */
+ if (rp->display) lept_stderr("\nRead %d images\n", n);
+ lept_free(data);
+ pixaWriteMemMultipageTiff(&data, &size, pixa2); /* (4) */
+ pixa3 = pixaReadMemMultipageTiff(data, size); /* (5) */
+ pix1 = pixaDisplayTiledInRows(pixa3, 8, 1500, 0.8, 0, 15, 4);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 13 */
+ pixDestroy(&pix1);
+ n = pixaGetCount(pixa3);
+ if (rp->display) lept_stderr("Write/read %d images\n", n);
+ success = TRUE;
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa1, i, L_CLONE);
+ pix2 = pixaGetPix(pixa3, i, L_CLONE);
+ pixEqual(pix1, pix2, &equal);
+ if (!equal) success = FALSE;
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ regTestCompareValues(rp, TRUE, success, 0); /* 14 */
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ lept_free(data);
+
+ /* ------------------ Test single-to-multipage I/O -------------------*/
+ /* Read the files and generate a multipage tiff file of G4 images.
+ * Then convert that to a G4 compressed and ascii85 encoded PS file. */
+ sa = getSortedPathnamesInDirectory(".", "weasel4.", 0, 4);
+ if (rp->display) sarrayWriteStream(stderr, sa);
+ sarraySort(sa, sa, L_SORT_INCREASING);
+ if (rp->display) sarrayWriteStream(stderr, sa);
+ npages = sarrayGetCount(sa);
+ for (i = 0; i < npages; i++) {
+ fname = sarrayGetString(sa, i, L_NOCOPY);
+ filename = genPathname(".", fname);
+ pix1 = pixRead(filename);
+ if (!pix1) continue;
+ pix2 = pixConvertTo1(pix1, 128);
+ if (i == 0)
+ pixWriteTiff("/tmp/lept/tiff/weasel4", pix2, IFF_TIFF_G4, "w+");
+ else
+ pixWriteTiff("/tmp/lept/tiff/weasel4", pix2, IFF_TIFF_G4, "a");
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ lept_free(filename);
+ }
+ regTestCheckFile(rp, "/tmp/lept/tiff/junkm2.tif"); /* 15 */
+
+ /* Write it out as a PS file */
+ lept_stderr("Writing to: /tmp/lept/tiff/weasel4.ps\n");
+ convertTiffMultipageToPS("/tmp/lept/tiff/weasel4",
+ "/tmp/lept/tiff/weasel4.ps", 0.95);
+ regTestCheckFile(rp, "/tmp/lept/tiff/weasel4.ps"); /* 16 */
+
+ /* Write it out as a pdf file */
+ lept_stderr("Writing to: /tmp/lept/tiff/weasel4.pdf\n");
+ l_pdfSetDateAndVersion(FALSE);
+ convertTiffMultipageToPdf("/tmp/lept/tiff/weasel4",
+ "/tmp/lept/tiff/weasel4.pdf");
+ regTestCheckFile(rp, "/tmp/lept/tiff/weasel4.pdf"); /* 17 */
+ sarrayDestroy(&sa);
+
+ /* ------------------ Test multipage I/O -------------------*/
+ /* Read count of pages in tiff multipage file */
+ writeMultipageTiff(".", "weasel2", weasel_orig);
+ regTestCheckFile(rp, weasel_orig); /* 18 */
+ fp = lept_fopen(weasel_orig, "rb");
+ success = fileFormatIsTiff(fp);
+ regTestCompareValues(rp, TRUE, success, 0); /* 19 */
+ if (success) {
+ tiffGetCount(fp, &npages);
+ regTestCompareValues(rp, 5, npages, 0); /* 20 */
+ lept_stderr(" Tiff: %d page\n", npages);
+ }
+ lept_fclose(fp);
+
+ /* Split into separate page files */
+ for (i = 0; i < npages + 1; i++) { /* read one beyond to catch error */
+ pix1 = pixReadTiff(weasel_orig, i);
+ if (!pix1) continue;
+ snprintf(buf, sizeof(buf), "/tmp/lept/tiff/%03d.tif", i);
+ pixWrite(buf, pix1, IFF_TIFF_ZIP);
+ pixDestroy(&pix1);
+ }
+
+ /* Read separate page files and write reversed file */
+ for (i = npages - 1; i >= 0; i--) {
+ snprintf(buf, sizeof(buf), "/tmp/lept/tiff/%03d.tif", i);
+ pix1 = pixRead(buf);
+ if (!pix1) continue;
+ if (i == npages - 1)
+ pixWriteTiff(weasel_rev, pix1, IFF_TIFF_ZIP, "w+");
+ else
+ pixWriteTiff(weasel_rev, pix1, IFF_TIFF_ZIP, "a");
+ pixDestroy(&pix1);
+ }
+ regTestCheckFile(rp, weasel_rev); /* 21 */
+
+ /* Read reversed file and reverse again */
+ pixa = pixaCreate(npages);
+ for (i = 0; i < npages; i++) {
+ pix1 = pixReadTiff(weasel_rev, i);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ }
+ for (i = npages - 1; i >= 0; i--) {
+ pix1 = pixaGetPix(pixa, i, L_CLONE);
+ if (i == npages - 1)
+ pixWriteTiff(weasel_rev_rev, pix1, IFF_TIFF_ZIP, "w+");
+ else
+ pixWriteTiff(weasel_rev_rev, pix1, IFF_TIFF_ZIP, "a");
+ pixDestroy(&pix1);
+ }
+ regTestCheckFile(rp, weasel_rev_rev); /* 22 */
+ regTestCompareFiles(rp, 18, 22); /* 23 */
+ pixaDestroy(&pixa);
+
+
+#if 1 /* ----- test adding custom public tags to a tiff header ----- */
+{
+ size_t length;
+ NUMA *naflags, *nasizes;
+ SARRAY *savals, *satypes;
+
+ pix1 = pixRead("feyn.tif");
+ naflags = numaCreate(10);
+ savals = sarrayCreate(10);
+ satypes = sarrayCreate(10);
+ nasizes = numaCreate(10);
+
+/* numaAddNumber(naflags, TIFFTAG_XMLPACKET); */ /* XMP: 700 */
+ numaAddNumber(naflags, 700);
+ str = "<xmp>This is a Fake XMP packet</xmp>\n<text>Guess what ...?</text>";
+ length = strlen(str);
+ sarrayAddString(savals, str, L_COPY);
+ sarrayAddString(satypes, "char*", L_COPY);
+ numaAddNumber(nasizes, length); /* get it all */
+
+ numaAddNumber(naflags, 269); /* DOCUMENTNAME */
+ sarrayAddString(savals, "One silly title", L_COPY);
+ sarrayAddString(satypes, "const char*", L_COPY);
+ numaAddNumber(naflags, 270); /* IMAGEDESCRIPTION */
+ sarrayAddString(savals, "One page of text", L_COPY);
+ sarrayAddString(satypes, "const char*", L_COPY);
+ /* the max sample is used by rendering programs
+ * to scale the dynamic range */
+ numaAddNumber(naflags, 281); /* MAXSAMPLEVALUE */
+ sarrayAddString(savals, "4", L_COPY);
+ sarrayAddString(satypes, "l_uint16", L_COPY);
+ /* note that date is required to be a 20 byte string */
+ numaAddNumber(naflags, 306); /* DATETIME */
+ sarrayAddString(savals, "2004:10:11 09:35:15", L_COPY);
+ sarrayAddString(satypes, "const char*", L_COPY);
+ /* note that page number requires 2 l_uint16 input */
+ numaAddNumber(naflags, 297); /* PAGENUMBER */
+ sarrayAddString(savals, "1-412", L_COPY);
+ sarrayAddString(satypes, "l_uint16-l_uint16", L_COPY);
+ pixWriteTiffCustom("/tmp/lept/tiff/tags.tif", pix1, IFF_TIFF_G4, "w", naflags,
+ savals, satypes, nasizes);
+ fprintTiffInfo(stderr, "/tmp/lept/tiff/tags.tif");
+ lept_stderr("num flags = %d\n", numaGetCount(naflags));
+ lept_stderr("num sizes = %d\n", numaGetCount(nasizes));
+ lept_stderr("num vals = %d\n", sarrayGetCount(savals));
+ lept_stderr("num types = %d\n", sarrayGetCount(satypes));
+ numaDestroy(&naflags);
+ numaDestroy(&nasizes);
+ sarrayDestroy(&savals);
+ sarrayDestroy(&satypes);
+ pixDestroy(&pix1);
+}
+#endif
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/multitype_reg.c b/leptonica/prog/multitype_reg.c
new file mode 100644
index 00000000..4f899036
--- /dev/null
+++ b/leptonica/prog/multitype_reg.c
@@ -0,0 +1,483 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * multitype_reg.c
+ *
+ * Tests various functions against a set of the different image types.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const char *fnames[10] = {"feyn-fract.tif", "speckle2.png",
+ "weasel2.4g.png", "speckle4.png",
+ "weasel4.16c.png", "dreyfus8.png",
+ "weasel8.240c.png", "test8.jpg",
+ "marge.jpg", "test-gray-alpha.png"};
+
+ /* Affine uses the first 3 pt pairs; projective & bilinear use all 4 */
+static const l_int32 xs[] = {300, 1200, 225, 750};
+static const l_int32 xd[] = {330, 1225, 250, 870};
+static const l_int32 ys[] = {1250, 1120, 250, 200};
+static const l_int32 yd[] = {1150, 1200, 250, 290};
+
+enum {
+ PROJECTIVE = 1,
+ BILINEAR = 2
+};
+
+static l_float32 *Generate3PtTransformVector();
+static l_float32 *Generate4PtTransformVector(l_int32 type);
+
+#define DO_ALL 1
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w, h, x, y, i, n;
+l_float32 *vc;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXA *pixas, *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixas = pixaCreate(11);
+ for (i = 0; i < 10; i++) { /* this preserves any alpha */
+ pix1 = pixRead(fnames[i]);
+ pix2 = pixScaleBySamplingToSize(pix1, 250, 150);
+ pixaAddPix(pixas, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ /* Add a transparent grid over the rgb image */
+ pix1 = pixaGetPix(pixas, 8, L_COPY);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ pix2 = pixCreate(w, h, 1);
+ for (i = 0; i < 5; i++) {
+ y = h * (i + 1) / 6;
+ pixRenderLine(pix2, 0, y, w, y, 3, L_SET_PIXELS);
+ }
+ for (i = 0; i < 7; i++) {
+ x = w * (i + 1) / 8;
+ pixRenderLine(pix2, x, 0, x, h, 3, L_SET_PIXELS);
+ }
+ pix3 = pixConvertTo8(pix2, 0); /* 1 --> 0 ==> transparent */
+ pixSetRGBComponent(pix1, pix3, L_ALPHA_CHANNEL);
+ pixaAddPix(pixas, pix1, L_INSERT);
+ n = pixaGetCount(pixas);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+#if DO_ALL
+ /* Display with and without removing alpha with white bg */
+ pix1 = pixaDisplayTiledInRows(pixas, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixRemoveAlpha(pix1);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix1, 200, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if DO_ALL
+ /* Setting to gray */
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pixSetAllGray(pix1, 170);
+ pix2 = pixRemoveAlpha(pix1);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix1, 400, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if DO_ALL
+ /* General scaling */
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixScaleToSize(pix1, 350, 650);
+ pix3 = pixScaleToSize(pix2, 200, 200);
+ pix4 = pixRemoveAlpha(pix3);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pix1, 600, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if DO_ALL
+ /* Scaling by sampling */
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixScaleBySamplingToSize(pix1, 350, 650);
+ pix3 = pixScaleBySamplingToSize(pix2, 200, 200);
+ pix4 = pixRemoveAlpha(pix3);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix1, 800, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if DO_ALL
+ /* Rotation by area mapping; no embedding */
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixRotate(pix1, 0.25, L_ROTATE_AREA_MAP,
+ L_BRING_IN_WHITE, 0, 0);
+ pix3 = pixRotate(pix2, -0.35, L_ROTATE_AREA_MAP,
+ L_BRING_IN_WHITE, 0, 0);
+ pix4 = pixRemoveAlpha(pix3);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix1, 1000, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if DO_ALL
+ /* Rotation by area mapping; with embedding */
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixRotate(pix1, 0.25, L_ROTATE_AREA_MAP,
+ L_BRING_IN_WHITE, 250, 150);
+ pix3 = pixRotate(pix2, -0.35, L_ROTATE_AREA_MAP,
+ L_BRING_IN_WHITE, 250, 150);
+ pix4 = pixRemoveBorderToSize(pix3, 250, 150);
+ pix5 = pixRemoveAlpha(pix4);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pix1, 0, 400, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if DO_ALL
+ /* Rotation by 3-shear; no embedding */
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixRotate(pix1, 0.25, L_ROTATE_SHEAR,
+ L_BRING_IN_WHITE, 0, 0);
+ pix3 = pixRotate(pix2, -0.35, L_ROTATE_SHEAR,
+ L_BRING_IN_WHITE, 0, 0);
+ pix4 = pixRemoveAlpha(pix3);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 7 */
+ pixDisplayWithTitle(pix1, 200, 400, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if DO_ALL
+ /* Rotation by 3-shear; with embedding */
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixRotate(pix1, 0.25, L_ROTATE_SHEAR,
+ L_BRING_IN_WHITE, 250, 150);
+ pix3 = pixRotate(pix2, -0.35, L_ROTATE_SHEAR,
+ L_BRING_IN_WHITE, 250, 150);
+ pix4 = pixRemoveBorderToSize(pix3, 250, 150);
+ pix5 = pixRemoveAlpha(pix4);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 8 */
+ pixDisplayWithTitle(pix1, 400, 400, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if DO_ALL
+ /* Rotation by 2-shear about the center */
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ pix2 = pixRotate2Shear(pix1, w / 2, h / 2, 0.25, L_BRING_IN_WHITE);
+ pix3 = pixRotate2Shear(pix2, w / 2, h / 2, -0.35, L_BRING_IN_WHITE);
+ pix4 = pixRemoveAlpha(pix3);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 9 */
+ pixDisplayWithTitle(pix1, 600, 400, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if DO_ALL
+ /* Rotation by sampling; no embedding */
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixRotate(pix1, 0.25, L_ROTATE_SAMPLING,
+ L_BRING_IN_WHITE, 0, 0);
+ pix3 = pixRotate(pix2, -0.35, L_ROTATE_SAMPLING,
+ L_BRING_IN_WHITE, 0, 0);
+ pix4 = pixRemoveAlpha(pix3);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10 */
+ pixDisplayWithTitle(pix1, 800, 400, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if DO_ALL
+ /* Rotation by sampling; with embedding */
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixRotate(pix1, 0.25, L_ROTATE_SAMPLING,
+ L_BRING_IN_WHITE, 250, 150);
+ pix3 = pixRotate(pix2, -0.35, L_ROTATE_SAMPLING,
+ L_BRING_IN_WHITE, 250, 150);
+ pix4 = pixRemoveBorderToSize(pix3, 250, 150);
+ pix5 = pixRemoveAlpha(pix4);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 11 */
+ pixDisplayWithTitle(pix1, 1000, 400, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if DO_ALL
+ /* Rotation by area mapping at corner */
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixRotateAMCorner(pix1, 0.25, L_BRING_IN_WHITE);
+ pix3 = pixRotateAMCorner(pix2, -0.35, L_BRING_IN_WHITE);
+ pix4 = pixRemoveAlpha(pix3);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 12 */
+ pixDisplayWithTitle(pix1, 0, 800, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+#endif
+
+#if DO_ALL
+ /* Affine transform by interpolation */
+ pixa = pixaCreate(n);
+ vc = Generate3PtTransformVector();
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixAffine(pix1, vc, L_BRING_IN_WHITE);
+/* pix2 = pixAffineSampled(pix1, vc, L_BRING_IN_WHITE); */
+ pix3 = pixRemoveAlpha(pix2);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 13 */
+ pixDisplayWithTitle(pix1, 200, 800, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+ lept_free(vc);
+#endif
+
+#if DO_ALL
+ /* Projective transform by sampling */
+ pixa = pixaCreate(n);
+ vc = Generate4PtTransformVector(PROJECTIVE);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixProjectiveSampled(pix1, vc, L_BRING_IN_WHITE);
+ pix3 = pixRemoveAlpha(pix2);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 14 */
+ pixDisplayWithTitle(pix1, 400, 800, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+ lept_free(vc);
+#endif
+
+#if DO_ALL
+ /* Projective transform by interpolation */
+ pixa = pixaCreate(n);
+ vc = Generate4PtTransformVector(PROJECTIVE);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixProjective(pix1, vc, L_BRING_IN_WHITE);
+ pix3 = pixRemoveAlpha(pix2);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 15 */
+ pixDisplayWithTitle(pix1, 600, 800, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+ lept_free(vc);
+#endif
+
+#if DO_ALL
+ /* Bilinear transform by interpolation */
+ pixa = pixaCreate(n);
+ vc = Generate4PtTransformVector(BILINEAR);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_COPY);
+ pix2 = pixBilinear(pix1, vc, L_BRING_IN_WHITE);
+ pix3 = pixRemoveAlpha(pix2);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 16 */
+ pixDisplayWithTitle(pix1, 800, 800, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+ lept_free(vc);
+#endif
+
+ pixaDestroy(&pixas);
+ return regTestCleanup(rp);
+}
+
+
+static l_float32 *
+Generate3PtTransformVector()
+{
+l_int32 i;
+l_float32 *vc;
+PTA *ptas, *ptad;
+
+ ptas = ptaCreate(3);
+ ptad = ptaCreate(3);
+ for (i = 0; i < 3; i++) {
+ ptaAddPt(ptas, xs[i], ys[i]);
+ ptaAddPt(ptad, xd[i], yd[i]);
+ }
+
+ getAffineXformCoeffs(ptad, ptas, &vc);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ return vc;
+}
+
+static l_float32 *
+Generate4PtTransformVector(l_int32 type)
+{
+l_int32 i;
+l_float32 *vc;
+PTA *ptas, *ptad;
+
+ ptas = ptaCreate(4);
+ ptad = ptaCreate(4);
+ for (i = 0; i < 4; i++) {
+ ptaAddPt(ptas, xs[i], ys[i]);
+ ptaAddPt(ptad, xd[i], yd[i]);
+ }
+
+ if (type == PROJECTIVE)
+ getProjectiveXformCoeffs(ptad, ptas, &vc);
+ else /* BILINEAR */
+ getBilinearXformCoeffs(ptad, ptas, &vc);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ return vc;
+}
diff --git a/leptonica/prog/nearline_reg.c b/leptonica/prog/nearline_reg.c
new file mode 100644
index 00000000..3fd3a4ab
--- /dev/null
+++ b/leptonica/prog/nearline_reg.c
@@ -0,0 +1,189 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * nearline_reg.c
+ *
+ * Regression test for finding min or max values (and averages)
+ * near a specified line.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 ret, i, n, similar, x1, y1, val1, val2, val3, val4;
+l_float32 minave, minave2, maxave, fract;
+NUMA *na1, *na2, *na3, *na4, *na5, *na6;
+NUMAA *naa;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "nearline_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn.tif");
+ pix1 = pixScaleToGray6(pixs);
+ pixDisplayWithTitle(pix1, 100, 600, NULL, rp->display);
+
+ /* Find averages of min and max along about 120 horizontal lines */
+ lept_stderr("******************************************************\n");
+ lept_stderr("* Testing error checking: ignore 12 error messages *\n");
+ na1 = numaCreate(0);
+ na3 = numaCreate(0);
+ for (y1 = 40; y1 < 575; y1 += 5) {
+ ret = pixMinMaxNearLine(pix1, 20, y1, 400, y1, 5, L_SCAN_BOTH,
+ NULL, NULL, &minave, &maxave);
+ if (!ret) {
+ numaAddNumber(na1, (l_int32)minave);
+ numaAddNumber(na3, (l_int32)maxave);
+ if (rp->display)
+ lept_stderr("y = %d: minave = %d, maxave = %d\n",
+ y1, (l_int32)minave, (l_int32)maxave);
+ }
+ }
+
+ /* Find averages along about 120 vertical lines. We've rotated
+ * the image by 90 degrees, so the results should be nearly
+ * identical to the first set. Also generate a single-sided
+ * scan (L_SCAN_NEGATIVE) for comparison with the double-sided scans. */
+ pix2 = pixRotateOrth(pix1, 3);
+ pixDisplayWithTitle(pix2, 600, 600, NULL, rp->display);
+ na2 = numaCreate(0);
+ na4 = numaCreate(0);
+ na5 = numaCreate(0);
+ for (x1 = 40; x1 < 575; x1 += 5) {
+ ret = pixMinMaxNearLine(pix2, x1, 20, x1, 400, 5, L_SCAN_BOTH,
+ NULL, NULL, &minave, &maxave);
+ pixMinMaxNearLine(pix2, x1, 20, x1, 400, 5, L_SCAN_NEGATIVE,
+ NULL, NULL, &minave2, NULL);
+ if (!ret) {
+ numaAddNumber(na2, (l_int32)minave);
+ numaAddNumber(na4, (l_int32)maxave);
+ numaAddNumber(na5, (l_int32)minave2);
+ if (rp->display)
+ lept_stderr("x = %d: minave = %d, minave2 = %d, maxave = %d\n",
+ x1, (l_int32)minave, (l_int32)minave2, (l_int32)maxave);
+ }
+ }
+ lept_stderr("******************************************************\n");
+
+ numaSimilar(na1, na2, 3.0, &similar); /* should be TRUE */
+ regTestCompareValues(rp, similar, 1, 0); /* 0 */
+ numaSimilar(na3, na4, 1.0, &similar); /* should be TRUE */
+ regTestCompareValues(rp, similar, 1, 0); /* 1 */
+ numaWrite("/tmp/lept/regout/na1.na", na1);
+ numaWrite("/tmp/lept/regout/na2.na", na2);
+ numaWrite("/tmp/lept/regout/na3.na", na3);
+ numaWrite("/tmp/lept/regout/na4.na", na4);
+ numaWrite("/tmp/lept/regout/na5.na", na5);
+ regTestCheckFile(rp, "/tmp/lept/regout/na1.na"); /* 2 */
+ regTestCheckFile(rp, "/tmp/lept/regout/na2.na"); /* 3 */
+ regTestCheckFile(rp, "/tmp/lept/regout/na3.na"); /* 4 */
+ regTestCheckFile(rp, "/tmp/lept/regout/na4.na"); /* 5 */
+ regTestCheckFile(rp, "/tmp/lept/regout/na5.na"); /* 6 */
+
+ /* Plot the average minimums for the 3 cases */
+ naa = numaaCreate(3);
+ numaaAddNuma(naa, na1, L_INSERT); /* portrait, double-sided */
+ numaaAddNuma(naa, na2, L_INSERT); /* landscape, double-sided */
+ numaaAddNuma(naa, na5, L_INSERT); /* landscape, single-sided */
+ gplotSimpleN(naa, GPLOT_PNG, "/tmp/lept/regout/nearline",
+ "Average minimums along lines");
+ pix3 = pixRead("/tmp/lept/regout/nearline.png");
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 7 */
+ pixDisplayWithTitle(pix3, 100, 100, NULL, rp->display);
+
+ if (rp->display) {
+ n = numaGetCount(na3);
+ for (i = 0; i < n; i++) {
+ numaGetIValue(na1, i, &val1);
+ numaGetIValue(na2, i, &val2);
+ numaGetIValue(na3, i, &val3);
+ numaGetIValue(na4, i, &val4);
+ lept_stderr("val1 = %d, val2 = %d, diff = %d; "
+ "val3 = %d, val4 = %d, diff = %d\n",
+ val1, val2, L_ABS(val1 - val2),
+ val3, val4, L_ABS(val3 - val4));
+ }
+ }
+
+ numaaDestroy(&naa);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+
+ /* Plot minima along a single line, with different distances */
+ pixMinMaxNearLine(pix1, 20, 200, 400, 200, 2, L_SCAN_BOTH,
+ &na1, NULL, NULL, NULL);
+ pixMinMaxNearLine(pix1, 20, 200, 400, 200, 5, L_SCAN_BOTH,
+ &na2, NULL, NULL, NULL);
+ pixMinMaxNearLine(pix1, 20, 200, 400, 200, 15, L_SCAN_BOTH,
+ &na3, NULL, NULL, NULL);
+ numaWrite("/tmp/lept/regout/na6.na", na1);
+ regTestCheckFile(rp, "/tmp/lept/regout/na6.na"); /* 8 */
+ n = numaGetCount(na1);
+ fract = 100.0 / n;
+ na4 = numaTransform(na1, 0.0, fract);
+ na5 = numaTransform(na2, 0.0, fract);
+ na6 = numaTransform(na3, 0.0, fract);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ na1 = numaUniformSampling(na4, 100);
+ na2 = numaUniformSampling(na5, 100);
+ na3 = numaUniformSampling(na6, 100);
+ naa = numaaCreate(3);
+ numaaAddNuma(naa, na1, L_INSERT);
+ numaaAddNuma(naa, na2, L_INSERT);
+ numaaAddNuma(naa, na3, L_INSERT);
+ gplotSimpleN(naa, GPLOT_PNG, "/tmp/lept/regout/nearline2",
+ "Min along line");
+ pix4 = pixRead("/tmp/lept/regout/nearline2.png");
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 9 */
+ pixDisplayWithTitle(pix4, 800, 100, NULL, rp->display);
+ numaaDestroy(&naa);
+ numaDestroy(&na4);
+ numaDestroy(&na5);
+ numaDestroy(&na6);
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/newspaper_reg.c b/leptonica/prog/newspaper_reg.c
new file mode 100644
index 00000000..a1f12b36
--- /dev/null
+++ b/leptonica/prog/newspaper_reg.c
@@ -0,0 +1,171 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * newspaper_seg.c
+ *
+ * Segmenting newspaper articles using morphology.
+ *
+ * Most of the work is done at 4x reduction (approx. 75 ppi),
+ * which makes it very fast.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w, h;
+BOXA *boxa;
+PIX *pixs, *pixt, *pix1, *pix2, *pix3, *pix4, *pix5;
+PIX *pix6, *pix7, *pix8, *pix9, *pix10, *pix11;
+PIXA *pixa1, *pixa2;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("scots-frag.tif");
+ pixa1 = pixaCreate(12);
+
+ pixt = pixScaleToGray4(pixs);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_JFIF_JPEG); /* 0 */
+
+ /* Rank reduce 2x */
+ pix1 = pixReduceRankBinary2(pixs, 2, NULL);
+ pixt = pixScale(pix1, 0.5, 0.5);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+
+ /* Open out the vertical lines */
+ pix2 = pixMorphSequence(pix1, "o1.50", 0);
+ pixt = pixScale(pix2, 0.5, 0.5);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_TIFF_G4); /* 1 */
+ pixDisplayWithTitle(pixt, 0, 0, "open vertical lines", rp->display);
+
+ /* Seedfill back to get those lines in their entirety */
+ pix3 = pixSeedfillBinary(NULL, pix2, pix1, 8);
+ pixt = pixScale(pix3, 0.5, 0.5);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_TIFF_G4); /* 2 */
+ pixDisplayWithTitle(pixt, 300, 0, "seedfill vertical", rp->display);
+
+ /* Remove the vertical lines (and some of the images) */
+ pixXor(pix2, pix1, pix3);
+ pixt = pixScale(pix2, 0.5, 0.5);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_TIFF_G4); /* 3 */
+ pixDisplayWithTitle(pixt, 600, 0, "remove vertical lines", rp->display);
+
+ /* Open out the horizontal lines */
+ pix4 = pixMorphSequence(pix2, "o50.1", 0);
+ pixt = pixScale(pix4, 0.5, 0.5);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+
+ /* Seedfill back to get those lines in their entirety */
+ pix5 = pixSeedfillBinary(NULL, pix4, pix2, 8);
+ pixt = pixScale(pix5, 0.5, 0.5);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_TIFF_G4); /* 4 */
+ pixDisplayWithTitle(pixt, 900, 0, "seedfill horizontal", rp->display);
+
+ /* Remove the horizontal lines */
+ pixXor(pix4, pix2, pix5);
+ pixt = pixScale(pix4, 0.5, 0.5);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_TIFF_G4); /* 5 */
+ pixDisplayWithTitle(pixt, 1200, 0, "remove horiz lines", rp->display);
+
+ /* Invert and identify vertical gutters between text columns */
+ pix6 = pixReduceRankBinaryCascade(pix4, 1, 1, 0, 0);
+ pixInvert(pix6, pix6);
+ pixt = pixScale(pix6, 2.0, 2.0);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_TIFF_G4); /* 6 */
+ pixDisplayWithTitle(pixt, 1500, 0, NULL, rp->display);
+ pix7 = pixMorphSequence(pix6, "o1.50", 0);
+ pixt = pixScale(pix7, 2.0, 2.0);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_TIFF_G4); /* 7 */
+ pixDisplayWithTitle(pixt, 0, 300, "vertical gutters", rp->display);
+ pix8 = pixExpandBinaryPower2(pix7, 4); /* gutter mask */
+ regTestWritePixAndCheck(rp, pix8, IFF_TIFF_G4); /* 8 */
+
+ /* Solidify text blocks */
+ pix9 = pixMorphSequence(pix4, "c50.1 + c1.10", 0);
+ pixSubtract(pix9, pix9, pix8); /* preserve gutter */
+ pix10 = pixMorphSequence(pix9, "d3.3", 0);
+ pixt = pixScale(pix10, 0.5, 0.5);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_TIFF_G4); /* 9 */
+ pixDisplayWithTitle(pixt, 300, 300, "solidify text", rp->display);
+
+ /* Show stuff under this mask */
+ pixGetDimensions(pix10, &w, &h, NULL);
+ boxa = pixConnComp(pix10, &pixa2, 8);
+ pix11 = pixaDisplayRandomCmap(pixa2, w, h);
+ pixPaintThroughMask(pix11, pix4, 0, 0, 0);
+ pixt = pixScale(pix11, 0.5, 0.5);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 10 */
+ pixDisplayWithTitle(pixt, 600, 300, "stuff under mask1", rp->display);
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa2);
+
+ /* Paint the background white */
+ cmap = pixGetColormap(pix11);
+ pixcmapResetColor(cmap, 0, 255, 255, 255);
+ regTestWritePixAndCheck(rp, pix11, IFF_PNG); /* 11 */
+ pixt = pixScale(pix11, 0.5, 0.5);
+ pixaAddPix(pixa1, pixt, L_INSERT);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 12 */
+ pixDisplayWithTitle(pixt, 900, 300, "stuff under mask2", rp->display);
+ pixaConvertToPdf(pixa1, 75, 1.0, 0, 0, "Segmentation: newspaper_reg",
+ "/tmp/lept/regout/newspaper.pdf");
+ L_INFO("Output pdf: /tmp/lept/regout/newspaper.pdf\n", rp->testname);
+
+ pixaDestroy(&pixa1);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ pixDestroy(&pix9);
+ pixDestroy(&pix10);
+ pixDestroy(&pix11);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/numa1_reg.c b/leptonica/prog/numa1_reg.c
new file mode 100644
index 00000000..414bdd60
--- /dev/null
+++ b/leptonica/prog/numa1_reg.c
@@ -0,0 +1,321 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * numa1_reg.c
+ *
+ * Tests:
+ * * histograms
+ * * interpolation
+ * * integration/differentiation
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, n, binsize, binstart, nbins;
+l_float32 pi, val, angle, xval, yval, x0, y0, startval, fbinsize;
+l_float32 minval, maxval, meanval, median, variance, rankval, rank, rmsdev;
+GPLOT *gplot;
+NUMA *na, *nahisto, *nax, *nay, *nasx, *nasy;
+NUMA *nadx, *nady, *nafx, *nafy;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "numa1_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/numa1");
+
+ /* -------------------------------------------------------------------*
+ * Histograms *
+ * -------------------------------------------------------------------*/
+ pi = 3.1415926535;
+ na = numaCreate(5000);
+ for (i = 0; i < 500000; i++) {
+ angle = 0.02293 * i * pi;
+ val = (l_float32)(999. * sin(angle));
+ numaAddNumber(na, val);
+ }
+
+ nahisto = numaMakeHistogramClipped(na, 6, 2000);
+ nbins = numaGetCount(nahisto);
+ nax = numaMakeSequence(0, 1, nbins);
+ pix1 = gplotGeneralPix2(nax, nahisto, GPLOT_LINES, "/tmp/lept/numa1/histo1",
+ "example histo 1", "i", "histo[i]");
+ numaDestroy(&nax);
+ numaDestroy(&nahisto);
+
+ nahisto = numaMakeHistogram(na, 1000, &binsize, &binstart);
+ nbins = numaGetCount(nahisto);
+ nax = numaMakeSequence(binstart, binsize, nbins);
+ lept_stderr(" binsize = %d, binstart = %d\n", binsize, binstart);
+ pix2 = gplotGeneralPix2(nax, nahisto, GPLOT_LINES, "/tmp/lept/numa1/histo2",
+ "example histo 2", "i", "histo[i]");
+ numaDestroy(&nax);
+ numaDestroy(&nahisto);
+
+ nahisto = numaMakeHistogram(na, 1000, &binsize, NULL);
+ nbins = numaGetCount(nahisto);
+ nax = numaMakeSequence(0, binsize, nbins);
+ lept_stderr(" binsize = %d, binstart = %d\n", binsize, 0);
+ pix3 = gplotGeneralPix2(nax, nahisto, GPLOT_LINES, "/tmp/lept/numa1/histo3",
+ "example histo 3", "i", "histo[i]");
+ numaDestroy(&nax);
+ numaDestroy(&nahisto);
+
+ nahisto = numaMakeHistogramAuto(na, 1000);
+ nbins = numaGetCount(nahisto);
+ numaGetParameters(nahisto, &startval, &fbinsize);
+ nax = numaMakeSequence(startval, fbinsize, nbins);
+ lept_stderr(" binsize = %7.4f, binstart = %8.3f\n", fbinsize, startval);
+ pix4 = gplotGeneralPix2(nax, nahisto, GPLOT_LINES, "/tmp/lept/numa1/histo4",
+ "example histo 4", "i", "histo[i]");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 2 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 3 */
+ pixa = pixaCreate(4);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ if (rp->display) {
+ pixd = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplayWithTitle(pixd, 0, 0, NULL, 1);
+ pixDestroy(&pixd);
+ }
+ pixaDestroy(&pixa);
+ numaDestroy(&nax);
+ numaDestroy(&nahisto);
+
+ numaGetStatsUsingHistogram(na, 2000, &minval, &maxval, &meanval,
+ &variance, &median, 0.80, &rankval, &nahisto);
+ rmsdev = sqrt((l_float64)variance);
+ numaHistogramGetRankFromVal(nahisto, rankval, &rank);
+ regTestCompareValues(rp, -999.00, minval, 0.1); /* 4 */
+ regTestCompareValues(rp, 999.00, maxval, 0.1); /* 5 */
+ regTestCompareValues(rp, 0.055, meanval, 0.001); /* 6 */
+ regTestCompareValues(rp, 0.30, median, 0.005); /* 7 */
+ regTestCompareValues(rp, 706.41, rmsdev, 0.1); /* 8 */
+ regTestCompareValues(rp, 808.15, rankval, 0.1); /* 9 */
+ regTestCompareValues(rp, 0.800, rank, 0.01); /* 10 */
+ if (rp->display) {
+ lept_stderr("Sin histogram: \n"
+ " min val = %7.3f -- should be -999.00\n"
+ " max val = %7.3f -- should be 999.00\n"
+ " mean val = %7.3f -- should be 0.055\n"
+ " median = %7.3f -- should be 0.30\n"
+ " rmsdev = %7.3f -- should be 706.41\n"
+ " rank val = %7.3f -- should be 808.152\n"
+ " rank = %7.3f -- should be 0.800\n",
+ minval, maxval, meanval, median, rmsdev, rankval, rank);
+ }
+ numaDestroy(&nahisto);
+ numaDestroy(&na);
+
+ /* -------------------------------------------------------------------*
+ * Interpolation *
+ * -------------------------------------------------------------------*/
+ /* Test numaInterpolateEqxInterval() */
+ pixs = pixRead("test8.jpg");
+ na = pixGetGrayHistogramMasked(pixs, NULL, 0, 0, 1);
+ nasy = numaGetPartialSums(na);
+ pix1 = gplotGeneralPix1(nasy, GPLOT_LINES, "/tmp/lept/numa1/int1",
+ "partial sums", NULL, NULL);
+ pix2 = gplotGeneralPix1(na, GPLOT_LINES, "/tmp/lept/numa1/int2",
+ "simple test", NULL, NULL);
+ numaInterpolateEqxInterval(0.0, 1.0, na, L_LINEAR_INTERP,
+ 0.0, 255.0, 15, &nax, &nay);
+ pix3 = gplotGeneralPix2(nax, nay, GPLOT_LINES, "/tmp/lept/numa1/int3",
+ "test interpolation", "pix val", "num pix");
+ numaDestroy(&na);
+ numaDestroy(&nasy);
+ numaDestroy(&nax);
+ numaDestroy(&nay);
+ pixDestroy(&pixs);
+
+ /* Test numaInterpolateArbxInterval() */
+ pixs = pixRead("test8.jpg");
+ na = pixGetGrayHistogramMasked(pixs, NULL, 0, 0, 1);
+ nasy = numaGetPartialSums(na);
+ numaInsertNumber(nasy, 0, 0.0);
+ nasx = numaMakeSequence(0.0, 1.0, 257);
+ numaInterpolateArbxInterval(nasx, nasy, L_LINEAR_INTERP,
+ 10.0, 250.0, 23, &nax, &nay);
+ pix4 = gplotGeneralPix2(nax, nay, GPLOT_LINES, "/tmp/lept/numa1/int4",
+ "arbx interpolation", "pix val", "cum num pix");
+ numaDestroy(&na);
+ numaDestroy(&nasx);
+ numaDestroy(&nasy);
+ numaDestroy(&nax);
+ numaDestroy(&nay);
+ pixDestroy(&pixs);
+
+ /* Test numaInterpolateArbxVal() */
+ pixs = pixRead("test8.jpg");
+ na = pixGetGrayHistogramMasked(pixs, NULL, 0, 0, 1);
+ nasy = numaGetPartialSums(na);
+ numaInsertNumber(nasy, 0, 0.0);
+ nasx = numaMakeSequence(0.0, 1.0, 257);
+ nax = numaMakeSequence(15.0, (250.0 - 15.0) / 23.0, 24);
+ n = numaGetCount(nax);
+ nay = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ numaGetFValue(nax, i, &xval);
+ numaInterpolateArbxVal(nasx, nasy, L_QUADRATIC_INTERP, xval, &yval);
+ numaAddNumber(nay, yval);
+ }
+ pix5 = gplotGeneralPix2(nax, nay, GPLOT_LINES, "/tmp/lept/numa1/int5",
+ "arbx interpolation", "pix val", "cum num pix");
+ numaDestroy(&na);
+ numaDestroy(&nasx);
+ numaDestroy(&nasy);
+ numaDestroy(&nax);
+ numaDestroy(&nay);
+ pixDestroy(&pixs);
+
+ /* Test interpolation */
+ nasx = numaRead("testangle.na");
+ nasy = numaRead("testscore.na");
+ gplot = gplotCreate("/tmp/lept/numa1/int6", GPLOT_PNG, "arbx interpolation",
+ "angle", "score");
+ numaInterpolateArbxInterval(nasx, nasy, L_LINEAR_INTERP,
+ -2.00, 0.0, 50, &nax, &nay);
+ gplotAddPlot(gplot, nax, nay, GPLOT_LINES, "linear");
+ numaDestroy(&nax);
+ numaDestroy(&nay);
+ numaInterpolateArbxInterval(nasx, nasy, L_QUADRATIC_INTERP,
+ -2.00, 0.0, 50, &nax, &nay);
+ gplotAddPlot(gplot, nax, nay, GPLOT_LINES, "quadratic");
+ numaDestroy(&nax);
+ numaDestroy(&nay);
+ pix6 = gplotMakeOutputPix(gplot);
+ gplotDestroy(&gplot);
+ gplot = gplotCreate("/tmp/lept/numa1/int7", GPLOT_PNG, "arbx interpolation",
+ "angle", "score");
+ numaInterpolateArbxInterval(nasx, nasy, L_LINEAR_INTERP,
+ -1.2, -0.8, 50, &nax, &nay);
+ gplotAddPlot(gplot, nax, nay, GPLOT_LINES, "quadratic");
+ pix7 = gplotMakeOutputPix(gplot);
+ gplotDestroy(&gplot);
+ numaFitMax(nay, &yval, nax, &xval);
+ if (rp->display) lept_stderr("max = %f at loc = %f\n", yval, xval);
+ pixa = pixaCreate(7);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 11 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 12 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 13 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 14 */
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 15 */
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 16 */
+ regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 17 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ pixaAddPix(pixa, pix6, L_INSERT);
+ pixaAddPix(pixa, pix7, L_INSERT);
+ if (rp->display) {
+ pixd = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplayWithTitle(pixd, 300, 0, NULL, 1);
+ pixDestroy(&pixd);
+ }
+ pixaDestroy(&pixa);
+ numaDestroy(&nasx);
+ numaDestroy(&nasy);
+ numaDestroy(&nax);
+ numaDestroy(&nay);
+
+ /* -------------------------------------------------------------------*
+ * Integration and differentiation *
+ * -------------------------------------------------------------------*/
+ /* Test integration and differentiation */
+ nasx = numaRead("testangle.na");
+ nasy = numaRead("testscore.na");
+ /* ---------- Plot the derivative ---------- */
+ numaDifferentiateInterval(nasx, nasy, -2.0, 0.0, 50, &nadx, &nady);
+ pix1 = gplotGeneralPix2(nadx, nady, GPLOT_LINES, "/tmp/lept/numa1/diff1",
+ "derivative", "angle", "slope");
+ /* ---------- Plot the original function ----------- */
+ /* and the integral of the derivative; the two */
+ /* should be approximately the same. */
+ gplot = gplotCreate("/tmp/lept/numa1/diff2", GPLOT_PNG, "integ-diff",
+ "angle", "val");
+ numaInterpolateArbxInterval(nasx, nasy, L_LINEAR_INTERP,
+ -2.00, 0.0, 50, &nafx, &nafy);
+ gplotAddPlot(gplot, nafx, nafy, GPLOT_LINES, "function");
+ n = numaGetCount(nadx);
+ numaGetFValue(nafx, 0, &x0);
+ numaGetFValue(nafy, 0, &y0);
+ nay = numaCreate(n);
+ /* (Note: this tests robustness of the integrator: we go from
+ * i = 0, and choose to have only 1 point in the interpolation
+ * there, which is too small and causes the function to bomb out.) */
+ lept_stderr("******************************************************\n");
+ lept_stderr("* Testing error checking: ignore 'npts < 2' error *\n");
+ for (i = 0; i < n; i++) {
+ numaGetFValue(nadx, i, &xval);
+ numaIntegrateInterval(nadx, nady, x0, xval, 2 * i + 1, &yval);
+ numaAddNumber(nay, y0 + yval);
+ }
+ lept_stderr("******************************************************\n");
+ gplotAddPlot(gplot, nafx, nay, GPLOT_LINES, "anti-derivative");
+ pix2 = gplotMakeOutputPix(gplot);
+ gplotDestroy(&gplot);
+ pixa = pixaCreate(2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 18 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 19 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ if (rp->display) {
+ pixd = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplayWithTitle(pixd, 600, 0, NULL, 1);
+ pixDestroy(&pixd);
+ }
+ pixaDestroy(&pixa);
+ numaDestroy(&nasx);
+ numaDestroy(&nasy);
+ numaDestroy(&nafx);
+ numaDestroy(&nafy);
+ numaDestroy(&nadx);
+ numaDestroy(&nady);
+ numaDestroy(&nay);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/numa2_reg.c b/leptonica/prog/numa2_reg.c
new file mode 100644
index 00000000..34786893
--- /dev/null
+++ b/leptonica/prog/numa2_reg.c
@@ -0,0 +1,490 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * numa_reg2.c
+ *
+ * Tests:
+ * * numa windowed stats
+ * * numa extraction from pix on a line
+ * * pixel averages and variances
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+#define DO_ALL 0
+
+/* Tiny helper */
+void SaveColorSquare(PIXA *pixa, l_uint32 rgbval);
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j;
+l_int32 w, h, bw, bh, wpls, rval, gval, bval, same;
+l_uint32 pixel, avergb;
+l_uint32 *lines, *datas;
+l_float32 sum1, sum2, ave1, ave2, ave3, ave4, diff1, diff2;
+l_float32 var1, var2, var3;
+BOX *box1, *box2, *box3;
+NUMA *na, *na1, *na2, *na3, *na4;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pixg, *pixd;
+PIXA *pixa1;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "numa2_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/numa2");
+
+ /* -------------------------------------------------------------------*
+ * Numa-windowed stats *
+ * -------------------------------------------------------------------*/
+ na = numaRead("lyra.5.na");
+ numaWindowedStats(na, 5, &na1, &na2, &na3, &na4);
+ gplotSimple1(na, GPLOT_PNG, "/tmp/lept/numa2/lyra1", "Original");
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/numa2/lyra2", "Mean");
+ gplotSimple1(na2, GPLOT_PNG, "/tmp/lept/numa2/lyra3", "Mean Square");
+ gplotSimple1(na3, GPLOT_PNG, "/tmp/lept/numa2/lyra4", "Variance");
+ gplotSimple1(na4, GPLOT_PNG, "/tmp/lept/numa2/lyra5", "RMS Difference");
+ pix1 = pixRead("/tmp/lept/numa2/lyra1.png");
+ pix2 = pixRead("/tmp/lept/numa2/lyra2.png");
+ pix3 = pixRead("/tmp/lept/numa2/lyra3.png");
+ pix4 = pixRead("/tmp/lept/numa2/lyra4.png");
+ pix5 = pixRead("/tmp/lept/numa2/lyra5.png");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 2 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 3 */
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 4 */
+ pixa1 = pixaCreate(5);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ pixaAddPix(pixa1, pix4, L_INSERT);
+ pixaAddPix(pixa1, pix5, L_INSERT);
+ if (rp->display) {
+ pixd = pixaDisplayTiledInRows(pixa1, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplayWithTitle(pixd, 0, 0, NULL, 1);
+ pixDestroy(&pixd);
+ }
+ pixaDestroy(&pixa1);
+ numaDestroy(&na);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+
+ /* -------------------------------------------------------------------*
+ * Extraction on a line *
+ * -------------------------------------------------------------------*/
+ /* First, make a pretty image */
+ w = h = 200;
+ pixs = pixCreate(w, h, 32);
+ wpls = pixGetWpl(pixs);
+ datas = pixGetData(pixs);
+ for (i = 0; i < 200; i++) {
+ lines = datas + i * wpls;
+ for (j = 0; j < 200; j++) {
+ rval = (l_int32)((255. * j) / w + (255. * i) / h);
+ gval = (l_int32)((255. * 2 * j) / w + (255. * 2 * i) / h) % 255;
+ bval = (l_int32)((255. * 4 * j) / w + (255. * 4 * i) / h) % 255;
+ composeRGBPixel(rval, gval, bval, &pixel);
+ lines[j] = pixel;
+ }
+ }
+ pixg = pixConvertTo8(pixs, 0); /* and a grayscale version */
+ regTestWritePixAndCheck(rp, pixg, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pixg, 0, 300, NULL, rp->display);
+
+ na1 = pixExtractOnLine(pixg, 20, 20, 180, 20, 1);
+ na2 = pixExtractOnLine(pixg, 40, 30, 40, 170, 1);
+ na3 = pixExtractOnLine(pixg, 20, 170, 180, 30, 1);
+ na4 = pixExtractOnLine(pixg, 20, 190, 180, 10, 1);
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/numa2/ext1", "Horizontal");
+ gplotSimple1(na2, GPLOT_PNG, "/tmp/lept/numa2/ext2", "Vertical");
+ gplotSimple1(na3, GPLOT_PNG, "/tmp/lept/numa2/ext3",
+ "Slightly more horizontal than vertical");
+ gplotSimple1(na4, GPLOT_PNG, "/tmp/lept/numa2/ext4",
+ "Slightly more vertical than horizontal");
+ pix1 = pixRead("/tmp/lept/numa2/ext1.png");
+ pix2 = pixRead("/tmp/lept/numa2/ext2.png");
+ pix3 = pixRead("/tmp/lept/numa2/ext3.png");
+ pix4 = pixRead("/tmp/lept/numa2/ext4.png");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 6 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 7 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 8 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 9 */
+ pixa1 = pixaCreate(4);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ pixaAddPix(pixa1, pix4, L_INSERT);
+ if (rp->display) {
+ pixd = pixaDisplayTiledInRows(pixa1, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplayWithTitle(pixd, 300, 0, NULL, 1);
+ pixDestroy(&pixd);
+ }
+ pixaDestroy(&pixa1);
+ pixDestroy(&pixg);
+ pixDestroy(&pixs);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+
+ /* -------------------------------------------------------------------*
+ * Row and column pixel sums *
+ * -------------------------------------------------------------------*/
+ /* Sum by columns in two halves (left and right) */
+ pixs = pixRead("test8.jpg");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ box1 = boxCreate(0, 0, w / 2, h);
+ box2 = boxCreate(w / 2, 0, w - 2 / 2, h);
+ na1 = pixAverageByColumn(pixs, box1, L_BLACK_IS_MAX);
+ na2 = pixAverageByColumn(pixs, box2, L_BLACK_IS_MAX);
+ numaJoin(na1, na2, 0, -1);
+ na3 = pixAverageByColumn(pixs, NULL, L_BLACK_IS_MAX);
+ numaSimilar(na1, na3, 0.0, &same); /* for columns */
+ regTestCompareValues(rp, 1, same, 0); /* 10 */
+ pix1 = pixConvertTo32(pixs);
+ pixRenderPlotFromNumaGen(&pix1, na3, L_HORIZONTAL_LINE, 3, h / 2, 80, 1,
+ 0xff000000);
+ pixRenderPlotFromNuma(&pix1, na3, L_PLOT_AT_BOT, 3, 80, 0xff000000);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+
+ /* Sum by rows in two halves (top and bottom) */
+ box1 = boxCreate(0, 0, w, h / 2);
+ box2 = boxCreate(0, h / 2, w, h - h / 2);
+ na1 = pixAverageByRow(pixs, box1, L_WHITE_IS_MAX);
+ na2 = pixAverageByRow(pixs, box2, L_WHITE_IS_MAX);
+ numaJoin(na1, na2, 0, -1);
+ na3 = pixAverageByRow(pixs, NULL, L_WHITE_IS_MAX);
+ numaSimilar(na1, na3, 0.0, &same); /* for rows */
+ regTestCompareValues(rp, 1, same, 0); /* 11 */
+ pixRenderPlotFromNumaGen(&pix1, na3, L_VERTICAL_LINE, 3, w / 2, 80, 1,
+ 0x00ff0000);
+ pixRenderPlotFromNuma(&pix1, na3, L_PLOT_AT_RIGHT, 3, 80, 0x00ff0000);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 12 */
+ pixDisplayWithTitle(pix1, 0, 600, NULL, rp->display);
+ pixDestroy(&pix1);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+
+ /* Average left by rows; right by columns; compare totals */
+ box1 = boxCreate(0, 0, w / 2, h);
+ box2 = boxCreate(w / 2, 0, w - 2 / 2, h);
+ na1 = pixAverageByRow(pixs, box1, L_WHITE_IS_MAX);
+ na2 = pixAverageByColumn(pixs, box2, L_WHITE_IS_MAX);
+ numaGetSum(na1, &sum1); /* sum of averages of left box */
+ numaGetSum(na2, &sum2); /* sum of averages of right box */
+ ave1 = sum1 / h;
+ ave2 = 2.0 * sum2 / w;
+ ave3 = 0.5 * (ave1 + ave2); /* average over both halves */
+ regTestCompareValues(rp, 189.59, ave1, 0.01); /* 13 */
+ regTestCompareValues(rp, 207.89, ave2, 0.01); /* 14 */
+
+ if (rp->display) {
+ lept_stderr("ave1 = %8.4f\n", sum1 / h);
+ lept_stderr("ave2 = %8.4f\n", 2.0 * sum2 / w);
+ }
+ pixAverageInRect(pixs, NULL, NULL, 0, 255, 1, &ave4); /* entire image */
+ diff1 = ave4 - ave3;
+ diff2 = w * h * ave4 - (0.5 * w * sum1 + h * sum2);
+ regTestCompareValues(rp, 0.0, diff1, 0.001); /* 15 */
+ regTestCompareValues(rp, 10.0, diff2, 10.0); /* 16 */
+
+ /* Variance left and right halves. Variance doesn't average
+ * in a simple way, unlike pixel sums. */
+ pixVarianceInRect(pixs, box1, &var1); /* entire image */
+ pixVarianceInRect(pixs, box2, &var2); /* entire image */
+ pixVarianceInRect(pixs, NULL, &var3); /* entire image */
+ regTestCompareValues(rp, 82.06, 0.5 * (var1 + var2), 0.01); /* 17 */
+ regTestCompareValues(rp, 82.66, var3, 0.01); /* 18 */
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+
+ /* -------------------------------------------------------------------*
+ * Row and column variances *
+ * -------------------------------------------------------------------*/
+ /* Display variance by rows and columns */
+ box1 = boxCreate(415, 0, 130, 425);
+ boxGetGeometry(box1, NULL, NULL, &bw, &bh);
+ na1 = pixVarianceByRow(pixs, box1);
+ na2 = pixVarianceByColumn(pixs, box1);
+ pix1 = pixConvertTo32(pixs);
+ pix2 = pixCopy(NULL, pix1);
+ pixRenderPlotFromNumaGen(&pix1, na1, L_VERTICAL_LINE, 3, 415, 100, 1,
+ 0xff000000);
+ pixRenderPlotFromNumaGen(&pix1, na2, L_HORIZONTAL_LINE, 3, bh / 2, 100, 1,
+ 0x00ff0000);
+ pixRenderPlotFromNuma(&pix2, na1, L_PLOT_AT_LEFT, 3, 60, 0x00ff0000);
+ pixRenderPlotFromNuma(&pix2, na1, L_PLOT_AT_MID_VERT, 3, 60, 0x0000ff00);
+ pixRenderPlotFromNuma(&pix2, na1, L_PLOT_AT_RIGHT, 3, 60, 0xff000000);
+ pixRenderPlotFromNuma(&pix2, na2, L_PLOT_AT_TOP, 3, 60, 0x0000ff00);
+ pixRenderPlotFromNuma(&pix2, na2, L_PLOT_AT_MID_HORIZ, 3, 60, 0xff000000);
+ pixRenderPlotFromNuma(&pix2, na2, L_PLOT_AT_BOT, 3, 60, 0x00ff0000);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 19 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 20 */
+ pixa1 = pixaCreate(2);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ if (rp->display) {
+ pixd = pixaDisplayTiledInRows(pixa1, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplayWithTitle(pixd, 400, 600, NULL, 1);
+ pixDestroy(&pixd);
+ }
+ pixaDestroy(&pixa1);
+ boxDestroy(&box1);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ pixDestroy(&pixs);
+
+ /* Again on a different image */
+ pix1 = pixRead("boxedpage.jpg");
+ pix2 = pixConvertTo8(pix1, 0);
+ pixGetDimensions(pix2, &w, &h, NULL);
+ na1 = pixVarianceByRow(pix2, NULL);
+ pix3 = pixConvertTo32(pix1);
+ pixRenderPlotFromNumaGen(&pix3, na1, L_VERTICAL_LINE, 3, 0, 70, 1,
+ 0xff000000);
+ na2 = pixVarianceByColumn(pix2, NULL);
+ pixRenderPlotFromNumaGen(&pix3, na2, L_HORIZONTAL_LINE, 3, bh - 1, 70, 1,
+ 0x00ff0000);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 21 */
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+
+ /* Again, with an erosion */
+ pix4 = pixErodeGray(pix2, 3, 21);
+ na1 = pixVarianceByRow(pix4, NULL);
+ pix5 = pixConvertTo32(pix1);
+ pixRenderPlotFromNumaGen(&pix5, na1, L_VERTICAL_LINE, 3, 30, 70, 1,
+ 0xff000000);
+ na2 = pixVarianceByColumn(pix4, NULL);
+ pixRenderPlotFromNumaGen(&pix5, na2, L_HORIZONTAL_LINE, 3, bh - 1, 70, 1,
+ 0x00ff0000);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 22 */
+ pixa1 = pixaCreate(2);
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ pixaAddPix(pixa1, pix5, L_INSERT);
+ if (rp->display) {
+ pixd = pixaDisplayTiledInRows(pixa1, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplayWithTitle(pixd, 800, 600, NULL, 1);
+ pixDestroy(&pixd);
+ }
+ pixaDestroy(&pixa1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix4);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+
+ /* -------------------------------------------------------------------*
+ * Windowed variance along a line *
+ * -------------------------------------------------------------------*/
+ pix1 = pixRead("boxedpage.jpg");
+ pix2 = pixConvertTo8(pix1, 0);
+ pixGetDimensions(pix2, &w, &h, NULL);
+ pix3 = pixCopy(NULL, pix1);
+
+ /* Plot along horizontal line */
+ pixWindowedVarianceOnLine(pix2, L_HORIZONTAL_LINE, h / 2 - 30, 0,
+ w, 5, &na1);
+ pixRenderPlotFromNumaGen(&pix1, na1, L_HORIZONTAL_LINE, 3, h / 2 - 30,
+ 80, 1, 0xff000000);
+ pixRenderPlotFromNuma(&pix3, na1, L_PLOT_AT_TOP, 3, 60, 0x00ff0000);
+ pixRenderPlotFromNuma(&pix3, na1, L_PLOT_AT_BOT, 3, 60, 0x0000ff00);
+
+ /* Plot along vertical line */
+ pixWindowedVarianceOnLine(pix2, L_VERTICAL_LINE, 0.78 * w, 0,
+ h, 5, &na2);
+ pixRenderPlotFromNumaGen(&pix1, na2, L_VERTICAL_LINE, 3, 0.78 * w, 60,
+ 1, 0x00ff0000);
+ pixRenderPlotFromNuma(&pix3, na2, L_PLOT_AT_LEFT, 3, 60, 0xff000000);
+ pixRenderPlotFromNuma(&pix3, na2, L_PLOT_AT_RIGHT, 3, 60, 0x00ff0000);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 23 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 24 */
+ pixa1 = pixaCreate(2);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ if (rp->display) {
+ pixd = pixaDisplayTiledInRows(pixa1, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplayWithTitle(pixd, 1200, 600, NULL, 1);
+ pixDestroy(&pixd);
+ }
+ pixaDestroy(&pixa1);
+ pixDestroy(&pix2);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+
+ /* -------------------------------------------------------------------*
+ * Test pixel average function for gray *
+ * -------------------------------------------------------------------*/
+ pix1 = pixRead("lyra.005.jpg");
+ pix2 = pixConvertRGBToLuminance(pix1);
+ box1 = boxCreate(20, 150, 700, 515);
+ pix3 = pixClipRectangle(pix2, box1, NULL);
+ /* No mask, no box, different subsampling */
+ pixAverageInRect(pix3, NULL, NULL, 0, 255, 1, &ave1); /* no mask */
+ regTestCompareValues(rp, 176.97, ave1, 0.1); /* 25 */
+ if (rp->display) lept_stderr("ave1 = %6.2f\n", ave1);
+ pixAverageInRect(pix3, NULL, NULL, 0, 255, 2, &ave2); /* no mask */
+ regTestCompareValues(rp, ave1, ave2, 0.1); /* 26 */
+ if (rp->display) lept_stderr("ave2 = %6.2f\n", ave2);
+ /* Mask allows bg, no box */
+ pix4 = pixThresholdToBinary(pix3, 80); /* use light pixels */
+ pixAverageInRect(pix3, pix4, NULL, 0, 255, 1, &ave1); /* mask bg */
+ regTestCompareValues(rp, 187.58, ave1, 0.1); /* 27 */
+ if (rp->display) lept_stderr("ave = %6.2f\n", ave1);
+ /* Mask allows fg, no box */
+ pixInvert(pix4, pix4); /* use dark pixels */
+ pixAverageInRect(pix3, pix4, NULL, 0, 255, 1, &ave1); /* mask fg */
+ regTestCompareValues(rp, 46.37, ave1, 0.1); /* 28 */
+ if (rp->display) lept_stderr("ave = %6.2f\n", ave1);
+ /* Mask allows fg, no box, restricted range with samples */
+ pixAverageInRect(pix3, pix4, NULL, 50, 60, 1, &ave1); /* mask fg */
+ regTestCompareValues(rp, 55.18, ave1, 0.1); /* 29 */
+ if (rp->display) lept_stderr("ave = %6.2f\n", ave1);
+ /* Mask allows fg, no box, restricted range without samples */
+ pixAverageInRect(pix3, pix4, NULL, 100, 255, 1, &ave1);
+ regTestCompareValues(rp, 0.0, ave1, 0.0); /* 30 */ /* mask fg */
+ if (rp->display) lept_stderr("ave = %6.2f\n", ave1);
+ /* No mask, use box */
+ box2 = boxCreate(100, 100, 200, 150);
+ pixAverageInRect(pix3, NULL, box2, 0, 255, 1, &ave1); /* no mask */
+ regTestCompareValues(rp, 165.63, ave1, 0.1); /* 31 */
+ if (rp->display) lept_stderr("ave1 = %6.2f\n", ave1);
+ /* No mask, pix cropped to box */
+ pixInvert(pix4, pix4); /* use light pixels */
+ pix5 = pixClipRectangle(pix3, box2, NULL);
+ pixAverageInRect(pix5, NULL, NULL, 0, 255, 1, &ave2); /* no mask */
+ regTestCompareValues(rp, ave1, ave2, 0.1); /* 32 */
+ if (rp->display) lept_stderr("ave2 = %6.2f\n", ave2);
+ /* Mask allows bg, use box */
+ pixAverageInRect(pix3, pix4, box2, 0, 255, 1, &ave1); /* mask bg */
+ regTestCompareValues(rp, 175.65, ave1, 0.1); /* 33 */
+ if (rp->display) lept_stderr("ave1 = %6.2f\n", ave1);
+ /* Cropped mask allows bg, pix cropped to box */
+ pix6 = pixThresholdToBinary(pix5, 80); /* use light pixels */
+ pixAverageInRect(pix5, pix6, NULL, 0, 255, 1, &ave2);
+ regTestCompareValues(rp, ave1, ave2, 0.1); /* 34 */
+ if (rp->display) lept_stderr("ave2 = %6.2f\n", ave2);
+ /* Mask allows bg, use box, restricted range */
+ pixAverageInRect(pix3, pix4, box2, 100, 125, 1, &ave1);
+ regTestCompareValues(rp, 112.20, ave1, 0.1); /* 35 */
+ if (rp->display) lept_stderr("ave = %6.2f\n", ave1);
+ /* Cropped mask allows bg, pix cropped to box, restricted range */
+ pixAverageInRect(pix5, pix6, NULL, 100, 125, 1, &ave2);
+ regTestCompareValues(rp, ave1, ave2, 0.1); /* 36 */
+ if (rp->display) lept_stderr("ave = %6.2f\n", ave2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+
+ /* -------------------------------------------------------------------*
+ * Test pixel average function for color *
+ * -------------------------------------------------------------------*/
+ pix1 = pixRead("lyra.005.jpg");
+ box1 = boxCreate(20, 150, 700, 515);
+ pix2 = pixClipRectangle(pix1, box1, NULL);
+ pixa1 = pixaCreate(0);
+ /* No mask, no box, different subsampling */
+ pixAverageInRectRGB(pix2, NULL, NULL, 1, &avergb);
+ regTestCompareValues(rp, 0xc7b09000, avergb, 0); /* 37 */
+ SaveColorSquare(pixa1, avergb);
+ pixAverageInRectRGB(pix2, NULL, NULL, 10, &avergb);
+ regTestCompareValues(rp, 0xc7af8f00, avergb, 0); /* 38 */
+ SaveColorSquare(pixa1, avergb);
+ /* Mask allows bg, no box */
+ pix3 = pixConvertTo1(pix2, 128); /* use light pixels */
+ pixAverageInRectRGB(pix2, pix3, NULL, 1, &avergb);
+ regTestCompareValues(rp, 0xd5bf9d00, avergb, 0); /* 39 */
+ SaveColorSquare(pixa1, avergb);
+ /* Mask allows fg, no box */
+ pixInvert(pix3, pix3); /* use dark pixels */
+ pixAverageInRectRGB(pix2, pix3, NULL, 1, &avergb);
+ regTestCompareValues(rp, 0x5c3b2800, avergb, 0); /* 40 */
+ SaveColorSquare(pixa1, avergb);
+ /* Mask allows bg, box at lower left */
+ pixInvert(pix3, pix3); /* use light pixels */
+ box2 = boxCreate(20, 400, 100, 100);
+ pixAverageInRectRGB(pix2, pix3, box2, 1, &avergb);
+ regTestCompareValues(rp, 0xbba48500, avergb, 0); /* 41 */
+ SaveColorSquare(pixa1, avergb);
+ /* Mask allows bg, box at upper right */
+ box3 = boxCreate(600, 20, 100, 100);
+ pixAverageInRectRGB(pix2, pix3, box3, 1, &avergb);
+ regTestCompareValues(rp, 0xfdfddd00, avergb, 0); /* 42 */
+ SaveColorSquare(pixa1, avergb);
+ if (rp->display) {
+ pix4 = pixaDisplayTiledInRows(pixa1, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplay(pix4, 0, 800);
+ pixDestroy(&pix4);
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ boxDestroy(&box3);
+ pixaDestroy(&pixa1);
+
+ return regTestCleanup(rp);;
+}
+
+
+void SaveColorSquare(PIXA *pixa,
+ l_uint32 rgbval) {
+ PIX *pixc = pixMakeColorSquare(rgbval, 0, 1, L_ADD_BELOW, 0xff000000);
+ pixaAddPix(pixa, pixc, L_INSERT);
+}
+
diff --git a/leptonica/prog/numa3_reg.c b/leptonica/prog/numa3_reg.c
new file mode 100644
index 00000000..826fe36a
--- /dev/null
+++ b/leptonica/prog/numa3_reg.c
@@ -0,0 +1,215 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * numa3_reg.c
+ *
+ * Tests:
+ * * rank extraction
+ * * numa-morphology
+ * * find threshold from numa
+ * * insertion in sorted array
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char buf1[64], buf2[64];
+l_int32 i, n, hw, thresh, same, ival;
+l_float32 val, maxval, rank;
+NUMA *na, *nax, *nay, *nap, *nasy, *na1, *na2, *na3, *na4;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "numa3_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/numa3");
+
+ /* -------------------------------------------------------------------*
+ * Rank extraction *
+ * -------------------------------------------------------------------*/
+ /* Rank extraction with interpolation */
+ pixs = pixRead("test8.jpg");
+ nasy= pixGetGrayHistogramMasked(pixs, NULL, 0, 0, 1);
+ numaMakeRankFromHistogram(0.0, 1.0, nasy, 350, &nax, &nay);
+ pix1 = gplotGeneralPix2(nax, nay, GPLOT_LINES, "/tmp/lept/numa1/rank1",
+ "test rank extractor", "pix val", "rank val");
+ numaDestroy(&nasy);
+ numaDestroy(&nax);
+ numaDestroy(&nay);
+ pixDestroy(&pixs);
+
+ /* Rank extraction, point by point */
+ pixs = pixRead("test8.jpg");
+ nap = numaCreate(200);
+ pixGetRankValueMasked(pixs, NULL, 0, 0, 2, 0.0, &val, &na);
+ for (i = 0; i < 101; i++) {
+ rank = 0.01 * i;
+ numaHistogramGetValFromRank(na, rank, &val);
+ numaAddNumber(nap, val);
+ }
+ pix2 = gplotGeneralPix1(nap, GPLOT_LINES, "/tmp/lept/numa1/rank2",
+ "rank value", NULL, NULL);
+ pixa = pixaCreate(2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ if (rp->display) {
+ pixd = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplayWithTitle(pixd, 900, 0, NULL, 1);
+ pixDestroy(&pixd);
+ }
+ pixaDestroy(&pixa);
+ numaDestroy(&na);
+ numaDestroy(&nap);
+ pixDestroy(&pixs);
+
+ /* -------------------------------------------------------------------*
+ * Numa-morphology *
+ * -------------------------------------------------------------------*/
+ na = numaRead("lyra.5.na");
+ pix1 = gplotGeneralPix1(na, GPLOT_LINES, "/tmp/lept/numa1/lyra1",
+ "Original", NULL, NULL);
+ na1 = numaErode(na, 21);
+ pix2 = gplotGeneralPix1(na1, GPLOT_LINES, "/tmp/lept/numa1/lyra2",
+ "Erosion", NULL, NULL);
+ na2 = numaDilate(na, 21);
+ pix3 = gplotGeneralPix1(na2, GPLOT_LINES, "/tmp/lept/numa1/lyra3",
+ "Dilation", NULL, NULL);
+ na3 = numaOpen(na, 21);
+ pix4 = gplotGeneralPix1(na3, GPLOT_LINES, "/tmp/lept/numa1/lyra4",
+ "Opening", NULL, NULL);
+ na4 = numaClose(na, 21);
+ pix5 = gplotGeneralPix1(na4, GPLOT_LINES, "/tmp/lept/numa1/lyra5",
+ "Closing", NULL, NULL);
+ pixa = pixaCreate(2);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 3 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 4 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 5 */
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 6 */
+ if (rp->display) {
+ pixd = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplayWithTitle(pixd, 1200, 0, NULL, 1);
+ pixDestroy(&pixd);
+ }
+ pixaDestroy(&pixa);
+ numaDestroy(&na);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+ pixaDestroy(&pixa);
+
+ /* -------------------------------------------------------------------*
+ * Find threshold from numa *
+ * -------------------------------------------------------------------*/
+ na1 = numaRead("two-peak-histo.na");
+ na4 = numaCreate(0);
+ pixa = pixaCreate(0);
+ for (hw = 2; hw < 21; hw += 2) {
+ na2 = numaWindowedMean(na1, hw); /* smoothing */
+ numaGetMax(na2, &maxval, NULL);
+ na3 = numaTransform(na2, 0.0, 1.0 / maxval);
+ numaFindLocForThreshold(na3, 0, &thresh, NULL);
+ numaAddNumber(na4, thresh);
+ snprintf(buf1, sizeof(buf1), "/tmp/lept/numa1/histoplot-%d", hw);
+ snprintf(buf2, sizeof(buf2), "halfwidth = %d, skip = 20, thresh = %d",
+ hw, thresh);
+ pix1 = gplotGeneralPix1(na3, GPLOT_LINES, buf1, buf2, NULL, NULL);
+ if (hw == 4 || hw == 20)
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 7, 8 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ }
+ numaWrite("/tmp/lept/numa1/threshvals.na", na4);
+ regTestCheckFile(rp, "/tmp/lept/numa1/threshvals.na"); /* 9 */
+ L_INFO("writing /tmp/lept/numa1/histoplots.pdf\n", "numa1_reg");
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0,
+ "Effect of smoothing on threshold value",
+ "/tmp/lept/numa1/histoplots.pdf");
+ numaDestroy(&na1);
+ numaDestroy(&na4);
+ pixaDestroy(&pixa);
+
+ /* -------------------------------------------------------------------*
+ * Insertion in a sorted array *
+ * -------------------------------------------------------------------*/
+ na1 = numaCreate(0);
+ srand(5);
+ numaAddNumber(na1, 27);
+ numaAddNumber(na1, 13);
+ for (i = 0; i < 70; i++) {
+ genRandomIntOnInterval(0, 200, 0, &ival);
+ numaAddSorted(na1, ival);
+ }
+ if (rp->display) numaWriteStderr(na1);
+ na2 = numaSort(NULL, na1, L_SORT_INCREASING);
+ numaReverse(na2, na2);
+ numaSimilar(na1, na2, 0.0, &same);
+ regTestCompareValues(rp, 1, same, 0.0); /* 10 */
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+
+ na1 = numaCreate(0);
+ srand(6);
+ numaAddNumber(na1, 13);
+ numaAddNumber(na1, 27);
+ for (i = 0; i < 70; i++) {
+ genRandomIntOnInterval(0, 200, 0, &ival);
+ numaAddSorted(na1, ival);
+ }
+ if (rp->display) numaWriteStderr(na1);
+ na2 = numaSort(NULL, na1, L_SORT_DECREASING);
+ numaReverse(na2, na2);
+ numaSimilar(na1, na2, 0.0, &same);
+ regTestCompareValues(rp, 1, same, 0.0); /* 11 */
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/numaranktest.c b/leptonica/prog/numaranktest.c
new file mode 100644
index 00000000..083f0fbc
--- /dev/null
+++ b/leptonica/prog/numaranktest.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * numaranktest.c
+ *
+ * Test on 8 bpp grayscale (e.g., w91frag.jpg)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 BIN_SIZE = 1;
+
+int main(int argc,
+ char **argv)
+{
+char *filein;
+l_int32 i, j, w, h, d, sampling;
+l_float32 rank, rval;
+l_uint32 val;
+NUMA *na, *nah, *nar, *nav;
+PIX *pix;
+static char mainName[] = "numaranktest";
+
+ if (argc != 3)
+ return ERROR_INT(" Syntax: numaranktest filein sampling", mainName, 1);
+ filein = argv[1];
+ sampling = atoi(argv[2]);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/numa");
+
+ if ((pix = pixRead(filein)) == NULL)
+ return ERROR_INT("pix not made", mainName, 1);
+ pixGetDimensions(pix, &w, &h, &d);
+ if (d != 8)
+ return ERROR_INT("d != 8 bpp", mainName, 1);
+
+ na = numaCreate(0);
+ for (i = 0; i < h; i += sampling) {
+ for (j = 0; j < w; j += sampling) {
+ pixGetPixel(pix, j, i, &val);
+ numaAddNumber(na, val);
+ }
+ }
+ nah = numaMakeHistogramClipped(na, BIN_SIZE, 255);
+
+ nar = numaCreate(0);
+ for (rval = 0.0; rval < 256.0; rval += 2.56) {
+ numaHistogramGetRankFromVal(nah, rval, &rank);
+ numaAddNumber(nar, rank);
+ }
+ gplotSimple1(nar, GPLOT_PNG, "/tmp/lept/numa/rank", "rank vs val");
+ l_fileDisplay("/tmp/lept/numa/rank.png", 0, 0, 1.0);
+
+ nav = numaCreate(0);
+ for (rank = 0.0; rank <= 1.0; rank += 0.01) {
+ numaHistogramGetValFromRank(nah, rank, &rval);
+ numaAddNumber(nav, rval);
+ }
+ gplotSimple1(nav, GPLOT_PNG, "/tmp/lept/numa/val", "val vs rank");
+ l_fileDisplay("/tmp/lept/numa/val.png", 750, 0, 1.0);
+
+ pixDestroy(&pix);
+ numaDestroy(&na);
+ numaDestroy(&nah);
+ numaDestroy(&nar);
+ numaDestroy(&nav);
+ return 0;
+}
diff --git a/leptonica/prog/ortiz-02.tif b/leptonica/prog/ortiz-02.tif
new file mode 100644
index 00000000..0609c118
--- /dev/null
+++ b/leptonica/prog/ortiz-02.tif
Binary files differ
diff --git a/leptonica/prog/ortiz-03.tif b/leptonica/prog/ortiz-03.tif
new file mode 100644
index 00000000..50a28f08
--- /dev/null
+++ b/leptonica/prog/ortiz-03.tif
Binary files differ
diff --git a/leptonica/prog/ortiz-04.tif b/leptonica/prog/ortiz-04.tif
new file mode 100644
index 00000000..1c57a2d1
--- /dev/null
+++ b/leptonica/prog/ortiz-04.tif
Binary files differ
diff --git a/leptonica/prog/ortiz-05.tif b/leptonica/prog/ortiz-05.tif
new file mode 100644
index 00000000..98bfa062
--- /dev/null
+++ b/leptonica/prog/ortiz-05.tif
Binary files differ
diff --git a/leptonica/prog/otsutest1.c b/leptonica/prog/otsutest1.c
new file mode 100644
index 00000000..1584a51e
--- /dev/null
+++ b/leptonica/prog/otsutest1.c
@@ -0,0 +1,161 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * otsutest1.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+static const l_int32 NTests = 5;
+static const l_int32 gaussmean1[5] = {20, 40, 60, 80, 60};
+static const l_int32 gaussstdev1[5] = {10, 20, 20, 20, 30};
+static const l_int32 gaussmean2[5] = {220, 200, 140, 180, 150};
+static const l_int32 gaussstdev2[5] = {15, 20, 40, 20, 30};
+static const l_float32 gaussfract1[5] = {0.2f, 0.3f, 0.1f, 0.5f, 0.3f};
+static char buf[256];
+
+static l_int32 GenerateSplitPlot(l_int32 i);
+static NUMA *MakeGaussian(l_int32 mean, l_int32 stdev, l_float32 fract);
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i;
+PIX *pix;
+PIXA *pixa;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/otsu");
+ for (i = 0; i < NTests; i++)
+ GenerateSplitPlot(i);
+
+ /* Read the results back in ... */
+ pixa = pixaCreate(0);
+ for (i = 0; i < NTests; i++) {
+ snprintf(buf, sizeof(buf), "/tmp/lept/otsu/plot.%d.png", i);
+ pix = pixRead(buf);
+ pixaAddPix(pixa, pix, L_INSERT);
+ snprintf(buf, sizeof(buf), "/tmp/lept/otsu/plots.%d.png", i);
+ pix = pixRead(buf);
+ pixaAddPix(pixa, pix, L_INSERT);
+ }
+
+ /* ... and save into a tiled pix */
+ pix = pixaDisplayTiledInColumns(pixa, 2, 1.0, 25, 0);
+ pixWrite("/tmp/lept/otsu/plot.png", pix, IFF_PNG);
+ pixDisplay(pix, 100, 100);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix);
+ return 0;
+}
+
+
+static l_int32
+GenerateSplitPlot(l_int32 i)
+{
+char title[256];
+l_int32 split;
+l_float32 ave1, ave2, num1, num2, maxnum, maxscore;
+GPLOT *gplot;
+NUMA *na1, *na2, *nascore, *nax, *nay;
+
+ /* Generate a fake histogram composed of 2 gaussians */
+ na1 = MakeGaussian(gaussmean1[i], gaussstdev1[i], gaussfract1[i]);
+ na2 = MakeGaussian(gaussmean2[i], gaussstdev1[i], 1.0 - gaussfract1[i]);
+ numaArithOp(na1, na1, na2, L_ARITH_ADD);
+
+ /* Otsu splitting */
+ numaSplitDistribution(na1, 0.08, &split, &ave1, &ave2, &num1, &num2,
+ &nascore);
+ lept_stderr("split = %d, ave1 = %6.1f, ave2 = %6.1f\n", split, ave1, ave2);
+ lept_stderr("num1 = %8.0f, num2 = %8.0f\n", num1, num2);
+
+ /* Prepare for plotting a vertical line at the split point */
+ nax = numaMakeConstant(split, 2);
+ numaGetMax(na1, &maxnum, NULL);
+ nay = numaMakeConstant(0, 2);
+ numaReplaceNumber(nay, 1, (l_int32)(0.5 * maxnum));
+
+ /* Plot the input histogram with the split location */
+ snprintf(buf, sizeof(buf), "/tmp/lept/otsu/plot.%d", i);
+ snprintf(title, sizeof(title), "Plot %d", i);
+ gplot = gplotCreate(buf, GPLOT_PNG,
+ "Histogram: mixture of 2 gaussians",
+ "Grayscale value", "Number of pixels");
+ gplotAddPlot(gplot, NULL, na1, GPLOT_LINES, title);
+ gplotAddPlot(gplot, nax, nay, GPLOT_LINES, NULL);
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+
+ /* Plot the score function */
+ snprintf(buf, sizeof(buf), "/tmp/lept/otsu/plots.%d", i);
+ snprintf(title, sizeof(title), "Plot %d", i);
+ gplot = gplotCreate(buf, GPLOT_PNG,
+ "Otsu score function for splitting",
+ "Grayscale value", "Score");
+ gplotAddPlot(gplot, NULL, nascore, GPLOT_LINES, title);
+ numaGetMax(nascore, &maxscore, NULL);
+ numaReplaceNumber(nay, 1, maxscore);
+ gplotAddPlot(gplot, nax, nay, GPLOT_LINES, NULL);
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ numaDestroy(&nax);
+ numaDestroy(&nay);
+ numaDestroy(&nascore);
+ return 0;
+}
+
+
+static NUMA *
+MakeGaussian(l_int32 mean, l_int32 stdev, l_float32 fract)
+{
+l_int32 i, total;
+l_float32 norm, val;
+NUMA *na;
+
+ na = numaMakeConstant(0.0, 256);
+ norm = fract / ((l_float32)stdev * sqrt(2 * 3.14159));
+ total = 0;
+ for (i = 0; i < 256; i++) {
+ val = norm * 1000000. * exp(-(l_float32)((i - mean) * (i - mean)) /
+ (l_float32)(2 * stdev * stdev));
+ total += (l_int32)val;
+ numaSetValue(na, i, val);
+ }
+ lept_stderr("Total = %d\n", total);
+
+ return na;
+}
+
diff --git a/leptonica/prog/otsutest2.c b/leptonica/prog/otsutest2.c
new file mode 100644
index 00000000..cbe94f45
--- /dev/null
+++ b/leptonica/prog/otsutest2.c
@@ -0,0 +1,129 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * otsutest2.c
+ *
+ * This demonstrates the usefulness of the modified version of Otsu
+ * for thresholding an image that doesn't have a well-defined
+ * background color.
+ *
+ * Standard Otsu binarization is done with scorefract = 0.0, which
+ * returns the threshold at the maximum value of the score. However.
+ * this value is up on the shoulder of the background, and its
+ * use causes some of the dark background to be binarized as foreground.
+ *
+ * Using the modified Otsu with scorefract = 0.1 returns a threshold
+ * at the lowest value of this histogram such that the score
+ * is at least 0.9 times the maximum value of the score. This allows
+ * the threshold to be taken in the histogram minimum between
+ * the fg and bg peaks, producing a much cleaner binarization.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+
+int main(int argc,
+ char **argv)
+{
+char textstr[256];
+l_int32 i, thresh, fgval, bgval;
+l_float32 scorefract;
+L_BMF *bmf;
+PIX *pixs, *pixb, *pixg, *pixp, *pix1, *pix2, *pix3;
+PIXA *pixa1, *pixad;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/otsu");
+
+ pixs = pixRead("1555.007.jpg");
+ pixg = pixConvertTo8(pixs, 0);
+ bmf = bmfCreate(NULL, 8);
+ pixad = pixaCreate(0);
+ for (i = 0; i < 3; i++) {
+ pixa1 = pixaCreate(2);
+ scorefract = 0.1 * i;
+
+ /* Get a 1 bpp version; use a single tile */
+ pixOtsuAdaptiveThreshold(pixg, 2000, 2000, 0, 0, scorefract,
+ NULL, &pixb);
+ pixaAddPix(pixa1, pixg, L_COPY);
+ pixaAddPix(pixa1, pixb, L_INSERT);
+
+ /* Show the histogram of gray values and the split location */
+ pixSplitDistributionFgBg(pixg, scorefract, 1,
+ &thresh, &fgval, &bgval, &pixp);
+ lept_stderr("thresh = %d, fgval = %d, bgval = %d\n",
+ thresh, fgval, bgval);
+ pixaAddPix(pixa1, pixp, L_INSERT);
+
+ /* Join these together and add some text */
+ pix1 = pixaDisplayTiledInColumns(pixa1, 3, 1.0, 20, 2);
+ snprintf(textstr, sizeof(textstr),
+ "Scorefract = %3.1f ........... Thresh = %d", scorefract, thresh);
+ pix2 = pixAddSingleTextblock(pix1, bmf, textstr, 0x00ff0000,
+ L_ADD_BELOW, NULL);
+
+ /* Save and display the result */
+ pixaAddPix(pixad, pix2, L_INSERT);
+ snprintf(textstr, sizeof(textstr), "/tmp/lept/otsu/%03d.png", i);
+ pixWrite(textstr, pix2, IFF_PNG);
+ pixDisplay(pix2, 100, 100);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+ }
+
+ /* Use a smaller tile for Otsu */
+ for (i = 0; i < 2; i++) {
+ scorefract = 0.1 * i;
+ pixOtsuAdaptiveThreshold(pixg, 300, 300, 0, 0, scorefract,
+ NULL, &pixb);
+ pix1 = pixAddBlackOrWhiteBorder(pixb, 2, 2, 2, 2, L_GET_BLACK_VAL);
+ pix2 = pixScale(pix1, 0.5, 0.5);
+ snprintf(textstr, sizeof(textstr),
+ "Scorefract = %3.1f", scorefract);
+ pix3 = pixAddSingleTextblock(pix2, bmf, textstr, 1,
+ L_ADD_BELOW, NULL);
+ pixaAddPix(pixad, pix3, L_INSERT);
+ pixDestroy(&pixb);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+
+ lept_stderr("Writing to: /tmp/lept/otsu/result1.pdf\n");
+ pixaConvertToPdf(pixad, 75, 1.0, 0, 0, "Otsu thresholding",
+ "/tmp/lept/otsu/result1.pdf");
+ bmfDestroy(&bmf);
+ pixDestroy(&pixs);
+ pixDestroy(&pixg);
+ pixaDestroy(&pixad);
+ return 0;
+}
diff --git a/leptonica/prog/overlap_reg.c b/leptonica/prog/overlap_reg.c
new file mode 100644
index 00000000..d24649d0
--- /dev/null
+++ b/leptonica/prog/overlap_reg.c
@@ -0,0 +1,261 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * overlap_reg.c
+ *
+ * Tests functions that combine boxes that overlap into
+ * their bounding regions.
+ *
+ * Also tests the overlap and separation distance between boxes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Determines maximum size of randomly-generated boxes. Note the
+ * rapid change in results as the maximum box dimension approaches
+ * the critical size of 28. */
+static const l_float32 maxsize[] = {5.0, 10.0, 15.0, 20.0, 25.0, 26.0, 27.0};
+
+ /* Alternative implementation; just for fun */
+BOXA *boxaCombineOverlapsAlt(BOXA *boxas);
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, n, k, x, y, w, h, result, hovl, hsep, vovl, vsep;
+l_uint8 *data;
+size_t nbytes;
+BOX *box1, *box2;
+BOXA *boxa1, *boxa2, *boxa3, *boxa4;
+FILE *fp;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa1;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* -------------------------------------------------------- */
+ /* Show the result as a kind of percolation problem */
+ /* -------------------------------------------------------- */
+ for (k = 0; k < 7; k++) {
+ srand(45617);
+ pixa1 = pixaCreate(2);
+ boxa1 = boxaCreate(0);
+ for (i = 0; i < 500; i++) {
+ x = (l_int32)(600.0 * (l_float64)rand() / (l_float64)RAND_MAX);
+ y = (l_int32)(600.0 * (l_float64)rand() / (l_float64)RAND_MAX);
+ w = (l_int32)
+ (1.0 + maxsize[k] * (l_float64)rand() / (l_float64)RAND_MAX);
+ h = (l_int32)
+ (1.0 + maxsize[k] * (l_float64)rand() / (l_float64)RAND_MAX);
+ box1 = boxCreate(x, y, w, h);
+ boxaAddBox(boxa1, box1, L_INSERT);
+ }
+
+ pix1 = pixCreate(660, 660, 1);
+ pixRenderBoxa(pix1, boxa1, 2, L_SET_PIXELS);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ boxa2 = boxaCombineOverlaps(boxa1, NULL);
+ pix2 = pixCreate(660, 660, 1);
+ pixRenderBoxa(pix2, boxa2, 2, L_SET_PIXELS);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+
+ pix3 = pixaDisplayTiledInRows(pixa1, 1, 1500, 1.0, 0, 50, 2);
+ pixDisplayWithTitle(pix3, 100, 100 + 100 * k, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 0 - 6 */
+ lept_stderr("Test %d, maxsize = %d: n_init = %d, n_final = %d\n",
+ k + 1, (l_int32)maxsize[k] + 1,
+ boxaGetCount(boxa1), boxaGetCount(boxa2));
+ pixDestroy(&pix3);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ pixaDestroy(&pixa1);
+ }
+
+ /* -------------------------------------------------------- */
+ /* Show for one case, with debugging, and compare with an */
+ /* an alternative version. */
+ /* -------------------------------------------------------- */
+ boxa1 = boxaCreate(0);
+ pixa1 = pixaCreate(10);
+ n = 80;
+ for (i = 0; i < n; i++) {
+ x = (l_int32)(600.0 * (l_float64)rand() / (l_float64)RAND_MAX);
+ y = (l_int32)(600.0 * (l_float64)rand() / (l_float64)RAND_MAX);
+ w = (l_int32)
+ (10 + 48 * (l_float64)rand() / (l_float64)RAND_MAX);
+ h = (l_int32)
+ (10 + 53 * (l_float64)rand() / (l_float64)RAND_MAX);
+ box1 = boxCreate(x, y, w, h);
+ boxaAddBox(boxa1, box1, L_INSERT);
+ }
+
+ boxa2 = boxaCombineOverlaps(boxa1, pixa1);
+ boxaContainedInBoxa(boxa2, boxa1, &result); /* 7 */
+ regTestCompareValues(rp, 1, result, 0);
+
+ pix1 = pixaDisplayTiledInRows(pixa1, 32, 1500, 1.0, 0, 50, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 8 */
+ pixDisplayWithTitle(pix1, 600, 0, NULL, rp->display);
+ pixaDestroy(&pixa1);
+ pixDestroy(&pix1);
+
+ /* Show the boxa from both functions are identical */
+ boxa3 = boxaCombineOverlapsAlt(boxa1);
+ boxaContainedInBoxa(boxa3, boxa2, &result);
+ regTestCompareValues(rp, 1, result, 0); /* 9 */
+ boxaContainedInBoxa(boxa2, boxa3, &result);
+ regTestCompareValues(rp, 1, result, 0); /* 10 */
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+
+ /* --------------------------------------------------------- */
+ /* Show for two boxa that are greedily munching each other */
+ /* --------------------------------------------------------- */
+ boxa1 = boxaCreate(0);
+ boxa2 = boxaCreate(0);
+ n = 80;
+ for (i = 0; i < n; i++) {
+ x = (l_int32)(600.0 * (l_float64)rand() / (l_float64)RAND_MAX);
+ y = (l_int32)(600.0 * (l_float64)rand() / (l_float64)RAND_MAX);
+ w = (l_int32)
+ (10 + 55 * (l_float64)rand() / (l_float64)RAND_MAX);
+ h = (l_int32)
+ (10 + 55 * (l_float64)rand() / (l_float64)RAND_MAX);
+ box1 = boxCreate(x, y, w, h);
+ if (i < n / 2)
+ boxaAddBox(boxa1, box1, L_INSERT);
+ else
+ boxaAddBox(boxa2, box1, L_INSERT);
+ }
+
+ pixa1 = pixaCreate(0);
+ boxaCombineOverlapsInPair(boxa1, boxa2, &boxa3, &boxa4, pixa1);
+ pix1 = pixaDisplayTiledInRows(pixa1, 32, 1500, 1.0, 0, 50, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 11 */
+ pixDisplayWithTitle(pix1, 1200, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+ boxaDestroy(&boxa4);
+
+ /* --------------------------------------------------------- */
+ /* Test the overlap and separation distance functions */
+ /* --------------------------------------------------------- */
+ box1 = boxCreate(0, 0, 1, 1);
+ lept_mkdir("lept/overlap");
+ fp = fopenWriteStream("/tmp/lept/overlap/result.dat", "w");
+ for (i = 0; i < 3; i++) { /* 9 1x1 boxes on a 3x3 square */
+ for (j = 0; j < 3; j++) {
+ box2 = boxCreate(i, j, 1, 1);
+ boxOverlapDistance(box1, box2, &hovl, &vovl);
+ boxSeparationDistance(box1, box2, &hsep, &vsep);
+ fprintf(fp, "(%d,%d): ovl = (%d,%d); sep = (%d,%d)\n",
+ i, j, hovl, vovl, hsep, vsep);
+ boxDestroy(&box2);
+ }
+ }
+ fclose(fp);
+ data = l_binaryRead("/tmp/lept/overlap/result.dat", &nbytes);
+ regTestWriteDataAndCheck(rp, data, nbytes, "dat"); /* 12 */
+ lept_free(data);
+ boxDestroy(&box1);
+ return regTestCleanup(rp);
+}
+
+
+/* -------------------------------------------------------------------- *
+ * Alternative (less elegant) implementation of boxaCombineOverlaps() *
+ * -------------------------------------------------------------------- */
+BOXA *
+boxaCombineOverlapsAlt(BOXA *boxas)
+{
+l_int32 i, j, n1, n2, inter, interfound, niters;
+BOX *box1, *box2, *box3;
+BOXA *boxa1, *boxa2;
+
+ PROCNAME("boxaCombineOverlapsAlt");
+
+ if (!boxas)
+ return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+
+ boxa1 = boxaCopy(boxas, L_COPY);
+ n1 = boxaGetCount(boxa1);
+ niters = 0;
+ while (1) { /* loop until no change from previous iteration */
+ niters++;
+ boxa2 = boxaCreate(n1);
+ for (i = 0; i < n1; i++) {
+ box1 = boxaGetBox(boxa1, i, L_COPY);
+ if (i == 0) {
+ boxaAddBox(boxa2, box1, L_INSERT);
+ continue;
+ }
+ n2 = boxaGetCount(boxa2);
+ /* Now test box1 against all boxes already put in boxa2.
+ * 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 boxa2. */
+ interfound = FALSE;
+ for (j = 0; j < n2; j++) {
+ box2 = boxaGetBox(boxa2, j, L_CLONE);
+ boxIntersects(box1, box2, &inter);
+ if (inter == 1) {
+ box3 = boxBoundingRegion(box1, box2);
+ boxaReplaceBox(boxa2, j, box3);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ interfound = TRUE;
+ break;
+ }
+ boxDestroy(&box2);
+ }
+ if (interfound == FALSE)
+ boxaAddBox(boxa2, box1, L_INSERT);
+ }
+
+ n2 = boxaGetCount(boxa2);
+ if (n2 == n1) /* we're done */
+ break;
+
+ n1 = n2;
+ boxaDestroy(&boxa1);
+ boxa1 = boxa2;
+ }
+ boxaDestroy(&boxa1);
+ return boxa2;
+}
diff --git a/leptonica/prog/pageseg1.tif b/leptonica/prog/pageseg1.tif
new file mode 100644
index 00000000..901950bc
--- /dev/null
+++ b/leptonica/prog/pageseg1.tif
Binary files differ
diff --git a/leptonica/prog/pageseg2-mask.png b/leptonica/prog/pageseg2-mask.png
new file mode 100644
index 00000000..051b8dcb
--- /dev/null
+++ b/leptonica/prog/pageseg2-mask.png
Binary files differ
diff --git a/leptonica/prog/pageseg2-seed.png b/leptonica/prog/pageseg2-seed.png
new file mode 100644
index 00000000..ff5cc907
--- /dev/null
+++ b/leptonica/prog/pageseg2-seed.png
Binary files differ
diff --git a/leptonica/prog/pageseg2.tif b/leptonica/prog/pageseg2.tif
new file mode 100644
index 00000000..30a60a26
--- /dev/null
+++ b/leptonica/prog/pageseg2.tif
Binary files differ
diff --git a/leptonica/prog/pageseg3.tif b/leptonica/prog/pageseg3.tif
new file mode 100644
index 00000000..d70f46c7
--- /dev/null
+++ b/leptonica/prog/pageseg3.tif
Binary files differ
diff --git a/leptonica/prog/pageseg4.tif b/leptonica/prog/pageseg4.tif
new file mode 100644
index 00000000..a34683fd
--- /dev/null
+++ b/leptonica/prog/pageseg4.tif
Binary files differ
diff --git a/leptonica/prog/pageseg_reg.c b/leptonica/prog/pageseg_reg.c
new file mode 100644
index 00000000..2298446f
--- /dev/null
+++ b/leptonica/prog/pageseg_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * pageseg_reg.c
+ *
+ * This is a regresssion test for some of the page segmentation
+ * algorithms. You can run some of these algorithms on any selected page
+ * image using prog/pagesegtest1.
+ * Tests are for:
+ * - Generic page segmentation
+ * - Foreground finding
+ * - Greedy whitespace rectangle finder
+ * - Table finder
+ * - Text auto-inversion
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_uint8 *data;
+char *fname;
+l_int32 i, n, w, h, istable, score;
+size_t size;
+BOX *box;
+BOXA *boxa;
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5;
+PIX *pixhm, *pixtm, *pixtb, *pixdb;
+PIXA *pixadb;
+PIXAC *pixac;
+SARRAY *sa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "pageseg_reg");
+ exit(77);
+#endif
+#if !defined(HAVE_LIBJPEG)
+ L_ERROR("This test requires libjpeg to run.\n", "pageseg_reg");
+ exit(77);
+#endif
+#if !defined(HAVE_LIBTIFF)
+ L_ERROR("This test requires libtiff to run.\n", "pageseg_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+#if 1
+ /* Test the generic page segmentation */
+ pixs = pixRead("pageseg1.tif");
+ pixadb = pixaCreate(0);
+ pixGetRegionsBinary(pixs, &pixhm, &pixtm, &pixtb, pixadb);
+ pixDestroy(&pixhm);
+ pixDestroy(&pixtm);
+ pixDestroy(&pixtb);
+
+ n = pixaGetCount(pixadb);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixadb, i, L_CLONE);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 - 19 */
+ pixDestroy(&pix1);
+ }
+
+ /* Display intermediate images and final segmentation */
+ if (rp->display) {
+ pix1 = pixaDisplayTiledAndScaled(pixadb, 32, 400, 4, 0, 20, 3);
+ pixDisplay(pix1, 0, 0);
+ pixDestroy(&pix1);
+ pix1 = pixaGetPix(pixadb, 18, L_CLONE);
+ pixDisplay(pix1, 510, 0);
+ pixDestroy(&pix1);
+ pix1 = pixaGetPix(pixadb, 19, L_CLONE);
+ pixDisplay(pix1, 1140, 0);
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixadb);
+
+ /* Test foreground finding */
+ sa = getSortedPathnamesInDirectory(".", "lion-page", 0, 0);
+ n = sarrayGetCount(sa);
+ boxa = boxaCreate(n);
+ box = boxCreate(0, 0, 0, 0); /* invalid box */
+ boxaInitFull(boxa, box); /* Init to invalid boxes */
+ boxDestroy(&box);
+ pixac = pixacompCreate(n);
+ for (i = 0; i < n; i++) {
+ fname = sarrayGetString(sa, i, L_NOCOPY);
+ pix1 = pixRead(fname);
+ box = pixFindPageForeground(pix1, 170, 70, 30, 0, pixac);
+ if (!box) {
+ pixDestroy(&pix1);
+ continue;
+ }
+ boxaReplaceBox(boxa, i, box);
+ pixDestroy(&pix1);
+ }
+ boxaWriteMem(&data, &size, boxa);
+ regTestWriteDataAndCheck(rp, data, size, "ba"); /* 20 */
+ boxaDestroy(&boxa);
+ lept_free(data);
+ l_pdfSetDateAndVersion(0);
+ pixacompConvertToPdfData(pixac, 0, 1.0, L_DEFAULT_ENCODE, 0,
+ "Page foreground", &data, &size);
+ regTestWriteDataAndCheck(rp, data, size, "pdf"); /* 21 */
+ lept_free(data);
+ sarrayDestroy(&sa);
+ pixacompDestroy(&pixac);
+
+ /* Test the greedy rectangle finder for white space */
+ pix1 = pixScale(pixs, 0.5, 0.5);
+ pixFindLargeRectangles(pix1, 0, 20, &boxa, &pixdb);
+ regTestWritePixAndCheck(rp, pixdb, IFF_PNG); /* 22 */
+ pixDisplayWithTitle(pixdb, 0, 700, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pixdb);
+ boxaDestroy(&boxa);
+
+ /* Test the table finder */
+ pix1 = pixRead("table.15.tif");
+ pixadb = pixaCreate(0);
+ pixDecideIfTable(pix1, NULL, L_PORTRAIT_MODE, &score, pixadb);
+ istable = (score >= 2) ? 1 : 0;
+ regTestCompareValues(rp, 1.0, istable, 0.0); /* 23 */
+ pix2 = pixaDisplayTiledInRows(pixadb, 32, 2000, 1.0, 0, 30, 2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 24 */
+ pixDisplayWithTitle(pix2, 620, 700, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixadb);
+
+ pix1 = pixRead("table.27.tif");
+ pixadb = pixaCreate(0);
+ pixDecideIfTable(pix1, NULL, L_PORTRAIT_MODE, &score, pixadb);
+ istable = (score >= 2) ? 1 : 0;
+ regTestCompareValues(rp, 1.0, istable, 0.0); /* 25 */
+ pix2 = pixaDisplayTiledInRows(pixadb, 32, 2000, 1.0, 0, 30, 2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 26 */
+ pixDisplayWithTitle(pix2, 1000, 700, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixadb);
+
+ pix1 = pixRead("table.150.png");
+ pixadb = pixaCreate(0);
+ pixDecideIfTable(pix1, NULL, L_PORTRAIT_MODE, &score, pixadb);
+ istable = (score >= 2) ? 1 : 0;
+ regTestCompareValues(rp, 1.0, istable, 0.0); /* 27 */
+ pix2 = pixaDisplayTiledInRows(pixadb, 32, 2000, 1.0, 0, 30, 2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 28 */
+ pixDisplayWithTitle(pix2, 1300, 700, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixadb);
+
+ pix1 = pixRead("toc.99.tif"); /* not a table */
+ pixadb = pixaCreate(0);
+ pixDecideIfTable(pix1, NULL, L_PORTRAIT_MODE, &score, pixadb);
+ istable = (score >= 2) ? 1 : 0;
+ regTestCompareValues(rp, 0.0, istable, 0.0); /* 29 */
+ pix2 = pixaDisplayTiledInRows(pixadb, 32, 2000, 1.0, 0, 30, 2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 30 */
+ pixDisplayWithTitle(pix2, 1600, 700, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixadb);
+#endif
+
+ /* Tests of auto-inversion of text */
+ pix1 = pixRead("zanotti-78.jpg");
+ pix2 = pixConvertRGBToLuminance(pix1);
+ pixadb = pixaCreate(0);
+ pixaAddPix(pixadb, pix2, L_COPY);
+ pixGetDimensions(pix2, &w, &h, NULL);
+ pixRasterop(pix2, 0.2 * w, 0.08 * h, 0.4 * w, 0.23 * h, PIX_NOT(PIX_DST),
+ NULL, 0, 0);
+ pixRasterop(pix2, 0.6 * w, 0.5 * h, 0.2 * w, 0.15 * h, PIX_NOT(PIX_DST),
+ NULL, 0, 0);
+ pix3 = pixAutoPhotoinvert(pix2, 128, &pix4, pixadb);
+ pix5 = pixaDisplayTiledInColumns(pixadb, 5, 0.3, 20, 2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 31 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 32 */
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 33 */
+ pixDisplayWithTitle(pix5, 1750, 200, NULL, rp->display);
+ pixaDestroy(&pixadb);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+
+ pix1 = pixRead("invertedtext.tif");
+ pixadb = pixaCreate(0);
+ pixaAddPix(pixadb, pix1, L_COPY);
+ pix2 = pixAutoPhotoinvert(pix1, 128, &pix3, pixadb);
+ pix4 = pixaDisplayTiledInColumns(pixadb, 5, 1.0, 20, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 34 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 35 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 36 */
+ pixDisplayWithTitle(pix4, 1750, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/pagesegtest1.c b/leptonica/prog/pagesegtest1.c
new file mode 100644
index 00000000..7feaae20
--- /dev/null
+++ b/leptonica/prog/pagesegtest1.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * pagesegtest1.c
+ *
+ * Use on, e.g.: feyn.tif, witten.tif,
+ * pageseg1.tif, pageseg2.tif, pageseg3.tif, pageseg4.tif
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pixhm, *pixtm, *pixtb, *pixd;
+PIXA *pixadb;
+char *filein;
+static char mainName[] = "pagesegtest1";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: pagesegtest1 filein", mainName, 1);
+ filein = argv[1];
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ pixadb = pixaCreate(0);
+ pixGetRegionsBinary(pixs, &pixhm, &pixtm, &pixtb, pixadb);
+ pixDestroy(&pixhm);
+ pixDestroy(&pixtm);
+ pixDestroy(&pixtb);
+ pixDestroy(&pixs);
+
+ /* Display intermediate images in a single image */
+ lept_mkdir("lept/pagseg");
+ pixd = pixaDisplayTiledAndScaled(pixadb, 32, 400, 4, 0, 20, 3);
+ pixWrite("/tmp/lept/pageseg/debug.png", pixd, IFF_PNG);
+ pixaDestroy(&pixadb);
+ pixDestroy(&pixd);
+ return 0;
+}
+
diff --git a/leptonica/prog/pagesegtest2.c b/leptonica/prog/pagesegtest2.c
new file mode 100644
index 00000000..93668287
--- /dev/null
+++ b/leptonica/prog/pagesegtest2.c
@@ -0,0 +1,131 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * pagesegtest2.c
+ *
+ * Demonstrates morphological approach to segmenting images.
+ *
+ * pagesegtest2 filein fileout
+ *
+ * where:
+ * filein: 1, 8 or 32 bpp page image
+ * fileout: photomask for image regions at full resolution
+ *
+ * This example shows how to use the morphseq specification of
+ * a sequence of morphological and reduction/expansion operations.
+ *
+ * This is much simpler than generating the structuring elements
+ * for the morph operations, specifying each of the function
+ * calls, keeping track of the intermediate images, and removing
+ * them at the end.
+ *
+ * The specific sequences below tend to work ok for images scanned at
+ * about 600 ppi.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Mask at 4x reduction */
+static const char *mask_sequence = "r11";
+ /* Seed at 4x reduction, formed by doing a 16x reduction,
+ * an opening, and finally a 4x replicative expansion. */
+static const char *seed_sequence = "r1143 + o5.5+ x4";
+ /* Simple dilation */
+static const char *dilation_sequence = "d3.3";
+
+#define DFLAG 1
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 thresh;
+PIX *pixs, *pixg, *pixb;
+PIX *pixmask4, *pixseed4, *pixsf4, *pixd4, *pixd;
+static char mainName[] = "pagesegtest2";
+
+ if (argc != 4)
+ return ERROR_INT(" Syntax: pagesegtest2 filein thresh fileout",
+ mainName, 1);
+ filein = argv[1];
+ thresh = atoi(argv[2]);
+ fileout = argv[3];
+ setLeptDebugOK(1);
+
+ /* Get a 1 bpp version of the page */
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+ if (pixGetDepth(pixs) == 32)
+ pixg = pixConvertRGBToGrayFast(pixs);
+ else
+ pixg = pixClone(pixs);
+ if (pixGetDepth(pixg) == 8)
+ pixb = pixThresholdToBinary(pixg, thresh);
+ else
+ pixb = pixClone(pixg);
+
+ /* Make seed and mask, and fill seed into mask */
+ pixseed4 = pixMorphSequence(pixb, seed_sequence, 0);
+ pixmask4 = pixMorphSequence(pixb, mask_sequence, 0);
+ pixsf4 = pixSeedfillBinary(NULL, pixseed4, pixmask4, 8);
+ pixd4 = pixMorphSequence(pixsf4, dilation_sequence, 0);
+
+ /* Mask at full resolution */
+ pixd = pixExpandBinaryPower2(pixd4, 4);
+ pixWrite(fileout, pixd, IFF_TIFF_G4);
+
+ /* Extract non-image parts (e.g., text) at full resolution */
+ pixSubtract(pixb, pixb, pixd);
+
+ pixDisplayWithTitle(pixseed4, 400, 100, "halftone seed", DFLAG);
+ pixDisplayWithTitle(pixmask4, 100, 100, "halftone seed mask", DFLAG);
+ pixDisplayWithTitle(pixd4, 700, 100, "halftone mask", DFLAG);
+ pixDisplayWithTitle(pixb, 1000, 100, "non-halftone", DFLAG);
+
+#if 1
+ pixWrite("junkseed", pixseed4, IFF_TIFF_G4);
+ pixWrite("junkmask", pixmask4, IFF_TIFF_G4);
+ pixWrite("junkfill", pixd4, IFF_TIFF_G4);
+ pixWrite("junktext", pixb, IFF_TIFF_G4);
+#endif
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixg);
+ pixDestroy(&pixb);
+ pixDestroy(&pixseed4);
+ pixDestroy(&pixmask4);
+ pixDestroy(&pixsf4);
+ pixDestroy(&pixd4);
+ pixDestroy(&pixd);
+ return 0;
+}
+
+
diff --git a/leptonica/prog/paint_reg.c b/leptonica/prog/paint_reg.c
new file mode 100644
index 00000000..dd3a08c5
--- /dev/null
+++ b/leptonica/prog/paint_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * paint_reg.c
+ *
+ * Regression test for:
+ * (1) painting on images of various types and depths.
+ * (2) painting through masks (test by reconstructing cmapped image)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static PIX * ReconstructByValue(L_REGPARAMS *rp, const char *fname);
+static PIX * FakeReconstructByBand(L_REGPARAMS *rp, const char *fname);
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 index;
+l_uint32 val32;
+BOX *box, *box1, *box2, *box3, *box4, *box5;
+BOXA *boxa;
+L_KERNEL *kel;
+PIX *pixs, *pixg, *pixb, *pixd, *pixt, *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixa = pixaCreate(0);
+
+ /* Color non-white pixels on RGB */
+ pixs = pixRead("lucasta-frag.jpg");
+ pixt = pixConvert8To32(pixs);
+ box = boxCreate(120, 30, 200, 200);
+ pixColorGray(pixt, box, L_PAINT_DARK, 220, 0, 0, 255);
+ regTestWritePixAndCheck(rp, pixt, IFF_JFIF_JPEG); /* 0 */
+ pixaAddPix(pixa, pixt, L_COPY);
+ pixColorGray(pixt, NULL, L_PAINT_DARK, 220, 255, 100, 100);
+ regTestWritePixAndCheck(rp, pixt, IFF_JFIF_JPEG); /* 1 */
+ pixaAddPix(pixa, pixt, L_INSERT);
+ boxDestroy(&box);
+
+ /* Color non-white pixels on colormap */
+ pixt = pixThresholdTo4bpp(pixs, 6, 1);
+ box = boxCreate(120, 30, 200, 200);
+ pixColorGray(pixt, box, L_PAINT_DARK, 220, 0, 0, 255);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 2 */
+ pixaAddPix(pixa, pixt, L_COPY);
+ pixColorGray(pixt, NULL, L_PAINT_DARK, 220, 255, 100, 100);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 3 */
+ pixaAddPix(pixa, pixt, L_INSERT);
+ boxDestroy(&box);
+
+ /* Color non-black pixels on RGB */
+ pixt = pixConvert8To32(pixs);
+ box = boxCreate(120, 30, 200, 200);
+ pixColorGray(pixt, box, L_PAINT_LIGHT, 20, 0, 0, 255);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 4 */
+ pixaAddPix(pixa, pixt, L_COPY);
+ pixColorGray(pixt, NULL, L_PAINT_LIGHT, 80, 255, 100, 100);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 5 */
+ pixaAddPix(pixa, pixt, L_INSERT);
+ boxDestroy(&box);
+
+ /* Color non-black pixels on colormap */
+ pixt = pixThresholdTo4bpp(pixs, 6, 1);
+ box = boxCreate(120, 30, 200, 200);
+ pixColorGray(pixt, box, L_PAINT_LIGHT, 20, 0, 0, 255);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 6 */
+ pixaAddPix(pixa, pixt, L_COPY);
+ pixColorGray(pixt, NULL, L_PAINT_LIGHT, 20, 255, 100, 100);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 7 */
+ pixaAddPix(pixa, pixt, L_INSERT);
+ boxDestroy(&box);
+
+ /* Add highlight color to RGB */
+ pixt = pixConvert8To32(pixs);
+ box = boxCreate(507, 5, 385, 45);
+ pixg = pixClipRectangle(pixs, box, NULL);
+ pixb = pixThresholdToBinary(pixg, 180);
+ pixInvert(pixb, pixb);
+ composeRGBPixel(50, 0, 250, &val32);
+ pixPaintThroughMask(pixt, pixb, box->x, box->y, val32);
+ boxDestroy(&box);
+ pixDestroy(&pixg);
+ pixDestroy(&pixb);
+ box = boxCreate(236, 107, 262, 40);
+ pixg = pixClipRectangle(pixs, box, NULL);
+ pixb = pixThresholdToBinary(pixg, 180);
+ pixInvert(pixb, pixb);
+ composeRGBPixel(250, 0, 50, &val32);
+ pixPaintThroughMask(pixt, pixb, box->x, box->y, val32);
+ boxDestroy(&box);
+ pixDestroy(&pixg);
+ pixDestroy(&pixb);
+ box = boxCreate(222, 208, 247, 43);
+ pixg = pixClipRectangle(pixs, box, NULL);
+ pixb = pixThresholdToBinary(pixg, 180);
+ pixInvert(pixb, pixb);
+ composeRGBPixel(60, 250, 60, &val32);
+ pixPaintThroughMask(pixt, pixb, box->x, box->y, val32);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 8 */
+ pixaAddPix(pixa, pixt, L_INSERT);
+ boxDestroy(&box);
+ pixDestroy(&pixg);
+ pixDestroy(&pixb);
+
+ /* Add highlight color to colormap */
+ pixt = pixThresholdTo4bpp(pixs, 5, 1);
+ cmap = pixGetColormap(pixt);
+ pixcmapGetIndex(cmap, 255, 255, 255, &index);
+ box = boxCreate(507, 5, 385, 45);
+ pixSetSelectCmap(pixt, box, index, 50, 0, 250);
+ boxDestroy(&box);
+ box = boxCreate(236, 107, 262, 40);
+ pixSetSelectCmap(pixt, box, index, 250, 0, 50);
+ boxDestroy(&box);
+ box = boxCreate(222, 208, 247, 43);
+ pixSetSelectCmap(pixt, box, index, 60, 250, 60);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 9 */
+ pixaAddPix(pixa, pixt, L_INSERT);
+ boxDestroy(&box);
+
+ /* Paint lines on RGB */
+ pixt = pixConvert8To32(pixs);
+ pixRenderLineArb(pixt, 450, 20, 850, 320, 5, 200, 50, 125);
+ pixRenderLineArb(pixt, 30, 40, 440, 40, 5, 100, 200, 25);
+ box = boxCreate(70, 80, 300, 245);
+ pixRenderBoxArb(pixt, box, 3, 200, 200, 25);
+ regTestWritePixAndCheck(rp, pixt, IFF_JFIF_JPEG); /* 10 */
+ pixaAddPix(pixa, pixt, L_INSERT);
+ boxDestroy(&box);
+
+ /* Paint lines on colormap */
+ pixt = pixThresholdTo4bpp(pixs, 5, 1);
+ pixRenderLineArb(pixt, 450, 20, 850, 320, 5, 200, 50, 125);
+ pixRenderLineArb(pixt, 30, 40, 440, 40, 5, 100, 200, 25);
+ box = boxCreate(70, 80, 300, 245);
+ pixRenderBoxArb(pixt, box, 3, 200, 200, 25);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 11 */
+ pixaAddPix(pixa, pixt, L_INSERT);
+ boxDestroy(&box);
+
+ /* Blend lines on RGB */
+ pixt = pixConvert8To32(pixs);
+ pixRenderLineBlend(pixt, 450, 20, 850, 320, 5, 200, 50, 125, 0.35);
+ pixRenderLineBlend(pixt, 30, 40, 440, 40, 5, 100, 200, 25, 0.35);
+ box = boxCreate(70, 80, 300, 245);
+ pixRenderBoxBlend(pixt, box, 3, 200, 200, 25, 0.6);
+ regTestWritePixAndCheck(rp, pixt, IFF_JFIF_JPEG); /* 12 */
+ pixaAddPix(pixa, pixt, L_INSERT);
+ boxDestroy(&box);
+
+ /* Colorize gray on cmapped image. */
+ pix1 = pixRead("lucasta.150.jpg");
+ pix2 = pixThresholdTo4bpp(pix1, 7, 1);
+ box1 = boxCreate(73, 206, 140, 27);
+ pixColorGrayCmap(pix2, box1, L_PAINT_LIGHT, 130, 207, 43);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 13 */
+ pixaAddPix(pixa, pix2, L_COPY);
+ if (rp->display)
+ pixPrintStreamInfo(stderr, pix2, "One box added");
+
+ box2 = boxCreate(255, 404, 197, 25);
+ pixColorGrayCmap(pix2, box2, L_PAINT_LIGHT, 230, 67, 119);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 14 */
+ pixaAddPix(pixa, pix2, L_COPY);
+ if (rp->display)
+ pixPrintStreamInfo(stderr, pix2, "Two boxes added");
+
+ box3 = boxCreate(122, 756, 224, 22);
+ pixColorGrayCmap(pix2, box3, L_PAINT_DARK, 230, 67, 119);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 15 */
+ pixaAddPix(pixa, pix2, L_COPY);
+ if (rp->display)
+ pixPrintStreamInfo(stderr, pix2, "Three boxes added");
+
+ box4 = boxCreate(11, 780, 147, 22);
+ pixColorGrayCmap(pix2, box4, L_PAINT_LIGHT, 70, 137, 229);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 16 */
+ pixaAddPix(pixa, pix2, L_COPY);
+ if (rp->display)
+ pixPrintStreamInfo(stderr, pix2, "Four boxes added");
+
+ box5 = boxCreate(163, 605, 78, 22);
+ pixColorGrayCmap(pix2, box5, L_PAINT_LIGHT, 70, 137, 229);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 17 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ if (rp->display)
+ pixPrintStreamInfo(stderr, pix2, "Five boxes added");
+ pixDestroy(&pix1);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ boxDestroy(&box3);
+ boxDestroy(&box4);
+ boxDestroy(&box5);
+ pixDestroy(&pixs);
+
+ /* Make a gray image and identify the fg pixels (val > 230) */
+ pixs = pixRead("feyn-fract.tif");
+ pix1 = pixConvertTo8(pixs, 0);
+ kel = makeGaussianKernel(2, 2, 1.5, 1.0);
+ pix2 = pixConvolve(pix1, kel, 8, 1);
+ pix3 = pixThresholdToBinary(pix2, 230);
+ boxa = pixConnComp(pix3, NULL, 8);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ kernelDestroy(&kel);
+
+ /* Color the individual components in the gray image */
+ pix4 = pixColorGrayRegions(pix2, boxa, L_PAINT_DARK, 230, 255, 0, 0);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 18 */
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDisplayWithTitle(pix4, 0, 0, NULL, rp->display);
+
+ /* Threshold to 10 levels of gray */
+ pix3 = pixThresholdOn8bpp(pix2, 10, 1);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 19 */
+ pixaAddPix(pixa, pix3, L_COPY);
+
+ /* Color the individual components in the cmapped image */
+ pix4 = pixColorGrayRegions(pix3, boxa, L_PAINT_DARK, 230, 255, 0, 0);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 20 */
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDisplayWithTitle(pix4, 0, 100, NULL, rp->display);
+ boxaDestroy(&boxa);
+
+ /* Color the entire gray image (not component-wise) */
+ pixColorGray(pix2, NULL, L_PAINT_DARK, 230, 255, 0, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 21 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ /* Color the entire cmapped image (not component-wise) */
+ pixColorGray(pix3, NULL, L_PAINT_DARK, 230, 255, 0, 0);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 22 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+
+ /* Reconstruct cmapped images */
+ pixd = ReconstructByValue(rp, "weasel2.4c.png");
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 23 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixd = ReconstructByValue(rp, "weasel4.11c.png");
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 24 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixd = ReconstructByValue(rp, "weasel8.240c.png");
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 25 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+
+ /* Fake reconstruct cmapped images, with one color into a band */
+ pixd = FakeReconstructByBand(rp, "weasel2.4c.png");
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 26 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixd = FakeReconstructByBand(rp, "weasel4.11c.png");
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 27 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixd = FakeReconstructByBand(rp, "weasel8.240c.png");
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 28 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+
+ /* If in testing mode, make a pdf */
+ if (rp->display) {
+ pixaConvertToPdf(pixa, 100, 1.0, L_FLATE_ENCODE, 0,
+ "Colorize and paint", "/tmp/lept/regout/paint.pdf");
+ L_INFO("Output pdf: /tmp/lept/regout/paint.pdf\n", rp->testname);
+ }
+
+ pixaDestroy(&pixa);
+ return regTestCleanup(rp);
+}
+
+
+static PIX *
+ReconstructByValue(L_REGPARAMS *rp,
+ const char *fname)
+{
+l_int32 i, n, rval, gval, bval;
+PIX *pixs, *pixm, *pixd;
+PIXCMAP *cmap;
+
+ pixs = pixRead(fname);
+ cmap = pixGetColormap(pixs);
+ n = pixcmapGetCount(cmap);
+ pixd = pixCreateTemplate(pixs);
+ for (i = 0; i < n; i++) {
+ pixm = pixGenerateMaskByValue(pixs, i, 1);
+ pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+ pixSetMaskedCmap(pixd, pixm, 0, 0, rval, gval, bval);
+ pixDestroy(&pixm);
+ }
+
+ regTestComparePix(rp, pixs, pixd);
+ pixDestroy(&pixs);
+ return pixd;
+}
+
+static PIX *
+FakeReconstructByBand(L_REGPARAMS *rp,
+ const char *fname)
+{
+l_int32 i, jlow, jup, n, nbands;
+l_int32 rval1, gval1, bval1, rval2, gval2, bval2, rval, gval, bval;
+PIX *pixs, *pixm, *pixd;
+PIXCMAP *cmaps, *cmapd;
+
+ pixs = pixRead(fname);
+ cmaps = pixGetColormap(pixs);
+ n = pixcmapGetCount(cmaps);
+ nbands = (n + 1) / 2;
+ pixd = pixCreateTemplate(pixs);
+ cmapd = pixcmapCreate(pixGetDepth(pixs));
+ pixSetColormap(pixd, cmapd);
+ for (i = 0; i < nbands; i++) {
+ jlow = 2 * i;
+ jup = L_MIN(jlow + 1, n - 1);
+ pixm = pixGenerateMaskByBand(pixs, jlow, jup, 1, 1);
+
+ /* Get average color in the band */
+ pixcmapGetColor(cmaps, jlow, &rval1, &gval1, &bval1);
+ pixcmapGetColor(cmaps, jup, &rval2, &gval2, &bval2);
+ rval = (rval1 + rval2) / 2;
+ gval = (gval1 + gval2) / 2;
+ bval = (bval1 + bval2) / 2;
+
+ pixcmapAddColor(cmapd, rval, gval, bval);
+ pixSetMaskedCmap(pixd, pixm, 0, 0, rval, gval, bval);
+ pixDestroy(&pixm);
+ }
+
+ pixDestroy(&pixs);
+ return pixd;
+}
diff --git a/leptonica/prog/paintmask_reg.c b/leptonica/prog/paintmask_reg.c
new file mode 100644
index 00000000..d944f438
--- /dev/null
+++ b/leptonica/prog/paintmask_reg.c
@@ -0,0 +1,215 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * paintmask_reg.c
+ *
+ * Regression test for painting through a mask onto various
+ * depth images.
+ *
+ * This file shows how one can start with a 32 bpp RGB image and
+ * derive from it the following:
+ * 8 bpp color, cmapped
+ * 4 bpp color, cmapped
+ * 2 bpp color, cmapped
+ * 8 bpp gray
+ * 4 bpp gray
+ * 4 bpp gray, cmapped
+ * 2 bpp gray
+ * 2 bpp gray, cmapped
+ *
+ * For each of these, pixClipMasked() is used to place a 1 bpp
+ * mask over part of the image, clip out the rectangular region
+ * supporting the mask, and paint a given color through the
+ * mask onto the result.
+ *
+ * Finally we do a clip/mask operation on 1 bpp sources.
+ *
+ * If you run 'paintmask_reg display', a pdf of the results is made.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+BOX *box;
+PIX *pixs, *pixs8, *pixm, *pixt1, *pixt2, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixa = pixaCreate(0);
+
+ /* Start with a 32 bpp image and a mask. Use the
+ * same mask for all clip/masked operations. */
+ pixs = pixRead("test24.jpg");
+ pixt1 = pixRead("rabi.png");
+ box = boxCreate(303, 1983, 800, 500);
+ pixm = pixClipRectangle(pixt1, box, NULL);
+ pixInvert(pixm, pixm);
+ boxDestroy(&box);
+ box = boxCreate(100, 100, 800, 500); /* clips on pixs and derivatives */
+ pixt2 = pixClipRectangle(pixs, box, NULL);
+ regTestWritePixAndCheck(rp, pixt2, IFF_JFIF_JPEG); /* 0 */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixDestroy(&pixt1);
+
+ /* Clip 32 bpp RGB */
+ pixd = pixClipMasked(pixs, pixm, 100, 100, 0x03c08000);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 1 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+
+ /* Clip 8 bpp colormapped */
+ pixt1 = pixMedianCutQuant(pixs, 0);
+ pixt2 = pixClipRectangle(pixt1, box, NULL);
+ regTestWritePixAndCheck(rp, pixt2, IFF_PNG); /* 2 */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixd = pixClipMasked(pixt1, pixm, 100, 100, 0x03c08000);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 3 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixt1);
+
+ /* Clip 4 bpp colormapped */
+ pixt1 = pixOctreeQuantNumColors(pixs, 16, 1);
+ pixt2 = pixClipRectangle(pixt1, box, NULL);
+ regTestWritePixAndCheck(rp, pixt2, IFF_PNG); /* 4 */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixd = pixClipMasked(pixt1, pixm, 100, 100, 0x03c08000);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 5 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixt1);
+
+ /* Clip 2 bpp colormapped */
+ pixt1 = pixMedianCutQuantGeneral(pixs, 0, 2, 4, 5, 1, 1);
+ pixt2 = pixClipRectangle(pixt1, box, NULL);
+ regTestWritePixAndCheck(rp, pixt2, IFF_PNG); /* 6 */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixd = pixClipMasked(pixt1, pixm, 100, 100, 0x03608000);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 7 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixt1);
+
+ /* Clip 8 bpp gray */
+ pixs8 = pixConvertRGBToLuminance(pixs);
+ pixt2 = pixClipRectangle(pixs8, box, NULL);
+ regTestWritePixAndCheck(rp, pixt2, IFF_JFIF_JPEG); /* 8 */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixd = pixClipMasked(pixs8, pixm, 100, 100, 90);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 9 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+
+ /* Clip 4 bpp gray */
+ pixt1 = pixThresholdTo4bpp(pixs8, 16, 0);
+ pixt2 = pixClipRectangle(pixt1, box, NULL);
+ regTestWritePixAndCheck(rp, pixt2, IFF_PNG); /* 10 */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixd = pixClipMasked(pixt1, pixm, 100, 100, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 11 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixd = pixClipMasked(pixt1, pixm, 100, 100, 5);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 12 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixd = pixClipMasked(pixt1, pixm, 100, 100, 15);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 13 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixt1);
+
+ /* Clip 4 bpp gray, colormapped */
+ pixt1 = pixThresholdTo4bpp(pixs8, 16, 1);
+ pixt2 = pixClipRectangle(pixt1, box, NULL);
+ regTestWritePixAndCheck(rp, pixt2, IFF_PNG); /* 14 */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixd = pixClipMasked(pixt1, pixm, 100, 100, 0x55555500);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 15 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixt1);
+
+ /* Clip 2 bpp gray */
+ pixt1 = pixThresholdTo2bpp(pixs8, 4, 0);
+ pixt2 = pixClipRectangle(pixt1, box, NULL);
+ regTestWritePixAndCheck(rp, pixt2, IFF_PNG); /* 16 */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixd = pixClipMasked(pixt1, pixm, 100, 100, 1);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 17 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixt1);
+
+ /* Clip 2 bpp gray, colormapped */
+ pixt1 = pixThresholdTo2bpp(pixs8, 4, 1);
+ pixt2 = pixClipRectangle(pixt1, box, NULL);
+ pixd = pixClipMasked(pixt1, pixm, 100, 100, 0x55555500);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 18 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ pixDestroy(&pixm);
+ pixDestroy(&pixs);
+ pixDestroy(&pixs8);
+ boxDestroy(&box);
+
+ /* Finally, do the 1 bpp painting through clipped region.
+ * We start with two 1 bpp text sources, use the inverse
+ * of the 2nd for the mask (so we take all of the 1st
+ * pixels under this mask), and for the remainder, which
+ * are the fg pixels in the 2nd, we paint them black (1).
+ * So this is a simple and fast blending of two 1 bpp pix. */
+ pixs = pixRead("feyn.tif");
+ box = boxCreate(670, 827, 800, 500);
+ pixt2 = pixClipRectangle(pixs, box, NULL);
+ regTestWritePixAndCheck(rp, pixt2, IFF_PNG); /* 19 */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ boxDestroy(&box);
+ pixt1 = pixRead("rabi.png");
+ box = boxCreate(303, 1983, 800, 500);
+ pixm = pixClipRectangle(pixt1, box, NULL);
+ pixInvert(pixm, pixm);
+ regTestWritePixAndCheck(rp, pixm, IFF_PNG); /* 20 */
+ pixaAddPix(pixa, pixm, L_INSERT);
+ pixd = pixClipMasked(pixs, pixm, 670, 827, 1);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 21 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixs);
+ pixDestroy(&pixt1);
+ boxDestroy(&box);
+
+ /* If in testing mode, make a pdf */
+ if (rp->display) {
+ pixaConvertToPdf(pixa, 100, 1.0, L_FLATE_ENCODE, 0,
+ "Paint through mask", "/tmp/lept/regout/paintmask.pdf");
+ L_INFO("Output pdf: /tmp/lept/regout/paintmask.pdf\n", rp->testname);
+ }
+
+ pixaDestroy(&pixa);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/pancrazi.15.jpg b/leptonica/prog/pancrazi.15.jpg
new file mode 100644
index 00000000..5bfdd594
--- /dev/null
+++ b/leptonica/prog/pancrazi.15.jpg
Binary files differ
diff --git a/leptonica/prog/partifytest.c b/leptonica/prog/partifytest.c
new file mode 100644
index 00000000..8ef59f5b
--- /dev/null
+++ b/leptonica/prog/partifytest.c
@@ -0,0 +1,53 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * partifytest.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+static char mainName[] = "partifytest";
+
+ if (argc != 1)
+ return ERROR_INT("syntax: partifytest", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/partify");
+#if 0
+ partifyFiles(".", "bois", 3, "/tmp/lept/partify/bois", "/tmp/lept/partify/debug.pdf");
+#else
+ partifyFiles(".", "ortiz", 5, "/tmp/lept/partify/ortiz", "/tmp/lept/partify/debug.pdf");
+#endif
+ return 0;
+}
diff --git a/leptonica/prog/partitiontest.c b/leptonica/prog/partitiontest.c
new file mode 100644
index 00000000..d89309fa
--- /dev/null
+++ b/leptonica/prog/partitiontest.c
@@ -0,0 +1,177 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * partitiontest.c
+ *
+ * partitiontest <fname> type [maxboxes ovlap]
+ *
+ * where type is:
+ * 5: L_SORT_BY_WIDTH
+ * 6: L_SORT_BY_HEIGHT
+ * 7: L_SORT_BY_MIN_DIMENSION
+ * 8: L_SORT_BY_MAX_DIMENSION
+ * 9: L_SORT_BY_PERIMETER
+ * 10: L_SORT_BY_AREA
+ *
+ * This partitions the input (1 bpp) image using boxaGetWhiteblocks(),
+ * which is an elegant but flawed method in computational geometry to
+ * extract the significant rectangular white blocks in a 1 bpp image.
+ * See partition.c for details.
+ *
+ * It then sorts the regions according to the selected input type,
+ * and displays the top sorted blocks in several different ways:
+ * - as outlines or solid filled regions
+ * - with random or specific colors
+ * - as an rgb or colormapped image.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filename;
+l_int32 w, h, type, maxboxes;
+l_float32 ovlap;
+BOX *box;
+BOXA *boxa, *boxat, *boxad;
+PIX *pix, *pixs, *pix1, *pix2;
+PIXA *pixa;
+static char mainName[] = "partitiontest";
+
+ if (argc != 3 && argc != 5)
+ return ERROR_INT("syntax: partitiontest <fname> type [maxboxes ovlap]",
+ mainName, 1);
+ filename = argv[1];
+ type = atoi(argv[2]);
+
+ if (type == L_SORT_BY_WIDTH) {
+ lept_stderr("Sorting by width:\n");
+ } else if (type == L_SORT_BY_HEIGHT) {
+ lept_stderr("Sorting by height:\n");
+ } else if (type == L_SORT_BY_MAX_DIMENSION) {
+ lept_stderr("Sorting by maximum dimension:\n");
+ } else if (type == L_SORT_BY_MIN_DIMENSION) {
+ lept_stderr("Sorting by minimum dimension:\n");
+ } else if (type == L_SORT_BY_PERIMETER) {
+ lept_stderr("Sorting by perimeter:\n");
+ } else if (type == L_SORT_BY_AREA) {
+ lept_stderr("Sorting by area:\n");
+ } else {
+ lept_stderr("Use one of the following for 'type':\n"
+ " 5: L_SORT_BY_WIDTH\n"
+ " 6: L_SORT_BY_HEIGHT\n"
+ " 7: L_SORT_BY_MIN_DIMENSION\n"
+ " 8: L_SORT_BY_MAX_DIMENSION\n"
+ " 9: L_SORT_BY_PERIMETER\n"
+ " 10: L_SORT_BY_AREA\n");
+ return ERROR_INT("invalid type: see source", mainName, 1);
+ }
+ if (argc == 5) {
+ maxboxes = atoi(argv[3]);
+ ovlap = atof(argv[4]);
+ } else {
+ maxboxes = 100;
+ ovlap = 0.2;
+ }
+
+ setLeptDebugOK(1);
+ pixa = pixaCreate(0);
+ pix = pixRead(filename);
+ pixs = pixConvertTo1(pix, 128);
+ pixDilateBrick(pixs, pixs, 5, 5);
+ pixaAddPix(pixa, pixs, L_COPY);
+ boxa = pixConnComp(pixs, NULL, 4);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ box = boxCreate(0, 0, w, h);
+ startTimer();
+ boxaPermuteRandom(boxa, boxa);
+ boxat = boxaSelectBySize(boxa, 500, 500, L_SELECT_IF_BOTH,
+ L_SELECT_IF_LT, NULL);
+ boxad = boxaGetWhiteblocks(boxat, box, type, maxboxes, ovlap,
+ 200, 0.15, 20000);
+ lept_stderr("Time: %7.3f sec\n", stopTimer());
+/* boxaWriteStream(stderr, boxad); */
+
+ /* Display box outlines in a single color in a cmapped image */
+ pix1 = pixDrawBoxa(pixs, boxad, 7, 0xe0708000);
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Display box outlines in a single color in an RGB image */
+ pix1 = pixConvertTo8(pixs, FALSE);
+ pix2 = pixDrawBoxa(pix1, boxad, 7, 0x40a0c000);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pix1);
+
+ /* Display box outlines with random colors in a cmapped image */
+ pix1 = pixDrawBoxaRandom(pixs, boxad, 7);
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Display box outlines with random colors in an RGB image */
+ pix1 = pixConvertTo8(pixs, FALSE);
+ pix2 = pixDrawBoxaRandom(pix1, boxad, 7);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pix1);
+
+ /* Display boxes in the same color in a cmapped image */
+ pix1 = pixPaintBoxa(pixs, boxad, 0x60e0a000);
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Display boxes in the same color in an RGB image */
+ pix1 = pixConvertTo8(pixs, FALSE);
+ pix2 = pixPaintBoxa(pix2, boxad, 0xc030a000);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pix1);
+
+ /* Display boxes in random colors in a cmapped image */
+ pix1 = pixPaintBoxaRandom(pixs, boxad);
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Display boxes in random colors in an RGB image */
+ pix1 = pixConvertTo8(pixs, FALSE);
+ pix2 = pixPaintBoxaRandom(pix1, boxad);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pix1);
+
+ lept_stderr("Writing to: /tmp/lept/part/partition.pdf\n");
+ lept_mkdir("lept/part");
+ pixaConvertToPdf(pixa, 300, 1.0, L_FLATE_ENCODE, 0, "Partition test",
+ "/tmp/lept/part/partition.pdf");
+ pixaDestroy(&pixa);
+
+ pixDestroy(&pix);
+ pixDestroy(&pixs);
+ boxDestroy(&box);
+ boxaDestroy(&boxa);
+ boxaDestroy(&boxat);
+ boxaDestroy(&boxad);
+ return 0;
+}
diff --git a/leptonica/prog/patent.png b/leptonica/prog/patent.png
new file mode 100644
index 00000000..bcb3cc43
--- /dev/null
+++ b/leptonica/prog/patent.png
Binary files differ
diff --git a/leptonica/prog/pdf2jpeg b/leptonica/prog/pdf2jpeg
new file mode 100644
index 00000000..c5b1f4da
--- /dev/null
+++ b/leptonica/prog/pdf2jpeg
@@ -0,0 +1,58 @@
+#!/bin/bash
+# pdf2jpeg
+#
+# Rasterizes a PDF file, saving as a set of 24 bpp RGB images
+#
+# input: PDF
+# root name of output files
+# output: 24 bpp RGB jpeg files for each page
+#
+# Note 1: Requires ghostscript
+#
+# Note 2: A modern alternative to ghostcript is to use poplar:
+# If the pdf is composed of images that were orthographically generated:
+# pdftoppm -png <pdf-file> <pdf-root> [output in png]
+# If the pdf is composed of images that were scanned:
+# pdftoppm -jpeg <pdf-file> <pdf-root> [output in jpeg]
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " inpdffile outjpgroot"
+ exit -1
+fi
+
+inpdffile=$1
+outjpgroot=$2
+
+# strip off directory and suffix parts of $1 to use in other names
+basename=${1##*/}
+baseroot=${basename%.*}
+
+# make names for temporary files
+tmppdffile=${baseroot}.$$_.pdf
+tmppdfroot=${tmppdffile%.*}
+
+# have the temporary files deleted on exit, interrupt, etc:
+trap "/bin/rm -f ${tmppdfroot}*" EXIT SIGHUP SIGINT SIGTERM
+
+cp $inpdffile $tmppdffile
+
+# need mysterious "primer"
+# choose one of the two options below
+
+#--------------------------------------------------------------------#
+# output image size depending on resolution #
+#--------------------------------------------------------------------#
+echo "0 neg 0 neg" translate | gs -sDEVICE=jpeg -sOutputFile=${outjpgroot}%03d.jpg -r300x300 -q - ${tmppdffile}
+#echo "0 neg 0 neg" translate | gs -sDEVICE=jpeg -sOutputFile=${outjpgroot}%03d.jpg -r150x150 -q - ${tmppdffile}
+#echo "0 neg 0 neg" translate | gs -sDEVICE=jpeg -sOutputFile=${outjpgroot}%03d.jpg -r75x75 -q - ${tmppdffile}
+
+
+#--------------------------------------------------------------------#
+# output fixed image size #
+#--------------------------------------------------------------------#
+#echo "0 neg 0 neg" translate | gs -sDEVICE=jpeg -sOutputFile=${outjpgroot}%03d.jpg -g2550x3300 -r300x300 -q - ${tmppdffile}
+
+
diff --git a/leptonica/prog/pdf2mtiff b/leptonica/prog/pdf2mtiff
new file mode 100755
index 00000000..60fd8f7b
--- /dev/null
+++ b/leptonica/prog/pdf2mtiff
@@ -0,0 +1,51 @@
+#!/bin/bash
+# pdf2mtiff
+#
+# Rasterizes a PDF file, saving as a single multipage g4 compressed
+# tiff file
+#
+# input: PDF file
+# output file
+# output: ccitt-g4 compressed mulitipage tiff file
+#
+# Note 1: Requires ghostscript
+# Note 2: If you give it images with bpp > 1, the result will be ugly.
+# Note 3: A modern alternative to ghostcript is to use poplar:
+# If the pdf is composed of images that were orthographically generated:
+# pdftoppm -png <pdf-file> <pdf-root> [output in png]
+# If the pdf is composed of images that were scanned:
+# pdftoppm -jpeg <pdf-file> <pdf-root> [output in jpeg]
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " pdfin tiffout"
+ exit -1
+fi
+
+pdfin=$1
+tiffout=$2
+
+# assert (input pdf filename ends in .pdf)
+if test ${pdfin##*.} != pdf
+then
+ echo $scriptname ": " $pdfin "does not end in .pdf"
+ exit -1
+fi
+
+# 'echo "0 neg 0 neg" translate' is the mysterious "primer"
+rm /tmp/junkpdf*
+
+# --------- Choose one of the two options below ---------
+
+# output image size at 300 ppi
+#echo "0 neg 0 neg" translate | /usr/bin/gs -sDEVICE=tiffg4 -sOutputFile=/tmp/junkpdf%03d.tif -r300x300 -q - ${pdfin}
+#./writemtiff /tmp junkpdf ${tiffout}
+
+# output image size at 600 ppi
+echo "0 neg 0 neg" translate | /usr/bin/gs -sDEVICE=tiffg4 -sOutputFile=/tmp/junkpdf%03d.tif -r600x600 -q - ${pdfin}
+./writemtiff /tmp junkpdf ${tiffout}
+
+# ------------------------------------------------------
+
diff --git a/leptonica/prog/pdf2png b/leptonica/prog/pdf2png
new file mode 100644
index 00000000..90049071
--- /dev/null
+++ b/leptonica/prog/pdf2png
@@ -0,0 +1,46 @@
+#!/bin/bash
+# pdf2png
+#
+# Rasterizes a PDF file, saving as a set of binary png images
+#
+# input: PDF
+# root name of output files
+# output: png binary files for each page
+#
+# Note 1: Requires ghostscript
+#
+# Note 2: A modern alternative to ghostcript is to use poplar:
+# If the pdf is composed of images that were orthographically generated:
+# pdftoppm -png <pdf-file> <pdf-root> [output in png]
+# If the pdf is composed of images that were scanned:
+# pdftoppm -jpeg <pdf-file> <pdf-root> [output in jpeg]
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " inpdffile outpngroot"
+ exit -1
+fi
+
+inpdffile=$1
+outpngroot=$2
+
+# strip off directory and suffix parts of $1 to use in other names
+basename=${1##*/}
+baseroot=${basename%.*}
+
+# make names for temporary files
+tmppdffile=${baseroot}.$$_.pdf
+tmppdfroot=${tmppdffile%.*}
+
+# have the temporary files deleted on exit, interrupt, etc:
+trap "/bin/rm -f ${tmppdfroot}*" EXIT SIGHUP SIGINT SIGTERM
+
+cp $inpdffile $tmppdffile
+
+# need mysterious "primer"
+#echo "0 neg 0 neg" translate | gs -sDEVICE=pngmono -sOutputFile=${outpngroot}%03d.png -r300x300 -q - ${tmppdffile}
+echo "0 neg 0 neg" translate | gs -sDEVICE=pngmono -sOutputFile=${outpngroot}%03d.png -g2550x3300 -r300x300 -q - ${tmppdffile}
+
+
diff --git a/leptonica/prog/pdf2png-binary b/leptonica/prog/pdf2png-binary
new file mode 100644
index 00000000..efa24d5b
--- /dev/null
+++ b/leptonica/prog/pdf2png-binary
@@ -0,0 +1,38 @@
+#!/bin/bash
+# pdf2png-binary
+#
+# Rasterizes a PDF file, saving as a set of binary png images
+#
+# input: PDF
+# root name of output files
+# output: 1 bpp png files for each page
+#
+# Note 1: Requires ghostscript
+#
+# Note 2: A modern alternative to ghostcript is to use poplar:
+# If the pdf is composed of images that were orthographically generated:
+# pdftoppm -png <pdf-file> <pdf-root> [output in png]
+# If the pdf is composed of images that were scanned:
+# pdftoppm -jpeg <pdf-file> <pdf-root> [output in jpeg]
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " inpdffile outpngroot"
+ exit -1
+fi
+
+inpdffile=$1
+outpngroot=$2
+
+# need mysterious "primer"
+# choose one of the two options below
+
+# output image size depending on resolution
+echo "0 neg 0 neg" translate | gs -sDEVICE=pngmono -sOutputFile=${outpngroot}%03d.png -r300x300 -q - ${inpdffile}
+
+# output fixed image size
+#echo "0 neg 0 neg" translate | gs -sDEVICE=pngmono -sOutputFile=${outpngroot}%03d.png -g2550x3300 -r300x300 -q - ${inpdffile}
+
+
diff --git a/leptonica/prog/pdf2png-color b/leptonica/prog/pdf2png-color
new file mode 100644
index 00000000..47f41b3f
--- /dev/null
+++ b/leptonica/prog/pdf2png-color
@@ -0,0 +1,46 @@
+#!/bin/bash
+# pdf2png-color
+#
+# Rasterizes a PDF file, saving a set of 24 bpp RGB png images
+#
+# input: PDF
+# root name of output files
+# output: 24 bpp RGB png files for each page
+#
+# Note 1: Requires ghostscript
+#
+# Note 2: A modern alternative to ghostcript is to use poplar:
+# If the pdf is composed of images that were orthographically generated:
+# pdftoppm -png <pdf-file> <pdf-root> [output in png]
+# If the pdf is composed of images that were scanned:
+# pdftoppm -jpeg <pdf-file> <pdf-root> [output in jpeg]
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " inpdffile outpngroot"
+ exit -1
+fi
+
+inpdffile=$1
+outpngroot=$2
+
+# strip off directory and suffix parts of $1 to use in other names
+basename=${1##*/}
+baseroot=${basename%.*}
+
+# make names for temporary files
+tmppdffile=${baseroot}.$$_.pdf
+tmppdfroot=${tmppdffile%.*}
+
+# have the temporary files deleted on exit, interrupt, etc:
+trap "/bin/rm -f ${tmppdfroot}*" EXIT SIGHUP SIGINT SIGTERM
+
+cp $inpdffile $tmppdffile
+
+# need mysterious "primer"
+#echo "0 neg 0 neg" translate | gs -sDEVICE=png16m -sOutputFile=${outpngroot}%03d.png -r300x300 -q - ${tmppdffile}
+echo "0 neg 0 neg" translate | gs -sDEVICE=png16m -sOutputFile=${outpngroot}%03d.png -g2550x3300 -r300x300 -q - ${tmppdffile}
+
+
diff --git a/leptonica/prog/pdf2png-gray b/leptonica/prog/pdf2png-gray
new file mode 100644
index 00000000..89a663e8
--- /dev/null
+++ b/leptonica/prog/pdf2png-gray
@@ -0,0 +1,46 @@
+#!/bin/bash
+# pdf2png-gray
+#
+# Rasterizes a PDF file, saving a set of 8 bpp grayscale png images
+#
+# input: PDF
+# root name of output files
+# output: 8 bpp png files for each page
+#
+# Note 1: Requires ghostscript
+#
+# Note 2: A modern alternative to ghostcript is to use poplar:
+# If the pdf is composed of images that were orthographically generated:
+# pdftoppm -png <pdf-file> <pdf-root> [output in png]
+# If the pdf is composed of images that were scanned:
+# pdftoppm -jpeg <pdf-file> <pdf-root> [output in jpeg]
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " inpdffile outpngroot"
+ exit -1
+fi
+
+inpdffile=$1
+outpngroot=$2
+
+# strip off directory and suffix parts of $1 to use in other names
+basename=${1##*/}
+baseroot=${basename%.*}
+
+# make names for temporary files
+tmppdffile=${baseroot}.$$_.pdf
+tmppdfroot=${tmppdffile%.*}
+
+# have the temporary files deleted on exit, interrupt, etc:
+trap "/bin/rm -f ${tmppdfroot}*" EXIT SIGHUP SIGINT SIGTERM
+
+cp $inpdffile $tmppdffile
+
+# need mysterious "primer"
+#echo "0 neg 0 neg" translate | gs -sDEVICE=pnggray -sOutputFile=${outpngroot}%03d.png -r300x300 -q - ${tmppdffile}
+echo "0 neg 0 neg" translate | gs -sDEVICE=pnggray -sOutputFile=${outpngroot}%03d.png -g2550x3300 -r300x300 -q - ${tmppdffile}
+
+
diff --git a/leptonica/prog/pdf2tiff b/leptonica/prog/pdf2tiff
new file mode 100644
index 00000000..47270f1b
--- /dev/null
+++ b/leptonica/prog/pdf2tiff
@@ -0,0 +1,47 @@
+#!/bin/bash
+# pdf2tiff
+#
+# Rasterizes a PDF file, saving as a set of g4 compressed tiff images
+#
+# input: PDF
+# root name of output files
+# output: g4 compressed tiff binary files for each page
+#
+# Note 1: Requires ghostscript
+#
+# Note 2: A modern alternative to ghostcript is to use poplar:
+# If the pdf is composed of images that were orthographically generated:
+# pdftoppm -png <pdf-file> <pdf-root> [output in png]
+# If the pdf is composed of images that were scanned:
+# pdftoppm -jpeg <pdf-file> <pdf-root> [output in jpeg]
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " inpdffile outtifroot"
+ exit -1
+fi
+
+inpdffile=$1
+outtifroot=$2
+
+# strip off directory and suffix parts of $1 to use in other names
+basename=${1##*/}
+baseroot=${basename%.*}
+
+# make names for temporary files
+tmppdffile=${baseroot}.$$_.pdf
+tmppdfroot=${tmppdffile%.*}
+
+# have the temporary files deleted on exit, interrupt, etc:
+trap "/bin/rm -f ${tmppdfroot}*" EXIT SIGHUP SIGINT SIGTERM
+
+cp $inpdffile $tmppdffile
+
+# need mysterious "primer"
+#echo "0 neg 0 neg" translate | gs -sDEVICE=tiffg4 -sOutputFile=${outtifroot}%03d.tif -r300x300 -q - ${tmppdffile}
+echo "0 neg 0 neg" translate | gs -sDEVICE=tiffg4 -sOutputFile=${outtifroot}%03d.tif -g2550x3300 -r300x300 -q - ${tmppdffile}
+
+#tiffcp -c g4 ${tmppsfile%.*}*.tif $outtiffile
+
diff --git a/leptonica/prog/pdfio1_reg.c b/leptonica/prog/pdfio1_reg.c
new file mode 100644
index 00000000..1d93b491
--- /dev/null
+++ b/leptonica/prog/pdfio1_reg.c
@@ -0,0 +1,318 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Basic high-level interface tests
+ * Single images
+ * Multiple images
+ * Segmented images, with and without colormaps
+ * 1 bpp images
+ *
+ * Low-level interface tests for 1 bpp images
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_uint8 *data8;
+l_int32 i, j, seq;
+size_t nbytes;
+const char *title;
+BOX *box;
+L_COMP_DATA *cid;
+L_PDF_DATA *lpd;
+PIX *pix1, *pix2, *pix3;
+PIX *pixs, *pixt, *pixg, *pixgc, *pixc;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "pdfio1_reg");
+ exit(77);
+#endif
+#if !defined(HAVE_LIBJPEG)
+ L_ERROR("This test requires libjpeg to run.\n", "pdfio1_reg");
+ exit(77);
+#endif
+#if !defined(HAVE_LIBTIFF)
+ L_ERROR("This test requires libtiff to run.\n", "pdfio1_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ l_pdfSetDateAndVersion(0);
+ lept_mkdir("lept/pdf1");
+
+#if 1
+ /* --------------- Single image tests ------------------- */
+ lept_stderr("\n*** Writing single images as pdf files\n");
+ convertToPdf("weasel2.4c.png", L_FLATE_ENCODE, 0,
+ "/tmp/lept/pdf1/file00.pdf",
+ 0, 0, 72, "weasel2.4c.png", NULL, 0);
+ convertToPdf("test24.jpg", L_JPEG_ENCODE, 0, "/tmp/lept/pdf1/file01.pdf",
+ 0, 0, 72, "test24.jpg", NULL, 0);
+ convertToPdf("feyn.tif", L_G4_ENCODE, 0, "/tmp/lept/pdf1/file02.pdf",
+ 0, 0, 300, "feyn.tif", NULL, 0);
+
+ pixs = pixRead("feyn.tif");
+ pixConvertToPdf(pixs, L_G4_ENCODE, 0, "/tmp/lept/pdf1/file03.pdf", 0, 0, 300,
+ "feyn.tif", NULL, 0);
+ pixDestroy(&pixs);
+
+ pixs = pixRead("test24.jpg");
+ pixConvertToPdf(pixs, L_JPEG_ENCODE, 5, "/tmp/lept/pdf1/file04.pdf",
+ 0, 0, 72, "test24.jpg", NULL, 0);
+ pixDestroy(&pixs);
+
+ pixs = pixRead("feyn.tif");
+ pixt = pixScaleToGray2(pixs);
+ pixWrite("/tmp/lept/pdf1/feyn8.png", pixt, IFF_PNG);
+ convertToPdf("/tmp/lept/pdf1/feyn8.png", L_JPEG_ENCODE, 0,
+ "/tmp/lept/pdf1/file05.pdf", 0, 0, 150, "feyn8.png", NULL, 0);
+ pixDestroy(&pixs);
+ pixDestroy(&pixt);
+
+ convertToPdf("weasel4.16g.png", L_FLATE_ENCODE, 0,
+ "/tmp/lept/pdf1/file06.pdf", 0, 0, 30,
+ "weasel4.16g.png", NULL, 0);
+
+ pixs = pixRead("test24.jpg");
+ pixg = pixConvertTo8(pixs, 0);
+ box = boxCreate(100, 100, 100, 100);
+ pixc = pixClipRectangle(pixs, box, NULL);
+ pixgc = pixClipRectangle(pixg, box, NULL);
+ pixWrite("/tmp/lept/pdf1/pix32.jpg", pixc, IFF_JFIF_JPEG);
+ pixWrite("/tmp/lept/pdf1/pix8.jpg", pixgc, IFF_JFIF_JPEG);
+ convertToPdf("/tmp/lept/pdf1/pix32.jpg", L_FLATE_ENCODE, 0,
+ "/tmp/lept/pdf1/file07.pdf", 0, 0, 72, "pix32.jpg", NULL, 0);
+ convertToPdf("/tmp/lept/pdf1/pix8.jpg", L_FLATE_ENCODE, 0,
+ "/tmp/lept/pdf1/file08.pdf", 0, 0, 72, "pix8.jpg", NULL, 0);
+ pixDestroy(&pixs);
+ pixDestroy(&pixg);
+ pixDestroy(&pixc);
+ pixDestroy(&pixgc);
+ boxDestroy(&box);
+#endif
+
+
+#if 1
+ /* --------------- Multiple image tests ------------------- */
+ lept_stderr("\n*** Writing multiple images as single page pdf files\n");
+ pix1 = pixRead("feyn-fract.tif");
+ pix2 = pixRead("weasel8.240c.png");
+
+ /* First, write the 1 bpp image through the mask onto the weasels */
+ for (i = 0; i < 5; i++) {
+ for (j = 0; j < 10; j++) {
+ seq = (i == 0 && j == 0) ? L_FIRST_IMAGE : L_NEXT_IMAGE;
+ title = (i == 0 && j == 0) ? "feyn-fract.tif" : NULL;
+ pixConvertToPdf(pix2, L_FLATE_ENCODE, 0, NULL, 100 * j,
+ 100 * i, 70, title, &lpd, seq);
+ }
+ }
+ pixConvertToPdf(pix1, L_G4_ENCODE, 0, "/tmp/lept/pdf1/file09.pdf", 0, 0, 80,
+ NULL, &lpd, L_LAST_IMAGE);
+
+ /* Now, write the 1 bpp image over the weasels */
+ l_pdfSetG4ImageMask(0);
+ for (i = 0; i < 5; i++) {
+ for (j = 0; j < 10; j++) {
+ seq = (i == 0 && j == 0) ? L_FIRST_IMAGE : L_NEXT_IMAGE;
+ title = (i == 0 && j == 0) ? "feyn-fract.tif" : NULL;
+ pixConvertToPdf(pix2, L_FLATE_ENCODE, 0, NULL, 100 * j,
+ 100 * i, 70, title, &lpd, seq);
+ }
+ }
+ pixConvertToPdf(pix1, L_G4_ENCODE, 0, "/tmp/lept/pdf1/file10.pdf", 0, 0, 80,
+ NULL, &lpd, L_LAST_IMAGE);
+ l_pdfSetG4ImageMask(1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+#endif
+
+#if 1
+ /* -------- pdf convert segmented with no image regions -------- */
+ lept_stderr("\n*** Writing segmented images without image regions\n");
+ pix1 = pixRead("rabi.png");
+ pix2 = pixScaleToGray2(pix1);
+ pixWrite("/tmp/lept/pdf1/rabi8.jpg", pix2, IFF_JFIF_JPEG);
+ pix3 = pixThresholdTo4bpp(pix2, 16, 1);
+ pixWrite("/tmp/lept/pdf1/rabi4.png", pix3, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* 1 bpp input */
+ convertToPdfSegmented("rabi.png", 300, L_G4_ENCODE, 128, NULL, 0, 0,
+ NULL, "/tmp/lept/pdf1/file11.pdf");
+ convertToPdfSegmented("rabi.png", 300, L_JPEG_ENCODE, 128, NULL, 0, 0,
+ NULL, "/tmp/lept/pdf1/file12.pdf");
+ convertToPdfSegmented("rabi.png", 300, L_FLATE_ENCODE, 128, NULL, 0, 0,
+ NULL, "/tmp/lept/pdf1/file13.pdf");
+
+ /* 8 bpp input, no cmap */
+ convertToPdfSegmented("/tmp/lept/pdf1/rabi8.jpg", 150, L_G4_ENCODE, 128,
+ NULL, 0, 0, NULL, "/tmp/lept/pdf1/file14.pdf");
+ convertToPdfSegmented("/tmp/lept/pdf1/rabi8.jpg", 150, L_JPEG_ENCODE, 128,
+ NULL, 0, 0, NULL, "/tmp/lept/pdf1/file15.pdf");
+ convertToPdfSegmented("/tmp/lept/pdf1/rabi8.jpg", 150, L_FLATE_ENCODE, 128,
+ NULL, 0, 0, NULL, "/tmp/lept/pdf1/file16.pdf");
+
+ /* 4 bpp input, cmap */
+ convertToPdfSegmented("/tmp/lept/pdf1/rabi4.png", 150, L_G4_ENCODE, 128,
+ NULL, 0, 0, NULL, "/tmp/lept/pdf1/file17.pdf");
+ convertToPdfSegmented("/tmp/lept/pdf1/rabi4.png", 150, L_JPEG_ENCODE, 128,
+ NULL, 0, 0, NULL, "/tmp/lept/pdf1/file18.pdf");
+ convertToPdfSegmented("/tmp/lept/pdf1/rabi4.png", 150, L_FLATE_ENCODE, 128,
+ NULL, 0, 0, NULL, "/tmp/lept/pdf1/file19.pdf");
+
+#endif
+
+#if 1
+ /* ---------- Generating from 1 bpp images (high-level) -------------- */
+ lept_stderr("\n*** Writing 1 bpp images as pdf files (high-level)\n");
+ pix1 = pixRead("feyn-fract.tif");
+ pixWrite("/tmp/lept/pdf1/feyn-nocmap.png", pix1, IFF_PNG);
+ pix2 = pixCopy(NULL, pix1);
+ cmap = pixcmapCreate(1);
+ pixcmapAddColor(cmap, 0, 0, 0); /* with cmap: black bg, white letters */
+ pixcmapAddColor(cmap, 255, 255, 255);
+ pixSetColormap(pix2, cmap);
+ pixWrite("/tmp/lept/pdf1/feyn-cmap1.png", pix2, IFF_PNG);
+ cmap = pixcmapCreate(1);
+ pixcmapAddColor(cmap, 200, 0, 0); /* with cmap: red bg, white letters */
+ pixcmapAddColor(cmap, 255, 255, 255);
+ pixSetColormap(pix1, cmap);
+ pixWrite("/tmp/lept/pdf1/feyn-cmap2.png", pix1, IFF_PNG);
+
+ convertToPdf("/tmp/lept/pdf1/feyn-nocmap.png", L_FLATE_ENCODE, 0,
+ "/tmp/lept/pdf1/file20.pdf",
+ 0, 0, 0, NULL, NULL, 0);
+ convertToPdf("/tmp/lept/pdf1/feyn-cmap1.png", L_FLATE_ENCODE, 0,
+ "/tmp/lept/pdf1/file21.pdf",
+ 0, 0, 0, NULL, NULL, 0);
+ convertToPdf("/tmp/lept/pdf1/feyn-cmap2.png", L_FLATE_ENCODE, 0,
+ "/tmp/lept/pdf1/file22.pdf",
+ 0, 0, 0, NULL, NULL, 0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+#endif
+
+#if 1
+ /* ---------- Generating from 1 bpp images (low-level) -------------- */
+ lept_stderr("\n*** Writing 1 bpp images as pdf files (low-level)\n");
+ pix1 = pixRead("cat-and-mouse.png");
+ pix2 = pixConvertRGBToCmapLossless(pix1); /* restore the cmap */
+
+ /* Add a black/white colormap */
+ cmap = pixcmapCreate(1);
+ pixcmapAddColor(cmap, 255, 255, 255); /* white = 0 */
+ pixcmapAddColor(cmap, 0, 0, 0); /* black = 1 */
+ pixSetColormap(pix2, cmap); /* replace with a b/w colormap */
+ pixWrite("/tmp/lept/pdf1/cat-and-mouse-cmap1.png", pix2, IFF_PNG);
+
+ /* Generate a pdf from this pix. The pdf has the colormap */
+ pixGenerateCIData(pix2, L_FLATE_ENCODE, 0, 0, &cid);
+ lept_stderr(" Should have 2 colors: %d\n", cid->ncolors);
+ cidConvertToPdfData(cid, "with colormap", &data8, &nbytes);
+ l_binaryWrite("/tmp/lept/pdf1/file23.pdf", "w", data8, nbytes);
+ lept_free(data8);
+
+ /* Generate a pdf from the colormap file:
+ * l_generateCIDataForPdf() calls l_generateFlateDataPdf()
+ * which calls pixRead(), removing the cmap */
+ l_generateCIDataForPdf("/tmp/lept/pdf1/cat-and-mouse-cmap1.png",
+ NULL, 75, &cid);
+ lept_stderr(" Should have 0 colors: %d\n", cid->ncolors);
+ cidConvertToPdfData(cid, "no colormap", &data8, &nbytes);
+ l_binaryWrite("/tmp/lept/pdf1/file24.pdf", "w", data8, nbytes);
+ lept_free(data8);
+
+ /* Use an arbitrary colormap */
+ cmap = pixcmapCreate(1);
+ pixcmapAddColor(cmap, 254, 240, 185); // yellow
+ pixcmapAddColor(cmap, 50, 50, 130); // blue
+ pixSetColormap(pix2, cmap);
+ pixWrite("/tmp/lept/pdf1/cat-and-mouse-cmap2.png", pix2, IFF_PNG);
+
+ /* Generate a pdf from this pix. The pdf has the colormap. */
+ pixGenerateCIData(pix2, L_FLATE_ENCODE, 0, 0, &cid);
+ lept_stderr(" Should have 2 colors: %d\n", cid->ncolors);
+ cidConvertToPdfData(cid, "with colormap", &data8, &nbytes);
+ l_binaryWrite("/tmp/lept/pdf1/file25.pdf", "w", data8, nbytes);
+ lept_free(data8);
+
+ /* Generate a pdf from the cmap file. No cmap in the pdf. */
+ l_generateCIDataForPdf("/tmp/lept/pdf1/cat-and-mouse-cmap2.png",
+ NULL, 75, &cid);
+ lept_stderr(" Should have 0 colors: %d\n", cid->ncolors);
+ cidConvertToPdfData(cid, "no colormap", &data8, &nbytes);
+ l_binaryWrite("/tmp/lept/pdf1/file26.pdf", "w", data8, nbytes);
+ lept_free(data8);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+#endif
+
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file00.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file01.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file02.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file03.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file04.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file05.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file06.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file07.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file08.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file09.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file10.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file11.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file12.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file13.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file14.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file15.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file16.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file17.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file18.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file19.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file20.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file21.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file22.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file23.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file24.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file25.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf1/file26.pdf");
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/pdfio2_reg.c b/leptonica/prog/pdfio2_reg.c
new file mode 100644
index 00000000..696907fc
--- /dev/null
+++ b/leptonica/prog/pdfio2_reg.c
@@ -0,0 +1,376 @@
+/*====================================================================*
+ - 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_reg.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static void GetImageMask(PIX *pixs, l_int32 res, BOXA **pboxa,
+ L_REGPARAMS *rp, const char *debugfile);
+static PIX * QuantizeNonImageRegion(PIX *pixs, PIX *pixm, l_int32 levels);
+
+
+int main(int argc,
+ char **argv)
+{
+l_uint8 *data;
+l_int32 w, h, same;
+size_t nbytes;
+BOXA *boxa1, *boxa2;
+L_BYTEA *ba;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "pdfio2_reg");
+ exit(77);
+#endif
+#if !defined(HAVE_LIBJPEG)
+ L_ERROR("This test requires libjpeg to run.\n", "pdfio2_reg");
+ exit(77);
+#endif
+#if !defined(HAVE_LIBTIFF)
+ L_ERROR("This test requires libtiff to run.\n", "pdfio2_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ l_pdfSetDateAndVersion(0);
+ lept_mkdir("lept/pdf2");
+
+ /* ---------- pdf convert segmented with image regions ---------- */
+ lept_stderr("\n*** Writing segmented images with image regions\n");
+ startTimer();
+
+ /* Get the image region(s) for rabi.png. There are two
+ * small bogus regions at the top, but we'll keep them for
+ * the demonstration. */
+ pix1 = pixRead("rabi.png");
+ pix2 = pixScaleToGray2(pix1);
+ pixWrite("/tmp/lept/pdf2/rabi8.jpg", pix2, IFF_JFIF_JPEG);
+ pix3 = pixThresholdTo4bpp(pix2, 16, 1);
+ pixWrite("/tmp/lept/pdf2/rabi4.png", pix3, IFF_PNG);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixSetResolution(pix1, 300, 300);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ pix2 = pixGenerateHalftoneMask(pix1, NULL, NULL, NULL);
+ pix3 = pixMorphSequence(pix2, "c20.1 + c1.20", 0);
+ boxa1 = pixConnComp(pix3, NULL, 8);
+ boxa2 = boxaTransform(boxa1, 0, 0, 0.5, 0.5);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* 1 bpp input */
+ convertToPdfSegmented("rabi.png", 300, L_G4_ENCODE, 128, boxa1,
+ 0, 0.25, NULL, "/tmp/lept/pdf2/file00.pdf");
+ convertToPdfSegmented("rabi.png", 300, L_JPEG_ENCODE, 128, boxa1,
+ 0, 0.25, NULL, "/tmp/lept/pdf2/file01.pdf");
+ convertToPdfSegmented("rabi.png", 300, L_FLATE_ENCODE, 128, boxa1,
+ 0, 0.25, NULL, "/tmp/lept/pdf2/file02.pdf");
+
+ /* 8 bpp input, no cmap */
+ convertToPdfSegmented("/tmp/lept/pdf2/rabi8.jpg", 150, L_G4_ENCODE, 128,
+ boxa2, 0, 0.5, NULL, "/tmp/lept/pdf2/file03.pdf");
+ convertToPdfSegmented("/tmp/lept/pdf2/rabi8.jpg", 150, L_JPEG_ENCODE, 128,
+ boxa2, 0, 0.5, NULL, "/tmp/lept/pdf2/file04.pdf");
+ convertToPdfSegmented("/tmp/lept/pdf2/rabi8.jpg", 150, L_FLATE_ENCODE, 128,
+ boxa2, 0, 0.5, NULL, "/tmp/lept/pdf2/file05.pdf");
+
+ /* 4 bpp input, cmap */
+ convertToPdfSegmented("/tmp/lept/pdf2/rabi4.png", 150, L_G4_ENCODE, 128,
+ boxa2, 0, 0.5, NULL, "/tmp/lept/pdf2/file06.pdf");
+ convertToPdfSegmented("/tmp/lept/pdf2/rabi4.png", 150, L_JPEG_ENCODE, 128,
+ boxa2, 0, 0.5, NULL, "/tmp/lept/pdf2/file07.pdf");
+ convertToPdfSegmented("/tmp/lept/pdf2/rabi4.png", 150, L_FLATE_ENCODE, 128,
+ boxa2, 0, 0.5, NULL, "/tmp/lept/pdf2/file08.pdf");
+
+ /* 4 bpp input, cmap, data output */
+ data = NULL;
+ convertToPdfDataSegmented("/tmp/lept/pdf2/rabi4.png", 150, L_G4_ENCODE,
+ 128, boxa2, 0, 0.5, NULL, &data, &nbytes);
+ l_binaryWrite("/tmp/lept/pdf2/file09.pdf", "w", data, nbytes);
+ lept_free(data);
+ convertToPdfDataSegmented("/tmp/lept/pdf2/rabi4.png", 150, L_JPEG_ENCODE,
+ 128, boxa2, 0, 0.5, NULL, &data, &nbytes);
+ l_binaryWrite("/tmp/lept/pdf2/file10.pdf", "w", data, nbytes);
+ lept_free(data);
+ convertToPdfDataSegmented("/tmp/lept/pdf2/rabi4.png", 150, L_FLATE_ENCODE,
+ 128, boxa2, 0, 0.5, NULL, &data, &nbytes);
+ l_binaryWrite("/tmp/lept/pdf2/file11.pdf", "w", data, nbytes);
+ lept_free(data);
+ lept_stderr("Segmented images time: %7.3f\n", stopTimer());
+
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+
+#if 1
+ /* -------- pdf convert segmented from color image -------- */
+ lept_stderr("\n*** Writing color segmented images\n");
+ startTimer();
+
+ pix1 = pixRead("candelabrum.011.jpg");
+ pix2 = pixScale(pix1, 3.0, 3.0);
+ pixWrite("/tmp/lept/pdf2/candelabrum3.jpg", pix2, IFF_JFIF_JPEG);
+ GetImageMask(pix2, 200, &boxa1, rp, "/tmp/lept/pdf2/seg1.jpg");
+ convertToPdfSegmented("/tmp/lept/pdf2/candelabrum3.jpg", 200, L_G4_ENCODE,
+ 100, boxa1, 0, 0.25, NULL,
+ "/tmp/lept/pdf2/file12.pdf");
+ convertToPdfSegmented("/tmp/lept/pdf2/candelabrum3.jpg", 200, L_JPEG_ENCODE,
+ 100, boxa1, 0, 0.25, NULL,
+ "/tmp/lept/pdf2/file13.pdf");
+ convertToPdfSegmented("/tmp/lept/pdf2/candelabrum3.jpg", 200, L_FLATE_ENCODE,
+ 100, boxa1, 0, 0.25, NULL,
+ "/tmp/lept/pdf2/file14.pdf");
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxaDestroy(&boxa1);
+
+ pix1 = pixRead("lion-page.00016.jpg");
+ pix2 = pixScale(pix1, 3.0, 3.0);
+ pixWrite("/tmp/lept/pdf2/lion16.jpg", pix2, IFF_JFIF_JPEG);
+ pix3 = pixRead("lion-mask.00016.tif");
+ boxa1 = pixConnComp(pix3, NULL, 8);
+ boxa2 = boxaTransform(boxa1, 0, 0, 3.0, 3.0);
+ convertToPdfSegmented("/tmp/lept/pdf2/lion16.jpg", 200, L_G4_ENCODE,
+ 190, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf2/file15.pdf");
+ convertToPdfSegmented("/tmp/lept/pdf2/lion16.jpg", 200, L_JPEG_ENCODE,
+ 190, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf2/file16.pdf");
+ convertToPdfSegmented("/tmp/lept/pdf2/lion16.jpg", 200, L_FLATE_ENCODE,
+ 190, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf2/file17.pdf");
+
+ /* Quantize the non-image part and flate encode.
+ * This is useful because it results in a smaller file than
+ * when you flate-encode the un-quantized non-image regions. */
+ pix4 = pixScale(pix3, 3.0, 3.0); /* higher res mask, for combining */
+ pix5 = QuantizeNonImageRegion(pix2, pix4, 12);
+ pixWrite("/tmp/lept/pdf2/lion16-quant.png", pix5, IFF_PNG);
+ convertToPdfSegmented("/tmp/lept/pdf2/lion16-quant.png", 200, L_FLATE_ENCODE,
+ 190, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf2/file18.pdf");
+ lept_stderr("Color segmented images time: %7.3f\n", stopTimer());
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+#endif
+
+#if 1
+ /* -- Test simple interface for generating multi-page pdf from images -- */
+ lept_stderr("\n*** Writing multipage pdfs from images");
+ startTimer();
+
+ /* Put four image files in a directory. They will be encoded thus:
+ * file1.png: flate (8 bpp, only 10 colors)
+ * file2.jpg: dct (8 bpp, 256 colors because of the jpeg encoding)
+ * file3.tif: g4 (1 bpp)
+ * file4.jpg: dct (32 bpp) */
+ lept_mkdir("lept/image");
+ pix1 = pixRead("feyn.tif");
+ pix2 = pixRead("rabi.png");
+ pix3 = pixScaleToGray3(pix1);
+ pix4 = pixScaleToGray3(pix2);
+ pix5 = pixScale(pix1, 0.33, 0.33);
+ pix6 = pixRead("test24.jpg");
+ pixWrite("/tmp/lept/image/file1.png", pix3, IFF_PNG); /* 10 colors */
+ pixWrite("/tmp/lept/image/file2.jpg", pix4, IFF_JFIF_JPEG); /* 256 colors */
+ pixWrite("/tmp/lept/image/file3.tif", pix5, IFF_TIFF_G4);
+ pixWrite("/tmp/lept/image/file4.jpg", pix6, IFF_JFIF_JPEG);
+
+ startTimer();
+ convertFilesToPdf("/tmp/lept/image", "file", 100, 0.8, 0, 75, "4 file test",
+ "/tmp/lept/pdf2/file19.pdf");
+ lept_stderr("4-page pdf generated: /tmp/lept/pdf2/file19.pdf\n"
+ "Multi-page gen time: %7.3f\n", stopTimer());
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+#endif
+
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file00.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file01.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file02.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file03.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file04.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file05.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file06.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file07.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file08.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file09.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file10.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file11.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file12.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file13.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file14.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file15.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file16.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file17.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file18.pdf");
+ regTestCheckFile(rp, "/tmp/lept/pdf2/file19.pdf");
+
+#if 1
+ /* ------------------ Test multipage pdf generation ----------------- */
+ lept_stderr("\n*** Writing multipage pdfs from single page pdfs\n");
+
+ /* Generate a multi-page pdf from all these files */
+ startTimer();
+ concatenatePdf("/tmp/lept/pdf2", "file", "/tmp/lept/pdf2/cat_lept.pdf");
+ lept_stderr("All files are concatenated: /tmp/lept/pdf2/cat_lept.pdf\n"
+ "Concatenation time: %7.3f\n", stopTimer());
+#endif
+
+#if 1
+ /* ----------- Test corruption recovery by concatenation ------------ */
+ /* Put two good pdf files in a directory */
+ startTimer();
+ lept_rmdir("lept/good");
+ lept_mkdir("lept/good");
+ lept_cp("testfile1.pdf", "lept/good", NULL, NULL);
+ lept_cp("testfile2.pdf", "lept/good", NULL, NULL);
+ concatenatePdf("/tmp/lept/good", "file", "/tmp/lept/pdf2/good.pdf");
+
+ /* Make a bad version with the pdf id removed, so that it is not
+ * recognized as a pdf */
+ lept_rmdir("lept/bad");
+ lept_mkdir("lept/bad");
+ ba = l_byteaInitFromFile("testfile2.pdf");
+ data = l_byteaGetData(ba, &nbytes);
+ l_binaryWrite("/tmp/lept/bad/testfile0.notpdf.pdf", "w",
+ data + 10, nbytes - 10);
+
+ /* Make a version with a corrupted trailer */
+ if (data)
+ data[2297] = '2'; /* munge trailer object 6: change 458 --> 428 */
+ l_binaryWrite("/tmp/lept/bad/testfile2.bad.pdf", "w", data, nbytes);
+ l_byteaDestroy(&ba);
+
+ /* Copy testfile1.pdf to the /tmp/lept/bad directory. Then
+ * run concat on the bad files. The "not pdf" file should be
+ * ignored, and the corrupted pdf file should be properly parsed,
+ * so the resulting concatenated pdf files should be identical. */
+ lept_stderr("\nWe attempt to build from a bad directory\n");
+ lept_stderr("******************************************************\n");
+ lept_stderr("* The next 3 error messages are intentional *\n");
+ lept_cp("testfile1.pdf", "lept/bad", NULL, NULL);
+ concatenatePdf("/tmp/lept/bad", "file", "/tmp/lept/pdf2/bad.pdf");
+ lept_stderr("******************************************************\n");
+ filesAreIdentical("/tmp/lept/pdf2/good.pdf", "/tmp/lept/pdf2/bad.pdf",
+ &same);
+ if (same)
+ lept_stderr("Fixed: files are the same\nAttempt succeeded\n");
+ else
+ lept_stderr("Busted: files are different\n");
+ lept_stderr("Corruption recovery time: %7.3f\n", stopTimer());
+#endif
+
+#if 0
+{
+ char buffer[512];
+ char *tempfile1, *tempfile2;
+ l_int32 ret;
+
+ lept_stderr("\n*** pdftk writes multipage pdfs from images\n");
+ tempfile1 = genPathname("/tmp/lept/pdf2", "file*.pdf");
+ tempfile2 = genPathname("/tmp/lept/pdf2", "cat_pdftk.pdf");
+ snprintf(buffer, sizeof(buffer), "pdftk %s output %s",
+ tempfile1, tempfile2);
+ ret = system(buffer); /* pdftk */
+ lept_free(tempfile1);
+ lept_free(tempfile2);
+}
+#endif
+
+ return regTestCleanup(rp);
+}
+
+
+static void
+GetImageMask(PIX *pixs,
+ l_int32 res,
+ BOXA **pboxa,
+ L_REGPARAMS *rp,
+ const char *debugfile)
+{
+PIX *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa;
+
+ pixSetResolution(pixs, 200, 200);
+ pix1 = pixConvertTo1(pixs, 100);
+ pix2 = pixGenerateHalftoneMask(pix1, NULL, NULL, NULL);
+ pix3 = pixMorphSequence(pix2, "c20.1 + c1.20", 0);
+ *pboxa = pixConnComp(pix3, NULL, 8);
+ if (debugfile) {
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pixs, L_COPY);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix4 = pixaDisplayTiledInRows(pixa, 32, 1800, 0.25, 0, 25, 2);
+ pixWrite(debugfile, pix4, IFF_JFIF_JPEG);
+ pixDisplayWithTitle(pix4, 100, 100, NULL, rp->display);
+ pixDestroy(&pix4);
+ pixaDestroy(&pixa);
+ } else {
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+
+ return;
+}
+
+static PIX *
+QuantizeNonImageRegion(PIX *pixs,
+ PIX *pixm,
+ l_int32 levels)
+{
+PIX *pix1, *pix2, *pixd;
+
+ pix1 = pixConvertTo8(pixs, 0);
+ pix2 = pixThresholdOn8bpp(pix1, levels, 1);
+ pixd = pixConvertTo32(pix2); /* save in rgb */
+ pixCombineMasked(pixd, pixs, pixm); /* rgb result */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return pixd;
+}
diff --git a/leptonica/prog/pdfseg_reg.c b/leptonica/prog/pdfseg_reg.c
new file mode 100644
index 00000000..0d020152
--- /dev/null
+++ b/leptonica/prog/pdfseg_reg.c
@@ -0,0 +1,183 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * pdfseg_reg.c
+ *
+ * Generates segmented images and encodes them efficiently in pdf.
+ * The encoding is mixed-raster, with the image parts encoded as
+ * DCT at one resolution and the non-image parts encoded at (typically)
+ * a higher resolution.
+ *
+ * Uses 6 images, all segmented and scaled to a fixed width
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* All images scaled to this width */
+static const l_int32 WIDTH = 800;
+
+int main(int argc,
+ char **argv)
+{
+l_int32 h;
+l_float32 scalefactor;
+BOX *box;
+BOXA *boxa1, *boxa2;
+BOXAA *baa;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8, *pix9;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "pdfseg_reg");
+ exit(77);
+#endif
+#if !defined(HAVE_LIBJPEG)
+ L_ERROR("This test requires libjpeg to run.\n", "pdfseg_reg");
+ exit(77);
+#endif
+#if !defined(HAVE_LIBTIFF)
+ L_ERROR("This test requires libtiff to run.\n", "pdfseg_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_rmdir("lept/pdfseg");
+ lept_mkdir("lept/pdfseg");
+ baa = boxaaCreate(5);
+
+ /* Image region input. */
+ pix1 = pixRead("wet-day.jpg");
+ pix2 = pixScaleToSize(pix1, WIDTH, 0);
+ pixWrite("/tmp/lept/pdfseg/0.jpg", pix2, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/pdfseg/0.jpg"); /* 0 */
+ box = boxCreate(105, 161, 620, 872); /* image region */
+ boxa1 = boxaCreate(1);
+ boxaAddBox(boxa1, box, L_INSERT);
+ boxaaAddBoxa(baa, boxa1, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Compute image region at w = 2 * WIDTH */
+ pix1 = pixRead("candelabrum.011.jpg");
+ pix2 = pixScaleToSize(pix1, WIDTH, 0);
+ pix3 = pixConvertTo1(pix2, 100);
+ pix4 = pixExpandBinaryPower2(pix3, 2); /* w = 2 * WIDTH */
+ pix5 = pixGenerateHalftoneMask(pix4, NULL, NULL, NULL);
+ pix6 = pixMorphSequence(pix5, "c20.1 + c1.20", 0);
+ pix7 = pixMaskConnComp(pix6, 8, &boxa1);
+ pix8 = pixReduceBinary2(pix7, NULL); /* back to w = WIDTH */
+ pix9 = pixBackgroundNormSimple(pix2, pix8, NULL);
+ pixWrite("/tmp/lept/pdfseg/1.jpg", pix9, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/pdfseg/1.jpg"); /* 1 */
+ boxa2 = boxaTransform(boxa1, 0, 0, 0.5, 0.5); /* back to w = WIDTH */
+ boxaaAddBoxa(baa, boxa2, L_INSERT);
+ boxaDestroy(&boxa1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ pixDestroy(&pix9);
+
+ /* Use mask to find image region */
+ pix1 = pixRead("lion-page.00016.jpg");
+ pix2 = pixScaleToSize(pix1, WIDTH, 0);
+ pixWrite("/tmp/lept/pdfseg/2.jpg", pix2, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/pdfseg/2.jpg"); /* 2 */
+ pix3 = pixRead("lion-mask.00016.tif");
+ pix4 = pixScaleToSize(pix3, WIDTH, 0);
+ boxa1 = pixConnComp(pix4, NULL, 8);
+ boxaaAddBoxa(baa, boxa1, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Compute image region at full res */
+ pix1 = pixRead("rabi.png");
+ scalefactor = (l_float32)WIDTH / (l_float32)pixGetWidth(pix1);
+ pix2 = pixScaleToGray(pix1, scalefactor);
+ pixWrite("/tmp/lept/pdfseg/3.jpg", pix2, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/pdfseg/3.jpg"); /* 3 */
+ pix3 = pixGenerateHalftoneMask(pix1, NULL, NULL, NULL);
+ pix4 = pixMorphSequence(pix3, "c20.1 + c1.20", 0);
+ boxa1 = pixConnComp(pix4, NULL, 8);
+ boxa2 = boxaTransform(boxa1, 0, 0, scalefactor, scalefactor);
+ boxaaAddBoxa(baa, boxa2, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ boxaDestroy(&boxa1);
+
+ /* Page with no image regions */
+ pix1 = pixRead("lucasta.047.jpg");
+ pix2 = pixScaleToSize(pix1, WIDTH, 0);
+ boxa1 = boxaCreate(1);
+ pixWrite("/tmp/lept/pdfseg/4.jpg", pix2, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/pdfseg/4.jpg"); /* 4 */
+ boxaaAddBoxa(baa, boxa1, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Page that is all image */
+ pix1 = pixRead("map1.jpg");
+ pix2 = pixScaleToSize(pix1, WIDTH, 0);
+ pixWrite("/tmp/lept/pdfseg/5.jpg", pix2, IFF_JFIF_JPEG);
+ regTestCheckFile(rp, "/tmp/lept/pdfseg/5.jpg"); /* 5 */
+ h = pixGetHeight(pix2);
+ box = boxCreate(0, 0, WIDTH, h);
+ boxa1 = boxaCreate(1);
+ boxaAddBox(boxa1, box, L_INSERT);
+ boxaaAddBoxa(baa, boxa1, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Save the boxaa file */
+ boxaaWrite("/tmp/lept/pdfseg/images.baa", baa);
+ regTestCheckFile(rp, "/tmp/lept/pdfseg/images.baa"); /* 6 */
+
+ /* Do the conversion */
+ l_pdfSetDateAndVersion(FALSE);
+ convertSegmentedFilesToPdf("/tmp/lept/pdfseg", "jpg", 100, L_G4_ENCODE,
+ 140, baa, 75, 0.6, "Segmentation Test",
+ "/tmp/lept/regout/pdfseg.7.pdf");
+ L_INFO("Generated pdf file: /tmp/lept/regout/pdfseg.7.pdf\n", rp->testname);
+ regTestCheckFile(rp, "/tmp/lept/regout/pdfseg.7.pdf"); /* 7 */
+
+ boxaaDestroy(&baa);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/pedante.079.jpg b/leptonica/prog/pedante.079.jpg
new file mode 100644
index 00000000..30aae0da
--- /dev/null
+++ b/leptonica/prog/pedante.079.jpg
Binary files differ
diff --git a/leptonica/prog/percolate-4cc.png b/leptonica/prog/percolate-4cc.png
new file mode 100644
index 00000000..c5392e0c
--- /dev/null
+++ b/leptonica/prog/percolate-4cc.png
Binary files differ
diff --git a/leptonica/prog/percolate-8cc.png b/leptonica/prog/percolate-8cc.png
new file mode 100644
index 00000000..a1cfd91a
--- /dev/null
+++ b/leptonica/prog/percolate-8cc.png
Binary files differ
diff --git a/leptonica/prog/percolatetest.c b/leptonica/prog/percolatetest.c
new file mode 100644
index 00000000..eb182b48
--- /dev/null
+++ b/leptonica/prog/percolatetest.c
@@ -0,0 +1,322 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * percolatetest.c
+ *
+ * This tests the code that keeps track of connected components as
+ * pixels are added (randomly, here) to a pix.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static PIX *PixDisplayWithColormap(PIX *pixs, l_int32 repl);
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i, x, y, ncc, npta;
+NUMA *na1;
+PIX *pixs, *pix1, *pix2, *pix3;
+PIXA *pixa;
+PTAA *ptaa;
+
+ if (argc != 1) {
+ lept_stderr(" Syntax: percolatetest\n");
+ return 1;
+ }
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/perc");
+
+ /* Fill in a tiny pix; 4 connected */
+ pixa = pixaCreate(0);
+ pixs = pixCreate(5, 5, 1);
+ pixConnCompIncrInit(pixs, 4, &pix1, &ptaa, &ncc);
+ lept_stderr("ncc = %d, npta = %d\n", ncc, ptaaGetCount(ptaa));
+ srand(26);
+ for (i = 0; i < 50; i++) {
+ pixGetRandomPixel(pix1, NULL, &x, &y);
+ pixConnCompIncrAdd(pix1, ptaa, &ncc, x, y, 2);
+ npta = ptaaGetCount(ptaa);
+ lept_stderr("x,y = (%d,%d), num c.c. = %d, num pta = %d\n",
+ x, y, ncc, npta);
+ pix2 = PixDisplayWithColormap(pix1, 20);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ }
+ pix3 = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 30, 2);
+ pixDisplay(pix3, 0, 0);
+ pixWrite("/tmp/lept/perc/file1.png", pix3, IFF_PNG);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+ ptaaDestroy(&ptaa);
+
+ /* Fill in a tiny pix; 8 connected */
+ pixa = pixaCreate(0);
+ pixs = pixCreate(5, 5, 1);
+ pixConnCompIncrInit(pixs, 8, &pix1, &ptaa, &ncc);
+ lept_stderr("ncc = %d, npta = %d\n", ncc, ptaaGetCount(ptaa));
+ srand(26);
+ for (i = 0; i < 50; i++) {
+ pixGetRandomPixel(pix1, NULL, &x, &y);
+ pixConnCompIncrAdd(pix1, ptaa, &ncc, x, y, 2);
+ npta = ptaaGetCount(ptaa);
+ lept_stderr("x,y = (%d,%d), num c.c. = %d, num pta = %d\n",
+ x, y, ncc, npta);
+ pix2 = PixDisplayWithColormap(pix1, 20);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ }
+ pix3 = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 30, 2);
+ pixDisplay(pix3, 0, 560);
+ pixWrite("/tmp/lept/perc/file2.png", pix3, IFF_PNG);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+ ptaaDestroy(&ptaa);
+
+ /* Fill in a small pix; 4 connected */
+ na1 = numaCreate(700);
+ pixa = pixaCreate(0);
+ pixs = pixCreate(20, 20, 1);
+ pixConnCompIncrInit(pixs, 4, &pix1, &ptaa, &ncc);
+ lept_stderr("ncc = %d, npta = %d\n", ncc, ptaaGetCount(ptaa));
+ srand(26);
+ for (i = 0; i < 700; i++) {
+ pixGetRandomPixel(pix1, NULL, &x, &y);
+ pixConnCompIncrAdd(pix1, ptaa, &ncc, x, y, 2);
+ numaAddNumber(na1, ncc);
+ npta = ptaaGetCount(ptaa);
+ if (i < 100) {
+ lept_stderr("x,y = (%d,%d), num c.c. = %d, num pta = %d\n",
+ x, y, ncc, npta);
+ }
+ if (i % 30 == 1) {
+ pix2 = PixDisplayWithColormap(pix1, 5);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ }
+ }
+ pix3 = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 30, 2);
+ pixDisplay(pix3, 0, 0);
+ pixWrite("/tmp/lept/perc/file3.png", pix3, IFF_PNG);
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/plot1",
+ "Number of components: 4 cc");
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+ ptaaDestroy(&ptaa);
+ numaDestroy(&na1);
+
+ /* Fill in a small pix; 8 connected */
+ na1 = numaCreate(700);
+ pixa = pixaCreate(0);
+ pixs = pixCreate(20, 20, 1);
+ pixConnCompIncrInit(pixs, 8, &pix1, &ptaa, &ncc);
+ lept_stderr("ncc = %d, npta = %d\n", ncc, ptaaGetCount(ptaa));
+ srand(26);
+ for (i = 0; i < 700; i++) {
+ pixGetRandomPixel(pix1, NULL, &x, &y);
+ pixConnCompIncrAdd(pix1, ptaa, &ncc, x, y, 2);
+ numaAddNumber(na1, ncc);
+ npta = ptaaGetCount(ptaa);
+ if (i < 100) {
+ lept_stderr("x,y = (%d,%d), num c.c. = %d, num pta = %d\n",
+ x, y, ncc, npta);
+ }
+ if (i % 30 == 1) {
+ pix2 = PixDisplayWithColormap(pix1, 5);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ }
+ }
+ pix3 = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 30, 2);
+ pixDisplay(pix3, 0, 360);
+ pixWrite("/tmp/lept/perc/file4.png", pix3, IFF_PNG);
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/plot2",
+ "Number of components: 8 cc");
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+ ptaaDestroy(&ptaa);
+ numaDestroy(&na1);
+
+ /* Fill in a larger pix; 4 connected */
+ pixa = pixaCreate(0);
+ na1 = numaCreate(20000);
+ pixs = pixCreate(195, 56, 1);
+ pixConnCompIncrInit(pixs, 4, &pix1, &ptaa, &ncc);
+ lept_stderr("ncc = %d, npta = %d\n", ncc, ptaaGetCount(ptaa));
+ srand(26);
+ for (i = 0; i < 20000; i++) {
+ pixGetRandomPixel(pix1, NULL, &x, &y);
+ pixConnCompIncrAdd(pix1, ptaa, &ncc, x, y, 3);
+ npta = ptaaGetCount(ptaa);
+ numaAddNumber(na1, ncc);
+ if (i % 500 == 1) {
+ pix2 = PixDisplayWithColormap(pix1, 3);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ lept_stderr("x,y = (%d,%d), num c.c. = %d, num pta = %d\n",
+ x, y, ncc, npta);
+ }
+ }
+ pix3 = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 30, 2);
+ pixDisplay(pix3, 0, 0);
+ pixWrite("/tmp/lept/perc/file5.png", pix3, IFF_PNG);
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/plot3",
+ "Number of components: 4 connected");
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+ ptaaDestroy(&ptaa);
+ numaDestroy(&na1);
+
+ /* Fill in a larger pix; 8 connected */
+ pixa = pixaCreate(0);
+ na1 = numaCreate(20000);
+ pixs = pixCreate(195, 56, 1);
+ pixConnCompIncrInit(pixs, 8, &pix1, &ptaa, &ncc);
+ srand(26);
+ lept_stderr("ncc = %d, npta = %d\n", ncc, ptaaGetCount(ptaa));
+ for (i = 0; i < 20000; i++) {
+ pixGetRandomPixel(pix1, NULL, &x, &y);
+ pixConnCompIncrAdd(pix1, ptaa, &ncc, x, y, 3);
+ npta = ptaaGetCount(ptaa);
+ numaAddNumber(na1, ncc);
+ if (i % 500 == 1) {
+ pix2 = PixDisplayWithColormap(pix1, 3);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ lept_stderr("x,y = (%d,%d), num c.c. = %d, num pta = %d\n",
+ x, y, ncc, npta);
+ }
+ }
+ pix3 = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 30, 2);
+ pixDisplay(pix3, 340, 0);
+ pixWrite("/tmp/lept/perc/file6.png", pix3, IFF_PNG);
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/plot4",
+ "Number of components: 8 connected");
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+ ptaaDestroy(&ptaa);
+ numaDestroy(&na1);
+
+ /* Fill in a larger pix; 8 connected; init with feyn-word.tif */
+ pixa = pixaCreate(0);
+ na1 = numaCreate(20000);
+ pixs = pixRead("feyn-word.tif");
+ pixConnCompIncrInit(pixs, 8, &pix1, &ptaa, &ncc);
+ srand(26);
+ lept_stderr("ncc = %d, npta = %d\n", ncc, ptaaGetCount(ptaa));
+ for (i = 0; i < 20000; i++) {
+ pixGetRandomPixel(pix1, NULL, &x, &y);
+ pixConnCompIncrAdd(pix1, ptaa, &ncc, x, y, 3);
+ npta = ptaaGetCount(ptaa);
+ numaAddNumber(na1, ncc);
+ if (i % 500 == 1) {
+ pix2 = PixDisplayWithColormap(pix1, 3);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ lept_stderr("x,y = (%d,%d), num c.c. = %d, num pta = %d\n",
+ x, y, ncc, npta);
+ }
+ }
+ pix3 = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 30, 2);
+ pixDisplay(pix3, 0, 0);
+ pixWrite("/tmp/lept/perc/file7.png", pix3, IFF_PNG);
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/plot5",
+ "Number of components: 8 connected");
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+ ptaaDestroy(&ptaa);
+ numaDestroy(&na1);
+
+ /* Do 10M pixel adds on an 8M pixel image!
+ * This gets it down to about 385 8-connected components.
+ * With 18.3M pixel adds, you finally arrive at 1 component.
+ * Speed: this does about 1.3M pixel adds/sec. Most of the time
+ * is used to write the 280MB plot data file and then generate
+ * the plot (percolate-4cc.png, percolate-8cc.png). */
+#if 0
+ pixs = pixRead("feyn.tif");
+// pixs = pixCreate(2500, 3200, 1);
+ na1 = numaCreate(10000000);
+ pixConnCompIncrInit(pixs, 4, &pix1, &ptaa, &ncc);
+ pix2 = PixDisplayWithColormap(pix1, 1);
+ pixDisplay(pix2, 0, 0);
+ pixDestroy(&pix2);
+ lept_stderr("ncc = %d, npta = %d\n", ncc, ptaaGetCount(ptaa));
+ lept_stderr("Now add 10M points: this takes about 7 seconds!\n");
+ for (i = 0; i < 10000000; i++) {
+ pixGetRandomPixel(pix1, NULL, &x, &y);
+ pixConnCompIncrAdd(pix1, ptaa, &ncc, x, y, 0);
+ numaAddNumber(na1, ncc);
+ }
+
+ lept_stderr("Plot the 10M points: this takes about 20 seconds\n");
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/plot6",
+ "Number of components: 4 connected, 8 million pixels");
+ pix3 = pixRead("/tmp/lept/plot6.png");
+ pixDisplay(pix3, 500, 0);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+ ptaaDestroy(&ptaa);
+ numaDestroy(&na1);
+#endif
+ return 0;
+}
+
+
+ /* This displays a pix where the pixel values are 32 bit labels
+ * using a random colormap whose index is the LSB of each pixel value,
+ * and where white is at index 0. */
+PIX *
+PixDisplayWithColormap(PIX *pixs, l_int32 repl)
+{
+PIX *pix1, *pixd;
+PIXCMAP *cmap;
+
+ cmap = pixcmapCreateRandom(8, 0, 0);
+ pixcmapResetColor(cmap, 0, 255, 255, 255);
+ pix1 = pixConvert32To8(pixs, L_LS_TWO_BYTES, L_LS_BYTE);
+ pixd = pixExpandReplicate(pix1, repl);
+ pixSetColormap(pixd, cmap);
+ pixDestroy(&pix1);
+ return pixd;
+}
+
diff --git a/leptonica/prog/pixa1_reg.c b/leptonica/prog/pixa1_reg.c
new file mode 100644
index 00000000..5de883b3
--- /dev/null
+++ b/leptonica/prog/pixa1_reg.c
@@ -0,0 +1,170 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * pixa1_reg.c
+ *
+ * Tests removal of connected components.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 CONNECTIVITY = 8;
+
+int main(int argc,
+ char **argv)
+{
+l_int32 size, i, n, n0;
+BOXA *boxa;
+GPLOT *gplot;
+NUMA *nax, *nay1, *nay2;
+PIX *pixs, *pix1, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn-fract.tif");
+ lept_mkdir("lept/pixa");
+ pixa = pixaCreate(2);
+
+ /* ---------------- Remove small components --------------- */
+ boxa = pixConnComp(pixs, NULL, 8);
+ n0 = boxaGetCount(boxa);
+ nax = numaMakeSequence(0, 2, 51);
+ nay1 = numaCreate(51);
+ nay2 = numaCreate(51);
+ boxaDestroy(&boxa);
+
+ if (rp->display) {
+ lept_stderr("\n Select Large if Both\n");
+ lept_stderr("Iter 0: n = %d\n", n0);
+ }
+ numaAddNumber(nay1, n0);
+ for (i = 1; i <= 50; i++) {
+ size = 2 * i;
+ pixd = pixSelectBySize(pixs, size, size, CONNECTIVITY,
+ L_SELECT_IF_BOTH, L_SELECT_IF_GTE, NULL);
+ boxa = pixConnComp(pixd, NULL, 8);
+ n = boxaGetCount(boxa);
+ numaAddNumber(nay1, n);
+ if (rp->display) lept_stderr("Iter %d: n = %d\n", i, n);
+ boxaDestroy(&boxa);
+ pixDestroy(&pixd);
+ }
+
+ if (rp->display) {
+ lept_stderr("\n Select Large if Either\n");
+ lept_stderr("Iter 0: n = %d\n", n0);
+ }
+ numaAddNumber(nay2, n0);
+ for (i = 1; i <= 50; i++) {
+ size = 2 * i;
+ pixd = pixSelectBySize(pixs, size, size, CONNECTIVITY,
+ L_SELECT_IF_EITHER, L_SELECT_IF_GTE, NULL);
+ boxa = pixConnComp(pixd, NULL, 8);
+ n = boxaGetCount(boxa);
+ numaAddNumber(nay2, n);
+ if (rp->display) lept_stderr("Iter %d: n = %d\n", i, n);
+ boxaDestroy(&boxa);
+ pixDestroy(&pixd);
+ }
+
+ gplot = gplotCreate("/tmp/lept/pixa/root1", GPLOT_PNG,
+ "Select large: number of cc vs size removed",
+ "min size", "number of c.c.");
+ gplotAddPlot(gplot, nax, nay1, GPLOT_LINES, "select if both");
+ gplotAddPlot(gplot, nax, nay2, GPLOT_LINES, "select if either");
+ pix1 = gplotMakeOutputPix(gplot);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ gplotDestroy(&gplot);
+
+ /* ---------------- Remove large components --------------- */
+ numaEmpty(nay1);
+ numaEmpty(nay2);
+
+ if (rp->display) {
+ lept_stderr("\n Select Small if Both\n");
+ lept_stderr("Iter 0: n = %d\n", 0);
+ }
+ numaAddNumber(nay1, 0);
+ for (i = 1; i <= 50; i++) {
+ size = 2 * i;
+ pixd = pixSelectBySize(pixs, size, size, CONNECTIVITY,
+ L_SELECT_IF_BOTH, L_SELECT_IF_LTE, NULL);
+ boxa = pixConnComp(pixd, NULL, 8);
+ n = boxaGetCount(boxa);
+ numaAddNumber(nay1, n);
+ if (rp->display) lept_stderr("Iter %d: n = %d\n", i, n);
+ boxaDestroy(&boxa);
+ pixDestroy(&pixd);
+ }
+
+ if (rp->display) {
+ lept_stderr("\n Select Small if Either\n");
+ lept_stderr("Iter 0: n = %d\n", 0);
+ }
+ numaAddNumber(nay2, 0);
+ for (i = 1; i <= 50; i++) {
+ size = 2 * i;
+ pixd = pixSelectBySize(pixs, size, size, CONNECTIVITY,
+ L_SELECT_IF_EITHER, L_SELECT_IF_LTE, NULL);
+ boxa = pixConnComp(pixd, NULL, 8);
+ n = boxaGetCount(boxa);
+ numaAddNumber(nay2, n);
+ if (rp->display) lept_stderr("Iter %d: n = %d\n", i, n);
+ boxaDestroy(&boxa);
+ pixDestroy(&pixd);
+ }
+
+ gplot = gplotCreate("/tmp/lept/pixa/root2", GPLOT_PNG,
+ "Remove large: number of cc vs size removed",
+ "min size", "number of c.c.");
+ gplotAddPlot(gplot, nax, nay1, GPLOT_LINES, "select if both");
+ gplotAddPlot(gplot, nax, nay2, GPLOT_LINES, "select if either");
+ pix1 = gplotMakeOutputPix(gplot);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ gplotDestroy(&gplot);
+
+ pixd = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplayWithTitle(pixd, 100, 0, NULL, rp->display);
+ pixWrite("/tmp/lept/pixa/root.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ numaDestroy(&nax);
+ numaDestroy(&nay1);
+ numaDestroy(&nay2);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/pixa2_reg.c b/leptonica/prog/pixa2_reg.c
new file mode 100644
index 00000000..5b651df9
--- /dev/null
+++ b/leptonica/prog/pixa2_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * pixa2_reg.c
+ *
+ * Tests various replacement functions on pixa.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+const char *name;
+l_int32 i, n;
+BOX *box;
+PIX *pix0, *pix1, *pixd;
+PIXA *pixa;
+SARRAY *sa1, *sa2, *sa3, *sa4;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* ---------------- Find all the jpg and tif images --------------- */
+ sa1 = getSortedPathnamesInDirectory(".", ".jpg", 0, 0);
+ sa2 = getSortedPathnamesInDirectory(".", ".tif", 0, 0);
+ sa3 = sarraySelectByRange(sa1, 10, 19);
+ sa4 = sarraySelectByRange(sa2, 10, 19);
+ sarrayJoin(sa3, sa4);
+ n =sarrayGetCount(sa3);
+ sarrayDestroy(&sa1);
+ sarrayDestroy(&sa2);
+ sarrayDestroy(&sa4);
+ if (rp->display) sarrayWriteStream(stderr, sa3);
+
+ /* ---------------- Use replace to fill up a pixa -------------------*/
+ pixa = pixaCreate(1);
+ pixaExtendArrayToSize(pixa, n);
+ if ((pix0 = pixRead("marge.jpg")) == NULL)
+ rp->success = FALSE;
+ pix1 = pixScaleToSize(pix0, 144, 108); /* scale 0.25 */
+ pixDestroy(&pix0);
+ pixaInitFull(pixa, pix1, NULL); /* fill it up */
+ pixd = pixaDisplayTiledInRows(pixa, 32, 1000, 1.0, 0, 25, 2);
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixWrite("/tmp/lept/regout/pixa2-1.jpg", pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pix1);
+ pixDestroy(&pixd);
+
+ /* ---------------- And again with jpgs and tifs -------------------*/
+ for (i = 0; i < n; i++) {
+ name = sarrayGetString(sa3, i, L_NOCOPY);
+ if ((pix0 = pixRead(name)) == NULL) {
+ lept_stderr("Error in %s_reg: failed to read %s\n",
+ rp->testname, name);
+ rp->success = FALSE;
+ continue;
+ }
+ pix1 = pixScaleToSize(pix0, 144, 108);
+ pixaReplacePix(pixa, i, pix1, NULL);
+ pixDestroy(&pix0);
+ }
+ pixd = pixaDisplayTiledInRows(pixa, 32, 1000, 1.0, 0, 25, 2);
+ pixDisplayWithTitle(pixd, 400, 100, NULL, rp->display);
+ pixWrite("/tmp/lept/regout/pixa2-2.jpg", pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixd);
+
+ /* ---------------- And again, reversing the order ------------------*/
+ box = boxCreate(0, 0, 0, 0);
+ pixaInitFull(pixa, NULL, box);
+ boxDestroy(&box);
+ for (i = 0; i < n; i++) {
+ name = sarrayGetString(sa3, i, L_NOCOPY);
+ if ((pix0 = pixRead(name)) == NULL) {
+ lept_stderr("Error in %s_reg: failed to read %s\n",
+ rp->testname, name);
+ rp->success = FALSE;
+ continue;
+ }
+ pix1 = pixScaleToSize(pix0, 144, 108);
+ pixaReplacePix(pixa, n - 1 - i, pix1, NULL);
+ pixDestroy(&pix0);
+ }
+ pixd = pixaDisplayTiledInRows(pixa, 32, 1000, 1.0, 0, 25, 2);
+ pixDisplayWithTitle(pixd, 700, 100, NULL, rp->display);
+ pixWrite("/tmp/lept/regout/pixa2-3.jpg", pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixd);
+ sarrayDestroy(&sa3);
+
+ pixaDestroy(&pixa);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/pixaatest.c b/leptonica/prog/pixaatest.c
new file mode 100644
index 00000000..2053f01c
--- /dev/null
+++ b/leptonica/prog/pixaatest.c
@@ -0,0 +1,109 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * pixaatest.c
+ *
+ * Syntax: pixaatest
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 nx = 10;
+static const l_int32 ny = 12;
+static const l_int32 ncols = 3;
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w, d, tilewidth;
+PIX *pixs;
+PIXA *pixa, *pixad1, *pixad2;
+PIXAA *pixaa1, *pixaa2;
+static char mainName[] = "pixaatest";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: pixaatest", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("/lept/paa");
+
+ /* Read in file, split it into a set of tiles, and generate a pdf.
+ * Two things to note for these tiny images:
+ * (1) If you use dct format (jpeg) for each image instead of
+ * flate (lossless), the quantization will be apparent.
+ * (2) If the resolution in pixaConvertToPdf() is above 50, and
+ * you add a red boundary, you will see errors in the boundary
+ * width.
+ */
+ pixs = pixRead("test24.jpg");
+ pixGetDimensions(pixs, &w, NULL, &d);
+ pixa = pixaSplitPix(pixs, nx, ny, 0, 0);
+/* pixa = pixaSplitPix(pixs, nx, ny, 2, 0xff000000); */ /* red border */
+ pixWrite("/tmp/lept/paa/pix0", pixa->pix[0], IFF_PNG);
+ pixWrite("/tmp/lept/paa/pix9", pixa->pix[9], IFF_PNG);
+ pixaConvertToPdf(pixa, 50, 1.0, 0, 95, "individual",
+ "/tmp/lept/paa/out1.pdf");
+
+ /* Generate two pixaa by sampling the pixa, and write them to file */
+ pixaa1 = pixaaCreateFromPixa(pixa, nx, L_CHOOSE_CONSECUTIVE, L_CLONE);
+ pixaa2 = pixaaCreateFromPixa(pixa, nx, L_CHOOSE_SKIP_BY, L_CLONE);
+ pixaaWrite("/tmp/lept/paa/pts1.paa", pixaa1);
+ pixaaWrite("/tmp/lept/paa/pts2.paa", pixaa2);
+ pixaDestroy(&pixa);
+ pixaaDestroy(&pixaa1);
+ pixaaDestroy(&pixaa2);
+
+ /* Read each pixaa from file; tile/scale into a pixa */
+ pixaa1 = pixaaRead("/tmp/lept/paa/pts1.paa");
+ pixaa2 = pixaaRead("/tmp/lept/paa/pts2.paa");
+ tilewidth = w / nx;
+ pixad1 = pixaaDisplayTiledAndScaled(pixaa1, d, tilewidth, ncols, 0, 10, 0);
+ pixad2 = pixaaDisplayTiledAndScaled(pixaa2, d, tilewidth, ncols, 0, 10, 0);
+
+ /* Generate a pdf from each pixa */
+ pixaConvertToPdf(pixad1, 50, 1.0, 0, 75, "consecutive",
+ "/tmp/lept/paa/out2.pdf");
+ pixaConvertToPdf(pixad2, 50, 1.0, 0, 75, "skip_by",
+ "/tmp/lept/paa/out3.pdf");
+
+ /* Write each pixa to a set of files, and generate a PS */
+ pixaWriteFiles("/tmp/lept/paa/split1.", pixad1, IFF_JFIF_JPEG);
+ pixaWriteFiles("/tmp/lept/paa/split2.", pixad2, IFF_JFIF_JPEG);
+ convertFilesToPS("/tmp/lept/paa", "split1", 40, "/tmp/lept/paa/out1out1.ps");
+ convertFilesToPS("/tmp/lept/paa", "split2", 40, "/tmp/lept/paa/out1out2.ps");
+
+ pixDestroy(&pixs);
+ pixaaDestroy(&pixaa1);
+ pixaaDestroy(&pixaa2);
+ pixaDestroy(&pixad1);
+ pixaDestroy(&pixad2);
+ return 0;
+}
diff --git a/leptonica/prog/pixadisp_reg.c b/leptonica/prog/pixadisp_reg.c
new file mode 100644
index 00000000..52116a44
--- /dev/null
+++ b/leptonica/prog/pixadisp_reg.c
@@ -0,0 +1,166 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * pixadisp_reg.c
+ *
+ * Regression test exercising various pixaDisplay*() functions.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 ws, hs, ncols;
+BOX *box;
+BOXA *boxa;
+PIX *pixs, *pix32, *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa, *pixa1, *pixa2, *pixa3;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixa = pixaCreate(0);
+ pix32 = pixRead("marge.jpg");
+ pixs = pixRead("feyn.tif");
+ box = boxCreate(683, 799, 970, 479);
+ pix1 = pixClipRectangle(pixs, box, NULL);
+ boxDestroy(&box);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Generate pixa2 from pixs and pixa3 from pix1 */
+ boxa = pixConnComp(pixs, &pixa1, 8);
+ pixa2 = pixaSelectBySize(pixa1, 60, 60, L_SELECT_IF_BOTH,
+ L_SELECT_IF_LTE, NULL);
+ pixaDestroy(&pixa1);
+ boxaDestroy(&boxa);
+ boxa = pixConnComp(pix1, &pixa3, 8);
+ boxaDestroy(&boxa);
+
+ /* pixaDisplay() */
+ pixGetDimensions(pixs, &ws, &hs, NULL);
+ pix2 = pixaDisplay(pixa2, ws, hs);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixDestroy(&pixs);
+
+ /* pixaDisplayRandomCmap() */
+ pix2 = pixaDisplayRandomCmap(pixa2, ws, hs); /* black bg */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 2 */
+ pixaAddPix(pixa, pix2, L_COPY);
+ pixcmapResetColor(pixGetColormap(pix2), 0, 255, 255, 255); /* white bg */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 3 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaDestroy(&pixa2);
+
+ /* pixaDisplayOnLattice() */
+ pix2 = pixaDisplayOnLattice(pixa3, 50, 50, &ncols, &boxa);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 4 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ lept_stderr("Number of columns = %d; number of boxes: %d\n",
+ ncols, boxaGetCount(boxa));
+ boxaDestroy(&boxa);
+
+ /* pixaDisplayUnsplit() */
+ pixa1 = pixaSplitPix(pix32, 5, 7, 10, 0x0000ff00);
+ pix2 = pixaDisplayUnsplit(pixa1, 5, 7, 10, 0x00ff0000);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 5 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaDestroy(&pixa1);
+
+ /* pixaDisplayTiled() */
+ pix2 = pixaDisplayTiled(pixa3, 1000, 0, 10);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 6 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ /* pixaDisplayTiledInRows() */
+ pix2 = pixaDisplayTiledInRows(pixa3, 1, 1000, 1.0, 0, 10, 2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 7 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ /* pixaDisplayTiledAndScaled() */
+ pix2 = pixaDisplayTiledAndScaled(pixa3, 1, 25, 20, 0, 5, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 8 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaDestroy(&pixa3);
+
+ pixa1 = pixaCreate(10);
+ pix2 = pixRankFilter(pix32, 8, 8, 0.5);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pix3 = pixScale(pix32, 0.5, 0.5);
+ pix2 = pixRankFilter(pix3, 8, 8, 0.5);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixDestroy(&pix3);
+ pix3 = pixScale(pix32, 0.25, 0.25);
+ pix2 = pixRankFilter(pix3, 8, 8, 0.5);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixDestroy(&pix3);
+ pix2 = pixaDisplayTiledAndScaled(pixa1, 32, 500, 3, 0, 25, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 9 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaDestroy(&pixa1);
+ pixDestroy(&pix32);
+
+ /* pixaMakeFromTiledPix() and pixaDisplayOnLattice() */
+ pix1 = pixRead("sevens.tif");
+ pixa1 = pixaMakeFromTiledPix(pix1, 20, 30, 0, 0, NULL);
+ pix2 = pixaDisplayOnLattice(pixa1, 20, 30, NULL, NULL);
+ regTestComparePix(rp, pix1, pix2); /* 10 */
+ pix3 = pixaDisplayOnLattice(pixa1, 20, 30, NULL, &boxa);
+ pixa2 = pixaMakeFromTiledPix(pix3, 0, 0, 0, 0, boxa);
+ pix4 = pixaDisplayOnLattice(pixa2, 20, 30, NULL, NULL);
+ regTestComparePix(rp, pix2, pix4); /* 11 */
+ pixaAddPix(pixa, pixScale(pix4, 2.5, 2.5), L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+
+ if (rp->display) {
+ lept_mkdir("lept/padisp");
+ lept_stderr("Writing to: /tmp/lept/padisp/pixadisp.pdf\n");
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, "pixadisp-test",
+ "/tmp/lept/padisp/pixadisp.pdf");
+ lept_stderr("Writing to: /tmp/lept/padisp/pixadisp.jpg\n");
+ pix1 = pixaDisplayTiledInColumns(pixa, 2, 0.5, 30, 2);
+ pixWrite("/tmp/lept/padisp/pixadisp.jpg", pix1, IFF_JFIF_JPEG);
+ pixDisplay(pix1, 100, 100);
+ pixDestroy(&pix1);
+ }
+
+ pixaDestroy(&pixa);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/pixafileinfo.c b/leptonica/prog/pixafileinfo.c
new file mode 100644
index 00000000..07ba9454
--- /dev/null
+++ b/leptonica/prog/pixafileinfo.c
@@ -0,0 +1,82 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * pixafileinfo.c
+ *
+ * Returns information about the images in the pixa or pixacomp file
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char buf[64];
+char *sn;
+l_int32 i, n;
+PIX *pix;
+PIXA *pixa;
+PIXAC *pac;
+char *filein;
+static char mainName[] = "pixafileinfo";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: pixafileinfo filein", mainName, 1);
+ setLeptDebugOK(1);
+
+ /* Input file can be either pixa or pixacomp */
+ filein = argv[1];
+ l_getStructStrFromFile(filein, L_STR_NAME, &sn);
+ if (strcmp(sn, "Pixa") == 0) {
+ if ((pixa = pixaRead(filein)) == NULL)
+ return ERROR_INT("pixa not made", mainName, 1);
+ } else if (strcmp(sn, "Pixacomp") == 0) {
+ if ((pac = pixacompRead(filein)) == NULL)
+ return ERROR_INT("pac not made", mainName, 1);
+ pixa = pixaCreateFromPixacomp(pac, L_COPY);
+ pixacompDestroy(&pac);
+ } else {
+ return ERROR_INT("invalid file type", mainName, 1);
+ }
+
+ n = pixaGetCount(pixa);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixa, i, L_CLONE);
+ snprintf(buf, sizeof(buf), "Pix(%d)", i);
+ pixPrintStreamInfo(stderr, pix, buf);
+ pixDestroy(&pix);
+ lept_stderr("=================================\n");
+ }
+
+ pixaDestroy(&pixa);
+ return 0;
+}
diff --git a/leptonica/prog/pixalloc_reg.c b/leptonica/prog/pixalloc_reg.c
new file mode 100644
index 00000000..3ee29022
--- /dev/null
+++ b/leptonica/prog/pixalloc_reg.c
@@ -0,0 +1,208 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Tests custom pix allocator.
+ *
+ * The custom allocator is intended for situations where a number of large
+ * pix will be repeatedly allocated and freed over the lifetime of a program.
+ * If those pix are large, relying on malloc and free can result in
+ * fragmentation, even if there are no small memory leaks in the program.
+ *
+ * Here we test the allocator in two situations:
+ * * a small number of relatively large pix
+ * * a large number of very small pix
+ *
+ * For the second case, timing shows that the custom allocator does
+ * about as well as (malloc, free), even for thousands of very small pix.
+ * (Turn off logging to get a fair comparison).
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+static const l_int32 logging = FALSE;
+
+static const l_int32 ncopies = 2;
+static const l_int32 nlevels = 4;
+static const l_int32 ntimes = 30;
+
+
+PIXA *GenerateSetOfMargePix(void);
+void CopyStoreClean(PIXA *pixas, l_int32 nlevels, l_int32 ncopies);
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i;
+BOXA *boxa;
+NUMA *nas, *nab;
+PIX *pixs;
+PIXA *pixa, *pixas;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/alloc");
+
+ /* ----------------- Custom with a few large pix -----------------*/
+ /* Set up pms */
+ nas = numaCreate(4); /* small */
+ numaAddNumber(nas, 5);
+ numaAddNumber(nas, 4);
+ numaAddNumber(nas, 3);
+ numaAddNumber(nas, 2);
+ setPixMemoryManager(pmsCustomAlloc, pmsCustomDealloc);
+ pmsCreate(200000, 400000, nas, "/tmp/lept/alloc/file1.log");
+
+ /* Make the pix and do successive copies and removals of the copies */
+ pixas = GenerateSetOfMargePix();
+ startTimer();
+ for (i = 0; i < ntimes; i++)
+ CopyStoreClean(pixas, nlevels, ncopies);
+ lept_stderr("Time (big pix; custom) = %7.3f sec\n", stopTimer());
+
+ /* Clean up */
+ numaDestroy(&nas);
+ pixaDestroy(&pixas);
+ pmsDestroy();
+
+
+ /* ----------------- Standard with a few large pix -----------------*/
+ setPixMemoryManager(malloc, free);
+
+ /* Make the pix and do successive copies and removals of the copies */
+ startTimer();
+ pixas = GenerateSetOfMargePix();
+ for (i = 0; i < ntimes; i++)
+ CopyStoreClean(pixas, nlevels, ncopies);
+ lept_stderr("Time (big pix; standard) = %7.3f sec\n", stopTimer());
+ pixaDestroy(&pixas);
+
+
+ /* ----------------- Custom with many small pix -----------------*/
+ /* Set up pms */
+ nab = numaCreate(10);
+ numaAddNumber(nab, 2000);
+ numaAddNumber(nab, 2000);
+ numaAddNumber(nab, 2000);
+ numaAddNumber(nab, 500);
+ numaAddNumber(nab, 100);
+ numaAddNumber(nab, 100);
+ numaAddNumber(nab, 100);
+ setPixMemoryManager(pmsCustomAlloc, pmsCustomDealloc);
+ if (logging) /* use logging == 0 for speed comparison */
+ pmsCreate(20, 40, nab, "/tmp/lept/alloc/file2.log");
+ else
+ pmsCreate(20, 40, nab, NULL);
+ pixs = pixRead("feyn.tif");
+
+ startTimer();
+ for (i = 0; i < 5; i++) {
+ boxa = pixConnComp(pixs, &pixa, 8);
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa);
+ }
+
+ numaDestroy(&nab);
+ pixDestroy(&pixs);
+ pmsDestroy();
+ lept_stderr("Time (custom) = %7.3f sec\n", stopTimer());
+
+
+ /* ----------------- Standard with many small pix -----------------*/
+ setPixMemoryManager(malloc, free);
+ pixs = pixRead("feyn.tif");
+
+ startTimer();
+ for (i = 0; i < 5; i++) {
+ boxa = pixConnComp(pixs, &pixa, 8);
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa);
+ }
+ pixDestroy(&pixs);
+ lept_stderr("Time (standard) = %7.3f sec\n", stopTimer());
+ return 0;
+}
+
+
+PIXA *
+GenerateSetOfMargePix(void)
+{
+l_float32 factor;
+BOX *box;
+PIX *pixs, *pixt1, *pixt2, *pixt3, *pixt4;
+PIXA *pixa;
+
+ pixs = pixRead("marge.jpg");
+ box = boxCreate(130, 93, 263, 253);
+ factor = sqrt(2.0);
+ pixt1 = pixClipRectangle(pixs, box, NULL); /* 266 KB */
+ pixt2 = pixScale(pixt1, factor, factor); /* 532 KB */
+ pixt3 = pixScale(pixt2, factor, factor); /* 1064 KB */
+ pixt4 = pixScale(pixt3, factor, factor); /* 2128 KB */
+ pixa = pixaCreate(4);
+ pixaAddPix(pixa, pixt1, L_INSERT);
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixaAddPix(pixa, pixt3, L_INSERT);
+ pixaAddPix(pixa, pixt4, L_INSERT);
+ boxDestroy(&box);
+ pixDestroy(&pixs);
+ return pixa;
+}
+
+
+void
+CopyStoreClean(PIXA *pixas,
+ l_int32 nlevels,
+ l_int32 ncopies)
+{
+l_int32 i, j;
+PIX *pix, *pixt;
+PIXA *pixa;
+PIXAA *paa;
+
+ paa = pixaaCreate(0);
+ for (i = 0; i < nlevels ; i++) {
+ pixa = pixaCreate(0);
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ pix = pixaGetPix(pixas, i, L_CLONE);
+ for (j = 0; j < ncopies; j++) {
+ pixt = pixCopy(NULL, pix);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ }
+ pixDestroy(&pix);
+ }
+ pixaaDestroy(&paa);
+
+ return;
+}
+
diff --git a/leptonica/prog/pixcomp_reg.c b/leptonica/prog/pixcomp_reg.c
new file mode 100644
index 00000000..ea581541
--- /dev/null
+++ b/leptonica/prog/pixcomp_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * pixcomp_reg.c
+ *
+ * Regression test for compressed pix and compressed pix arrays
+ * in memory.
+ *
+ * We also show some other ways to accumulate and display pixa.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+static const char *fnames[] = {"weasel32.png", "weasel2.4c.png",
+ "weasel4.16c.png", "weasel4.8g.png",
+ "weasel8.149g.png", "weasel8.16g.png"};
+
+LEPT_DLL extern const char *ImageFileFormatExtensions[];
+static void get_format_data(l_int32 i, l_uint8 *data, size_t size);
+
+int main(int argc,
+ char **argv)
+{
+l_uint8 *data1, *data2;
+l_int32 i;
+size_t size1, size2;
+BOX *box;
+PIX *pix, *pix1, *pix2, *pix3;
+PIXA *pixa, *pixa1;
+PIXC *pixc, *pixc1, *pixc2;
+PIXAC *pixac, *pixac1, *pixac2;
+L_REGPARAMS *rp;
+SARRAY *sa;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/comp");
+
+ pixac = pixacompCreate(1);
+ pixa = pixaCreate(0);
+
+ /* --- Read in the images --- */
+ pix1 = pixRead("marge.jpg");
+ pixc1 = pixcompCreateFromPix(pix1, IFF_JFIF_JPEG);
+ pix2 = pixCreateFromPixcomp(pixc1);
+ pixc2 = pixcompCreateFromPix(pix2, IFF_JFIF_JPEG);
+ pix3 = pixCreateFromPixcomp(pixc2);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 0 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixacompAddPix(pixac, pix1, IFF_DEFAULT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixcompDestroy(&pixc1);
+ pixcompDestroy(&pixc2);
+
+ pix = pixRead("feyn.tif");
+ pix1 = pixScaleToGray6(pix);
+ pixc1 = pixcompCreateFromPix(pix1, IFF_JFIF_JPEG);
+ pix2 = pixCreateFromPixcomp(pixc1);
+ pixc2 = pixcompCreateFromPix(pix2, IFF_JFIF_JPEG);
+ pix3 = pixCreateFromPixcomp(pixc2);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 1 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixacompAddPix(pixac, pix1, IFF_DEFAULT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixcompDestroy(&pixc1);
+ pixcompDestroy(&pixc2);
+
+ box = boxCreate(1144, 611, 690, 180);
+ pix1 = pixClipRectangle(pix, box, NULL);
+ pixc1 = pixcompCreateFromPix(pix1, IFF_TIFF_G4);
+ pix2 = pixCreateFromPixcomp(pixc1);
+ pixc2 = pixcompCreateFromPix(pix2, IFF_TIFF_G4);
+ pix3 = pixCreateFromPixcomp(pixc2);
+ regTestWritePixAndCheck(rp, pix3, IFF_TIFF_G4); /* 2 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixacompAddPix(pixac, pix1, IFF_DEFAULT);
+ boxDestroy(&box);
+ pixDestroy(&pix);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixcompDestroy(&pixc1);
+ pixcompDestroy(&pixc2);
+
+ pix1 = pixRead("weasel4.11c.png");
+ pixc1 = pixcompCreateFromPix(pix1, IFF_PNG);
+ pix2 = pixCreateFromPixcomp(pixc1);
+ pixc2 = pixcompCreateFromPix(pix2, IFF_PNG);
+ pix3 = pixCreateFromPixcomp(pixc2);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixacompAddPix(pixac, pix1, IFF_DEFAULT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixcompDestroy(&pixc1);
+ pixcompDestroy(&pixc2);
+
+ /* --- Extract formatting info from compressed strings --- */
+ for (i = 0; i < 4; i++) {
+ pixc = pixacompGetPixcomp(pixac, i, L_NOCOPY);
+ get_format_data(i, pixc->data, pixc->size);
+ }
+
+ /* Save a tiled composite from the pixa */
+ pix1 = pixaDisplayTiledAndScaled(pixa, 32, 400, 4, 0, 20, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 4 */
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+
+ /* Convert the pixac --> pixa and save a tiled composite */
+ pixa1 = pixaCreateFromPixacomp(pixac, L_COPY);
+ pix1 = pixaDisplayTiledAndScaled(pixa1, 32, 400, 4, 0, 20, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 5 */
+ pixaDestroy(&pixa1);
+ pixDestroy(&pix1);
+
+ /* Make a pixacomp from files, and join */
+ sa = sarrayCreate(0);
+ for (i = 0; i < 6; i++)
+ sarrayAddString(sa, fnames[i], L_COPY);
+ pixac1 = pixacompCreateFromSA(sa, IFF_DEFAULT);
+ pixacompJoin(pixac1, pixac, 0, -1);
+ pixa1 = pixaCreateFromPixacomp(pixac1, L_COPY);
+ pix1 = pixaDisplayTiledAndScaled(pixa1, 32, 250, 10, 0, 20, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 6 */
+ pixacompDestroy(&pixac1);
+ pixaDestroy(&pixa1);
+ pixDestroy(&pix1);
+ sarrayDestroy(&sa);
+
+ /* Test serialized I/O */
+ pixacompWrite("/tmp/lept/comp/file1.pac", pixac);
+ regTestCheckFile(rp, "/tmp/lept/comp/file1.pac"); /* 7 */
+ pixac1 = pixacompRead("/tmp/lept/comp/file1.pac");
+ pixacompWrite("/tmp/lept/comp/file2.pac", pixac1);
+ regTestCheckFile(rp, "/tmp/lept/comp/file2.pac"); /* 8 */
+ regTestCompareFiles(rp, 7, 8); /* 9 */
+ pixac2 = pixacompRead("/tmp/lept/comp/file2.pac");
+ pixa1 = pixaCreateFromPixacomp(pixac2, L_COPY);
+ pix1 = pixaDisplayTiledAndScaled(pixa1, 32, 250, 4, 0, 20, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 10 */
+ pixacompDestroy(&pixac1);
+ pixacompDestroy(&pixac2);
+ pixaDestroy(&pixa1);
+ pixDestroy(&pix1);
+
+ /* Test serialized pixacomp I/O to and from memory */
+ pixacompWriteMem(&data1, &size1, pixac);
+ pixac1 = pixacompReadMem(data1, size1);
+ pixacompWriteMem(&data2, &size2, pixac1);
+ pixac2 = pixacompReadMem(data2, size2);
+ pixacompWrite("/tmp/lept/comp/file3.pac", pixac1);
+ regTestCheckFile(rp, "/tmp/lept/comp/file3.pac"); /* 11 */
+ pixacompWrite("/tmp/lept/comp/file4.pac", pixac2);
+ regTestCheckFile(rp, "/tmp/lept/comp/file4.pac"); /* 12 */
+ regTestCompareFiles(rp, 11, 12); /* 13 */
+ pixacompDestroy(&pixac1);
+ pixacompDestroy(&pixac2);
+ lept_free(data1);
+ lept_free(data2);
+
+ /* Test pdf generation (both with and without transcoding */
+ pixacompDestroy(&pixac);
+ pix1 = pixRead("test24.jpg");
+ pix2 = pixRead("marge.jpg");
+ pixac = pixacompCreate(2);
+ pixacompAddPix(pixac, pix1, IFF_JFIF_JPEG);
+ pixacompAddPix(pixac, pix2, IFF_JFIF_JPEG);
+ l_pdfSetDateAndVersion(0);
+ pixacompConvertToPdfData(pixac, 0, 1.0, L_DEFAULT_ENCODE, 0, "test1",
+ &data1, &size1);
+ regTestWriteDataAndCheck(rp, data1, size1, "pdf"); /* 13 */
+ pixacompFastConvertToPdfData(pixac, "test2", &data2, &size2);
+ regTestWriteDataAndCheck(rp, data2, size2, "pdf"); /* 14 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixacompDestroy(&pixac);
+ lept_free(data1);
+ lept_free(data2);
+
+ return regTestCleanup(rp);
+}
+
+
+static void
+get_format_data(l_int32 i,
+ l_uint8 *data,
+ size_t size)
+{
+l_int32 ret, format, w, h, d, bps, spp, iscmap;
+
+ ret = pixReadHeaderMem(data, size, &format, &w, &h, &bps, &spp, &iscmap);
+ d = bps * spp;
+ if (d == 24) d = 32;
+ if (ret)
+ lept_stderr("Error: couldn't read data: size = %d\n", (l_int32)size);
+ else
+ lept_stderr("Format data for image %d:\n"
+ " format: %s, size (w, h, d) = (%d, %d, %d)\n"
+ " bps = %d, spp = %d, iscmap = %d\n",
+ i, ImageFileFormatExtensions[format], w, h, d,
+ bps, spp, iscmap);
+ return;
+}
+
diff --git a/leptonica/prog/pixmem_reg.c b/leptonica/prog/pixmem_reg.c
new file mode 100644
index 00000000..6dfd4c12
--- /dev/null
+++ b/leptonica/prog/pixmem_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * pixmem_reg.c
+ *
+ * Tests low-level pix data accessors, and functions that call them.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_uint32 *data;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixa = pixaCreate(0);
+
+ /* Copy with internal resizing: onto a cmapped image */
+ pix1 = pixRead("weasel4.16c.png");
+ pix2 = pixRead("feyn-fract.tif");
+ pix3 = pixRead("lucasta.150.jpg");
+ pixCopy(pix3, pix2);
+ regTestComparePix(rp, pix2, pix3); /* 0 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixCopy(pix1, pix3);
+ regTestComparePix(rp, pix1, pix2); /* 1 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixDestroy(&pix2);
+
+ /* Copy with internal resizing: from a cmapped image */
+ pix1 = pixRead("weasel4.16c.png");
+ pix2 = pixRead("feyn-fract.tif");
+ pix3 = pixRead("lucasta.150.jpg");
+ pixCopy(pix2, pix1);
+ regTestComparePix(rp, pix1, pix2); /* 2 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixCopy(pix3, pix2);
+ regTestComparePix(rp, pix2, pix3); /* 3 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+
+ /* Transfer of data pixs --> pixd, when pixs is not cloned.
+ * pixs is destroyed. */
+ pix1 = pixRead("weasel4.16c.png");
+ pix2 = pixRead("feyn-fract.tif");
+ pix3 = pixRead("lucasta.150.jpg");
+ pix4 = pixCopy(NULL, pix1);
+ pixTransferAllData(pix2, &pix1, 0, 0);
+ regTestComparePix(rp, pix2, pix4); /* 4 */
+ pixaAddPix(pixa, pix2, L_COPY);
+ pixTransferAllData(pix3, &pix2, 0, 0);
+ regTestComparePix(rp, pix3, pix4); /* 5 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix4);
+
+ /* Another transfer of data pixs --> pixd, when pixs is not cloned.
+ * pixs is destroyed. */
+ pix1 = pixRead("weasel4.16c.png");
+ pix2 = pixRead("feyn-fract.tif");
+ pix3 = pixRead("lucasta.150.jpg");
+ pix4 = pixCopy(NULL, pix1);
+ pixTransferAllData(pix2, &pix4, 0, 0);
+ regTestComparePix(rp, pix1, pix2); /* 6 */
+ pixaAddPix(pixa, pix2, L_COPY);
+ pixTransferAllData(pix3, &pix2, 0, 0);
+ regTestComparePix(rp, pix1, pix3); /* 7 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+
+ /* Transfer of data pixs --> pixd, when pixs is cloned.
+ * pixs has its refcount reduced by 1. */
+ pix1 = pixRead("weasel4.16c.png");
+ pix2 = pixRead("feyn-fract.tif");
+ pix3 = pixRead("lucasta.150.jpg");
+ pix4 = pixClone(pix1);
+ pix5 = pixClone(pix2);
+ pixTransferAllData(pix2, &pix4, 0, 0);
+ regTestComparePix(rp, pix1, pix2); /* 8 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixTransferAllData(pix3, &pix5, 0, 0);
+ regTestComparePix(rp, pix1, pix3); /* 9 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+
+ /* Extraction of data when pixs is not cloned, putting
+ * the data into a new template of pixs. */
+ pix2 = pixRead("feyn-fract.tif");
+ pix3 = pixCopy(NULL, pix2); /* for later reference */
+ data = pixExtractData(pix2);
+ pix4 = pixCreateTemplateNoInit(pix2);
+ pixFreeData(pix4);
+ pixSetData(pix4, data);
+ regTestComparePix(rp, pix3, pix4); /* 10 */
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Extraction of data when pixs is cloned, putting
+ * a copy of the data into a new template of pixs. */
+ pix1 = pixRead("weasel4.16c.png");
+ pix2 = pixClone(pix1); /* bump refcount of pix1 to 2 */
+ data = pixExtractData(pix1); /* should make a copy of data */
+ pix3 = pixCreateTemplateNoInit(pix1);
+ pixFreeData(pix3);
+ pixSetData(pix3, data);
+ regTestComparePix(rp, pix2, pix3); /* 11 */
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ pix1 = pixaDisplayTiledInRows(pixa, 32, 1500, 0.5, 0, 30, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 12 */
+ pixDisplayWithTitle(pix1, 100, 100, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/pixserial_reg.c b/leptonica/prog/pixserial_reg.c
new file mode 100644
index 00000000..4c34a547
--- /dev/null
+++ b/leptonica/prog/pixserial_reg.c
@@ -0,0 +1,142 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * pixserial_reg.c
+ *
+ * Tests the fast (uncompressed) serialization of pix to a string
+ * in memory and the deserialization back to a pix.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Use this set */
+static l_int32 nfiles = 10;
+static const char *filename[] = {
+ "feyn.tif", /* 1 bpp */
+ "dreyfus2.png", /* 2 bpp cmapped */
+ "dreyfus4.png", /* 4 bpp cmapped */
+ "weasel4.16c.png", /* 4 bpp cmapped */
+ "dreyfus8.png", /* 8 bpp cmapped */
+ "weasel8.240c.png", /* 8 bpp cmapped */
+ "karen8.jpg", /* 8 bpp, not cmapped */
+ "test16.tif", /* 8 bpp, not cmapped */
+ "marge.jpg", /* rgb */
+ "test24.jpg" /* rgb */
+ };
+
+int main(int argc,
+ char **argv)
+{
+char buf[256];
+size_t size;
+l_int32 i, w, h;
+l_int32 format, bps, spp, iscmap, format2, w2, h2, bps2, spp2, iscmap2;
+l_uint8 *data;
+l_uint32 *data32, *data32r;
+BOX *box;
+PIX *pixs, *pixt, *pixt2, *pixd;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Test basic serialization/deserialization */
+ data32 = NULL;
+ for (i = 0; i < nfiles; i++) {
+ pixs = pixRead(filename[i]);
+ /* Serialize to memory */
+ pixSerializeToMemory(pixs, &data32, &size);
+ /* Just for fun, write and read back from file */
+ l_binaryWrite("/tmp/lept/regout/array", "w", data32, size);
+ data32r = (l_uint32 *)l_binaryRead("/tmp/lept/regout/array", &size);
+ /* Deserialize */
+ pixd = pixDeserializeFromMemory(data32r, size);
+ regTestComparePix(rp, pixs, pixd); /* i */
+ pixDestroy(&pixd);
+ pixDestroy(&pixs);
+ lept_free(data32);
+ lept_free(data32r);
+ }
+
+ /* Test read/write fileio interface */
+ for (i = 0; i < nfiles; i++) {
+ pixs = pixRead(filename[i]);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ box = boxCreate(0, 0, L_MIN(150, w), L_MIN(150, h));
+ pixt = pixClipRectangle(pixs, box, NULL);
+ boxDestroy(&box);
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/pixs.%d.spix",
+ rp->index + 1);
+ pixWrite(buf, pixt, IFF_SPIX);
+ regTestCheckFile(rp, buf); /* nfiles + 2 * i */
+ pixt2 = pixRead(buf);
+ regTestComparePix(rp, pixt, pixt2); /* nfiles + 2 * i + 1 */
+ pixDestroy(&pixs);
+ pixDestroy(&pixt);
+ pixDestroy(&pixt2);
+ }
+
+ /* Test read header. Note that for rgb input, spp = 3,
+ * but for 32 bpp spix, we set spp = 4. */
+ data = NULL;
+ for (i = 0; i < nfiles; i++) {
+ pixs = pixRead(filename[i]);
+ pixWriteMem(&data, &size, pixs, IFF_SPIX);
+ pixReadHeader(filename[i], &format, &w, &h, &bps, &spp, &iscmap);
+ pixReadHeaderMem(data, size, &format2, &w2, &h2, &bps2,
+ &spp2, &iscmap2);
+ if (format2 != IFF_SPIX || w != w2 || h != h2 || bps != bps2 ||
+ iscmap != iscmap2) {
+ if (rp->fp)
+ fprintf(rp->fp, "Failure comparing data");
+ else
+ lept_stderr("Failure comparing data");
+ }
+ pixDestroy(&pixs);
+ lept_free(data);
+ }
+
+#if 0
+ /* Do timing */
+ for (i = 0; i < nfiles; i++) {
+ pixs = pixRead(filename[i]);
+ startTimer();
+ pixSerializeToMemory(pixs, &data32, &size);
+ pixd = pixDeserializeFromMemory(data32, size);
+ lept_stderr("Time for %s: %7.3f sec\n", filename[i], stopTimer());
+ lept_free(data32);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ }
+#endif
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/pixtile_reg.c b/leptonica/prog/pixtile_reg.c
new file mode 100644
index 00000000..cad6c64e
--- /dev/null
+++ b/leptonica/prog/pixtile_reg.c
@@ -0,0 +1,105 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * pixtile_reg.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static l_int32 TestTiling(PIX *pixd, PIX *pixs, l_int32 nx, l_int32 ny,
+ l_int32 w, l_int32 h, l_int32 xoverlap,
+ l_int32 yoverlap);
+
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pixd;
+
+ setLeptDebugOK(1);
+ pixs = pixRead("test24.jpg");
+ pixd = pixCreateTemplateNoInit(pixs);
+
+ TestTiling(pixd, pixs, 1, 1, 0, 0, 183, 83);
+ TestTiling(pixd, pixs, 0, 1, 60, 0, 30, 20);
+ TestTiling(pixd, pixs, 1, 0, 0, 60, 40, 40);
+ TestTiling(pixd, pixs, 0, 0, 27, 31, 27, 31);
+ TestTiling(pixd, pixs, 0, 0, 400, 400, 40, 20);
+ TestTiling(pixd, pixs, 7, 9, 0, 0, 35, 35);
+ TestTiling(pixd, pixs, 0, 0, 27, 31, 0, 0);
+ TestTiling(pixd, pixs, 7, 9, 0, 0, 0, 0);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
+
+
+l_int32
+TestTiling(PIX *pixd,
+ PIX *pixs,
+ l_int32 nx,
+ l_int32 ny,
+ l_int32 w,
+ l_int32 h,
+ l_int32 xoverlap,
+ l_int32 yoverlap)
+{
+l_int32 i, j, same;
+PIX *pixt;
+PIXTILING *pt;
+
+ pixClearAll(pixd);
+ pt = pixTilingCreate(pixs, nx, ny, w, h, xoverlap, yoverlap);
+ pixTilingGetCount(pt, &nx, &ny);
+ pixTilingGetSize(pt, &w, &h);
+ if (pt)
+ lept_stderr("nx,ny = %d,%d; w,h = %d,%d; overlap = %d,%d\n",
+ nx, ny, w, h, pt->xoverlap, pt->yoverlap);
+ else
+ return 1;
+
+ for (i = 0; i < ny; i++) {
+ for (j = 0; j < nx; j++) {
+ pixt = pixTilingGetTile(pt, i, j);
+ pixTilingPaintTile(pixd, i, j, pixt, pt);
+ pixDestroy(&pixt);
+ }
+ }
+ pixEqual(pixs, pixd, &same);
+ if (same)
+ lept_stderr("Tiling OK\n");
+ else
+ lept_stderr("Tiling ERROR !\n");
+
+ pixTilingDestroy(&pt);
+ return 0;
+}
diff --git a/leptonica/prog/plottest.c b/leptonica/prog/plottest.c
new file mode 100644
index 00000000..3b0cfc73
--- /dev/null
+++ b/leptonica/prog/plottest.c
@@ -0,0 +1,156 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+
+/*
+ * plottest.c
+ *
+ * This tests the gplot library functions that generate
+ * the plot commands and data required for input to gnuplot.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+
+#include "allheaders.h"
+
+ /* for GPLOT_STYLE, use one of the following set:
+ * GPLOT_LINES
+ * GPLOT_POINTS
+ * GPLOT_IMPULSE
+ * GPLOT_LINESPOINTS
+ * GPLOT_DOTS */
+#define GPLOT_STYLE GPLOT_LINES
+
+ /* for GPLOT_OUTPUT use one of the following set:
+ * GPLOT_PNG
+ * GPLOT_PS
+ * GPLOT_EPS
+ * GPLOT_LATEX
+ */
+
+#define GPLOT_OUTPUT GPLOT_PNG
+
+int main(int argc,
+ char **argv)
+{
+char *str1, *str2, *pngname;
+l_int32 i;
+size_t size1, size2;
+l_float32 x, y1, y2, pi;
+GPLOT *gplot1, *gplot2, *gplot3, *gplot4, *gplot5;
+NUMA *nax, *nay1, *nay2;
+static char mainName[] = "plottest";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: plottest", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/plot");
+
+ /* Generate plot data */
+ nax = numaCreate(0);
+ nay1 = numaCreate(0);
+ nay2 = numaCreate(0);
+ pi = 3.1415926535;
+ for (i = 0; i < 180; i++) {
+ x = (pi / 180.) * i;
+ y1 = (l_float32)sin(2.4 * x);
+ y2 = (l_float32)cos(2.4 * x);
+ numaAddNumber(nax, x);
+ numaAddNumber(nay1, y1);
+ numaAddNumber(nay2, y2);
+ }
+
+ /* Show the plot */
+ gplot1 = gplotCreate("/tmp/lept/plot/set1", GPLOT_OUTPUT, "Example plots",
+ "theta", "f(theta)");
+ gplotAddPlot(gplot1, nax, nay1, GPLOT_STYLE, "sin (2.4 * theta)");
+ gplotAddPlot(gplot1, nax, nay2, GPLOT_STYLE, "cos (2.4 * theta)");
+ gplotMakeOutput(gplot1);
+
+ /* Also save the plot to png */
+ gplot1->outformat = GPLOT_PNG;
+ pngname = genPathname("/tmp/lept/plot", "set1.png");
+ stringReplace(&gplot1->outname, pngname);
+ gplotMakeOutput(gplot1);
+ l_fileDisplay("/tmp/lept/plot/set1.png", 100, 100, 1.0);
+ lept_free(pngname);
+
+ /* Test gplot serialization */
+ gplotWrite("/tmp/lept/plot/plot1.gp", gplot1);
+ if ((gplot2 = gplotRead("/tmp/lept/plot/plot1.gp")) == NULL)
+ return ERROR_INT("gplotRead failure!", mainName, 1);
+ gplotWrite("/tmp/lept/plot/plot2.gp", gplot2);
+
+ /* Are the two written gplot files the same? */
+ str1 = (char *)l_binaryRead("/tmp/lept/plot/plot1.gp", &size1);
+ str2 = (char *)l_binaryRead("/tmp/lept/plot/plot2.gp", &size2);
+ if (size1 != size2)
+ lept_stderr("Error: size1 = %lu, size2 = %lu\n",
+ (unsigned long)size1, (unsigned long)size2);
+ else
+ lept_stderr("Correct: size1 = size2 = %lu\n", (unsigned long)size1);
+ if (strcmp(str1, str2) != 0)
+ lept_stderr("Error: str1 != str2\n");
+ else
+ lept_stderr("Correct: str1 == str2\n");
+ lept_free(str1);
+ lept_free(str2);
+
+ /* Read from file and regenerate the plot */
+ gplot3 = gplotRead("/tmp/lept/plot/plot2.gp");
+ stringReplace(&gplot3->title , "Example plots regen");
+ gplot3->outformat = GPLOT_PNG;
+ gplotMakeOutput(gplot3);
+
+ /* Build gplot but do not make the output formatted stuff */
+ gplot4 = gplotCreate("/tmp/lept/plot/set2", GPLOT_OUTPUT,
+ "Example plots 2", "theta", "f(theta)");
+ gplotAddPlot(gplot4, nax, nay1, GPLOT_STYLE, "sin (2.4 * theta)");
+ gplotAddPlot(gplot4, nax, nay2, GPLOT_STYLE, "cos (2.4 * theta)");
+
+ /* Write, read back, and generate the plot */
+ gplotWrite("/tmp/lept/plot/plot4.gp", gplot4);
+ if ((gplot5 = gplotRead("/tmp/lept/plot/plot4.gp")) == NULL)
+ return ERROR_INT("gplotRead failure!", mainName, 1);
+ gplotMakeOutput(gplot5);
+ l_fileDisplay("/tmp/lept/plot/set2.png", 750, 100, 1.0);
+
+ gplotDestroy(&gplot1);
+ gplotDestroy(&gplot2);
+ gplotDestroy(&gplot3);
+ gplotDestroy(&gplot4);
+ gplotDestroy(&gplot5);
+ numaDestroy(&nax);
+ numaDestroy(&nay1);
+ numaDestroy(&nay2);
+ return 0;
+}
diff --git a/leptonica/prog/pngio_reg.c b/leptonica/prog/pngio_reg.c
new file mode 100644
index 00000000..5bf3409b
--- /dev/null
+++ b/leptonica/prog/pngio_reg.c
@@ -0,0 +1,477 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This is the Leptonica regression test for lossless read/write
+ * I/O in png format.
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ *
+ * This tests reading and writing of images in png format for
+ * various depths, with and without colormaps.
+ *
+ * This test is dependent on the following external libraries:
+ * libpng, libz
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Needed for checking libraries */
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#define FILE_1BPP "rabi.png"
+#define FILE_2BPP "speckle2.png"
+#define FILE_2BPP_C "weasel2.4g.png"
+#define FILE_4BPP "speckle4.png"
+#define FILE_4BPP_C "weasel4.16c.png"
+#define FILE_8BPP "dreyfus8.png"
+#define FILE_8BPP_C "weasel8.240c.png"
+#define FILE_16BPP "test16.png"
+#define FILE_32BPP "weasel32.png"
+#define FILE_32BPP_ALPHA "test32-alpha.png"
+#define FILE_CMAP_ALPHA "test-cmap-alpha.png"
+#define FILE_CMAP_ALPHA2 "test-cmap-alpha2.png"
+#define FILE_TRANS_ALPHA "test-fulltrans-alpha.png"
+#define FILE_GRAY_ALPHA "test-gray-alpha.png"
+
+static l_int32 test_mem_png(const char *fname);
+static l_int32 get_header_data(const char *filename);
+static l_int32 test_1bpp_trans(L_REGPARAMS *rp);
+static l_int32 test_1bpp_color(L_REGPARAMS *rp);
+static l_int32 test_1bpp_gray(L_REGPARAMS *rp);
+static l_int32 test_1bpp_bw1(L_REGPARAMS *rp);
+static l_int32 test_1bpp_bw2(L_REGPARAMS *rp);
+static l_int32 test_8bpp_trans(L_REGPARAMS *rp);
+
+LEPT_DLL extern const char *ImageFileFormatExtensions[];
+
+int main(int argc,
+ char **argv)
+{
+l_int32 success, failure;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "pngio_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* --------- Part 1: Test lossless r/w to file ---------*/
+
+ failure = FALSE;
+ success = TRUE;
+ lept_stderr("Test bmp 1 bpp file:\n");
+ if (ioFormatTest(FILE_1BPP)) success = FALSE;
+ lept_stderr("\nTest 2 bpp file:\n");
+ if (ioFormatTest(FILE_2BPP)) success = FALSE;
+ lept_stderr("\nTest 2 bpp file with cmap:\n");
+ if (ioFormatTest(FILE_2BPP_C)) success = FALSE;
+ lept_stderr("\nTest 4 bpp file:\n");
+ if (ioFormatTest(FILE_4BPP)) success = FALSE;
+ lept_stderr("\nTest 4 bpp file with cmap:\n");
+ if (ioFormatTest(FILE_4BPP_C)) success = FALSE;
+ lept_stderr("\nTest 8 bpp grayscale file with cmap:\n");
+ if (ioFormatTest(FILE_8BPP)) success = FALSE;
+ lept_stderr("\nTest 8 bpp color file with cmap:\n");
+ if (ioFormatTest(FILE_8BPP_C)) success = FALSE;
+ lept_stderr("\nTest 16 bpp file:\n");
+ if (ioFormatTest(FILE_16BPP)) success = FALSE;
+ lept_stderr("\nTest 32 bpp RGB file:\n");
+ if (ioFormatTest(FILE_32BPP)) success = FALSE;
+ lept_stderr("\nTest 32 bpp RGBA file:\n");
+ if (ioFormatTest(FILE_32BPP_ALPHA)) success = FALSE;
+ lept_stderr("\nTest spp = 1, cmap with alpha file:\n");
+ if (ioFormatTest(FILE_CMAP_ALPHA)) success = FALSE;
+ lept_stderr("\nTest spp = 1, cmap with alpha (small alpha array):\n");
+ if (ioFormatTest(FILE_CMAP_ALPHA2)) success = FALSE;
+ lept_stderr("\nTest spp = 1, fully transparent with alpha file:\n");
+ if (ioFormatTest(FILE_TRANS_ALPHA)) success = FALSE;
+ lept_stderr("\nTest spp = 2, gray with alpha file:\n");
+ if (ioFormatTest(FILE_GRAY_ALPHA)) success = FALSE;
+ if (success) {
+ lept_stderr(
+ "\n ********** Success on lossless r/w to file *********\n\n");
+ } else {
+ lept_stderr(
+ "\n ******* Failure on at least one r/w to file ******\n\n");
+ }
+ if (!success) failure = TRUE;
+
+ /* ------------ Part 2: Test lossless r/w to memory ------------ */
+ success = TRUE;
+ if (test_mem_png(FILE_1BPP)) success = FALSE;
+ if (test_mem_png(FILE_2BPP)) success = FALSE;
+ if (test_mem_png(FILE_2BPP_C)) success = FALSE;
+ if (test_mem_png(FILE_4BPP)) success = FALSE;
+ if (test_mem_png(FILE_4BPP_C)) success = FALSE;
+ if (test_mem_png(FILE_8BPP)) success = FALSE;
+ if (test_mem_png(FILE_8BPP_C)) success = FALSE;
+ if (test_mem_png(FILE_16BPP)) success = FALSE;
+ if (test_mem_png(FILE_32BPP)) success = FALSE;
+ if (test_mem_png(FILE_32BPP_ALPHA)) success = FALSE;
+ if (test_mem_png(FILE_CMAP_ALPHA)) success = FALSE;
+ if (test_mem_png(FILE_CMAP_ALPHA2)) success = FALSE;
+ if (test_mem_png(FILE_TRANS_ALPHA)) success = FALSE;
+ if (test_mem_png(FILE_GRAY_ALPHA)) success = FALSE;
+ if (success) {
+ lept_stderr(
+ "\n ****** Success on lossless r/w to memory *****\n");
+ } else {
+ lept_stderr(
+ "\n ******* Failure on at least one r/w to memory ******\n");
+ }
+ if (!success) failure = TRUE;
+
+ /* ------------ Part 3: Test lossless 1 and 8 bpp r/w ------------ */
+ lept_stderr("\nTest lossless 1 and 8 bpp r/w\n");
+ success = TRUE;
+ if (test_1bpp_trans(rp) == 0) success = FALSE;
+ if (test_1bpp_color(rp) == 0) success = FALSE;
+ if (test_1bpp_gray(rp) == 0) success = FALSE;
+ if (test_1bpp_bw1(rp) == 0) success = FALSE;
+ if (test_1bpp_bw2(rp) == 0) success = FALSE;
+ if (test_8bpp_trans(rp) == 0) success = FALSE;
+
+ if (success) {
+ lept_stderr(
+ "\n ******* Success on 1 and 8 bpp lossless *******\n\n");
+ } else {
+ lept_stderr(
+ "\n ******* Failure on 1 and 8 bpp lossless *******\n\n");
+ }
+ if (!success) failure = TRUE;
+
+ /* -------------- Part 4: Read header information -------------- */
+ success = TRUE;
+ if (get_header_data(FILE_1BPP)) success = FALSE;
+ if (get_header_data(FILE_2BPP)) success = FALSE;
+ if (get_header_data(FILE_2BPP_C)) success = FALSE;
+ if (get_header_data(FILE_4BPP)) success = FALSE;
+ if (get_header_data(FILE_4BPP_C)) success = FALSE;
+ if (get_header_data(FILE_8BPP)) success = FALSE;
+ if (get_header_data(FILE_8BPP_C)) success = FALSE;
+ if (get_header_data(FILE_16BPP)) success = FALSE;
+ if (get_header_data(FILE_32BPP)) success = FALSE;
+ if (get_header_data(FILE_32BPP_ALPHA)) success = FALSE;
+ if (get_header_data(FILE_CMAP_ALPHA)) success = FALSE;
+ if (get_header_data(FILE_CMAP_ALPHA2)) success = FALSE;
+ if (get_header_data(FILE_TRANS_ALPHA)) success = FALSE;
+ if (get_header_data(FILE_GRAY_ALPHA)) success = FALSE;
+
+ if (success) {
+ lept_stderr("\n ******* Success on reading headers *******\n\n");
+ } else {
+ lept_stderr("\n ******* Failure on reading headers *******\n\n");
+ }
+ if (!success) failure = TRUE;
+
+ if (!failure) {
+ lept_stderr(" ******* Success on all tests *******\n\n");
+ } else {
+ lept_stderr(" ******* Failure on at least one test *******\n\n");
+ }
+
+ if (failure) rp->success = FALSE;
+ return regTestCleanup(rp);
+}
+
+
+ /* Returns 1 on error */
+static l_int32
+test_mem_png(const char *fname)
+{
+l_uint8 *data = NULL;
+l_int32 same;
+size_t size = 0;
+PIX *pixs;
+PIX *pixd = NULL;
+
+ if ((pixs = pixRead(fname)) == NULL) {
+ lept_stderr("Failure to read %s\n", fname);
+ return 1;
+ }
+ if (pixWriteMem(&data, &size, pixs, IFF_PNG)) {
+ lept_stderr("Mem write fail for png\n");
+ return 1;
+ }
+ if ((pixd = pixReadMem(data, size)) == NULL) {
+ lept_stderr("Mem read fail for png\n");
+ lept_free(data);
+ return 1;
+ }
+
+ pixEqual(pixs, pixd, &same);
+ if (!same)
+ lept_stderr("Mem write/read fail for file %s\n", fname);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ lept_free(data);
+ return (!same);
+}
+
+ /* Retrieve header data from file and from array in memory */
+static l_int32
+get_header_data(const char *filename)
+{
+l_uint8 *data;
+l_int32 ret1, ret2, format1, format2;
+l_int32 w1, w2, h1, h2, d1, d2, bps1, bps2, spp1, spp2, iscmap1, iscmap2;
+size_t nbytes1, nbytes2;
+
+ /* Read header from file */
+ nbytes1 = nbytesInFile(filename);
+ ret1 = pixReadHeader(filename, &format1, &w1, &h1, &bps1, &spp1, &iscmap1);
+ d1 = bps1 * spp1;
+ if (d1 == 24) d1 = 32;
+ if (ret1) {
+ lept_stderr("Error: couldn't read header data from file: %s\n",
+ filename);
+ } else {
+ lept_stderr("Format data for image %s with format %s:\n"
+ " nbytes = %lu, size (w, h, d) = (%d, %d, %d)\n"
+ " bps = %d, spp = %d, iscmap = %d\n",
+ filename, ImageFileFormatExtensions[format1],
+ (unsigned long)nbytes1, w1, h1, d1, bps1, spp1, iscmap1);
+ if (format1 != IFF_PNG) {
+ lept_stderr("Error: format is %d; should be %d\n",
+ format1, IFF_PNG);
+ ret1 = 1;
+ }
+ }
+
+ /* Read header from array in memory */
+ data = l_binaryRead(filename, &nbytes2);
+ ret2 = pixReadHeaderMem(data, nbytes2, &format2, &w2, &h2, &bps2,
+ &spp2, &iscmap2);
+ lept_free(data);
+ d2 = bps2 * spp2;
+ if (d2 == 24) d2 = 32;
+ if (ret2) {
+ lept_stderr("Error: couldn't mem-read header data: %s\n", filename);
+ } else {
+ if (nbytes1 != nbytes2 || format1 != format2 || w1 != w2 ||
+ h1 != h2 || d1 != d2 || bps1 != bps2 || spp1 != spp2 ||
+ iscmap1 != iscmap2) {
+ lept_stderr("Incomsistency reading image %s with format %s\n",
+ filename, ImageFileFormatExtensions[IFF_PNG]);
+ ret2 = 1;
+ }
+ }
+
+ return ret1 || ret2;
+}
+
+static l_int32
+test_1bpp_trans(L_REGPARAMS *rp)
+{
+l_int32 same, transp;
+FILE *fp;
+PIX *pix1, *pix2;
+PIXCMAP *cmap;
+
+ pix1 = pixRead("feyn-fract2.tif");
+ cmap = pixcmapCreate(1);
+ pixSetColormap(pix1, cmap);
+ pixcmapAddRGBA(cmap, 180, 130, 220, 0); /* transparent */
+ pixcmapAddRGBA(cmap, 20, 120, 0, 255); /* opaque */
+ pixWrite("/tmp/lept/regout/1bpp-trans.png", pix1, IFF_PNG);
+ pix2 = pixRead("/tmp/lept/regout/1bpp-trans.png");
+ pixEqual(pix1, pix2, &same);
+ if (same)
+ lept_stderr("1bpp_trans: success\n");
+ else
+ lept_stderr("1bpp_trans: bad output\n");
+ pixDisplayWithTitle(pix2, 700, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ fp = fopenReadStream("/tmp/lept/regout/1bpp-trans.png");
+ fgetPngColormapInfo(fp, &cmap, &transp);
+ if (fp) fclose(fp);
+ if (transp)
+ lept_stderr("1bpp_trans: correct -- transparency found\n");
+ else
+ lept_stderr("1bpp_trans: error -- no transparency found!\n");
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ pixcmapDestroy(&cmap);
+ return same;
+}
+
+static l_int32
+test_1bpp_color(L_REGPARAMS *rp)
+{
+l_int32 same, transp;
+FILE *fp;
+PIX *pix1, *pix2;
+PIXCMAP *cmap;
+
+ pix1 = pixRead("feyn-fract2.tif");
+ cmap = pixcmapCreate(1);
+ pixSetColormap(pix1, cmap);
+ pixcmapAddRGBA(cmap, 180, 130, 220, 255); /* color, opaque */
+ pixcmapAddRGBA(cmap, 20, 120, 0, 255); /* color, opaque */
+ pixWrite("/tmp/lept/regout/1bpp-color.png", pix1, IFF_PNG);
+ pix2 = pixRead("/tmp/lept/regout/1bpp-color.png");
+ pixEqual(pix1, pix2, &same);
+ if (same)
+ lept_stderr("1bpp_color: success\n");
+ else
+ lept_stderr("1bpp_color: bad output\n");
+ pixDisplayWithTitle(pix2, 700, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ fp = fopenReadStream("/tmp/lept/regout/1bpp-color.png");
+ fgetPngColormapInfo(fp, &cmap, &transp);
+ if (fp) fclose(fp);
+ if (transp)
+ lept_stderr("1bpp_color: error -- transparency found!\n");
+ else
+ lept_stderr("1bpp_color: correct -- no transparency found\n");
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ pixcmapDestroy(&cmap);
+ return same;
+}
+
+static l_int32
+test_1bpp_gray(L_REGPARAMS *rp)
+{
+l_int32 same;
+PIX *pix1, *pix2;
+PIXCMAP *cmap;
+
+ pix1 = pixRead("feyn-fract2.tif");
+ cmap = pixcmapCreate(1);
+ pixSetColormap(pix1, cmap);
+ pixcmapAddRGBA(cmap, 180, 180, 180, 255); /* light, opaque */
+ pixcmapAddRGBA(cmap, 60, 60, 60, 255); /* dark, opaque */
+ pixWrite("/tmp/lept/regout/1bpp-gray.png", pix1, IFF_PNG);
+ pix2 = pixRead("/tmp/lept/regout/1bpp-gray.png");
+ pixEqual(pix1, pix2, &same);
+ if (same)
+ lept_stderr("1bpp_gray: success\n");
+ else
+ lept_stderr("1bpp_gray: bad output\n");
+ pixDisplayWithTitle(pix2, 700, 200, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return same;
+}
+
+static l_int32
+test_1bpp_bw1(L_REGPARAMS *rp)
+{
+l_int32 same;
+PIX *pix1, *pix2;
+PIXCMAP *cmap;
+
+ pix1 = pixRead("feyn-fract2.tif");
+ cmap = pixcmapCreate(1);
+ pixSetColormap(pix1, cmap);
+ pixcmapAddRGBA(cmap, 0, 0, 0, 255); /* black, opaque */
+ pixcmapAddRGBA(cmap, 255, 255, 255, 255); /* white, opaque */
+ pixWrite("/tmp/lept/regout/1bpp-bw1.png", pix1, IFF_PNG);
+ pix2 = pixRead("/tmp/lept/regout/1bpp-bw1.png");
+ pixEqual(pix1, pix2, &same);
+ if (same)
+ lept_stderr("1bpp_bw1: success\n");
+ else
+ lept_stderr("1bpp_bw1: bad output\n");
+ pixDisplayWithTitle(pix2, 700, 300, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return same;
+}
+
+static l_int32
+test_1bpp_bw2(L_REGPARAMS *rp)
+{
+l_int32 same;
+PIX *pix1, *pix2;
+PIXCMAP *cmap;
+
+ pix1 = pixRead("feyn-fract2.tif");
+ cmap = pixcmapCreate(1);
+ pixSetColormap(pix1, cmap);
+ pixcmapAddRGBA(cmap, 255, 255, 255, 255); /* white, opaque */
+ pixcmapAddRGBA(cmap, 0, 0, 0, 255); /* black, opaque */
+ pixWrite("/tmp/lept/regout/1bpp-bw2.png", pix1, IFF_PNG);
+ pix2 = pixRead("/tmp/lept/regout/1bpp-bw2.png");
+ pixEqual(pix1, pix2, &same);
+ if (same)
+ lept_stderr("1bpp_bw2: success\n");
+ else
+ lept_stderr("1bpp_bw2: bad output\n");
+ pixDisplayWithTitle(pix2, 700, 400, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return same;
+}
+
+static l_int32
+test_8bpp_trans(L_REGPARAMS *rp)
+{
+l_int32 same, transp;
+FILE *fp;
+PIX *pix1, *pix2, *pix3;
+PIXCMAP *cmap;
+
+ pix1 = pixRead("wyom.jpg");
+ pix2 = pixColorSegment(pix1, 75, 10, 8, 7, 0);
+ cmap = pixGetColormap(pix2);
+ pixcmapSetAlpha(cmap, 0, 0); /* set blueish sky color to transparent */
+ pixWrite("/tmp/lept/regout/8bpp-trans.png", pix2, IFF_PNG);
+ pix3 = pixRead("/tmp/lept/regout/8bpp-trans.png");
+ pixEqual(pix2, pix3, &same);
+ if (same)
+ lept_stderr("8bpp_trans: success\n");
+ else
+ lept_stderr("8bpp_trans: bad output\n");
+ pixDisplayWithTitle(pix3, 700, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ fp = fopenReadStream("/tmp/lept/regout/8bpp-trans.png");
+ fgetPngColormapInfo(fp, &cmap, &transp);
+ if (fp) fclose(fp);
+ if (transp)
+ lept_stderr("8bpp_trans: correct -- transparency found\n");
+ else
+ lept_stderr("8bpp_trans: error -- no transparency found!\n");
+ if (rp->display) pixcmapWriteStream(stderr, cmap);
+ pixcmapDestroy(&cmap);
+ return same;
+}
+
diff --git a/leptonica/prog/pnmio_reg.c b/leptonica/prog/pnmio_reg.c
new file mode 100644
index 00000000..ae30fe3e
--- /dev/null
+++ b/leptonica/prog/pnmio_reg.c
@@ -0,0 +1,183 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Tests read and write of both ascii and packed pnm, using
+ * pix with 1, 2, 4, 8 and 32 bpp.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+l_int32 main(l_int32 argc,
+ char **argv)
+{
+l_uint8 *data;
+size_t size;
+FILE *fp;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+#if !USE_PNMIO
+ lept_stderr("pnm/pam writing is not enabled\n"
+ "See environ.h: #define USE_PNMIO 1\n\n");
+
+ regTestCleanup(rp);
+ return 0;
+#endif /* abort */
+
+ lept_rmdir("lept/pnm");
+ lept_mkdir("lept/pnm");
+
+ /* Test 1 bpp (pbm) read/write */
+ pix1 = pixRead("char.tif");
+ fp = lept_fopen("/tmp/lept/pnm/pix1.1.pnm", "wb");
+ pixWriteStreamAsciiPnm(fp, pix1);
+ lept_fclose(fp);
+ pix2 = pixRead("/tmp/lept/pnm/pix1.1.pnm");
+ pixWrite("/tmp/lept/pnm/pix2.1.pnm", pix2, IFF_PNM);
+ pix3 = pixRead("/tmp/lept/pnm/pix2.1.pnm");
+ regTestComparePix(rp, pix1, pix3); /* 0 */
+ /* write PAM */
+ fp = lept_fopen("/tmp/lept/pnm/pix3.1.pnm", "wb");
+ pixWriteStreamPam(fp, pix1);
+ lept_fclose(fp);
+ pix4 = pixRead("/tmp/lept/pnm/pix3.1.pnm");
+ regTestComparePix(rp, pix1, pix4); /* 1 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Test 2, 4 and 8 bpp (pgm) read/write */
+ pix1 = pixRead("weasel8.png");
+ pix2 = pixThresholdTo2bpp(pix1, 4, 0);
+ fp = lept_fopen("/tmp/lept/pnm/pix2.2.pnm", "wb");
+ pixWriteStreamAsciiPnm(fp, pix2);
+ lept_fclose(fp);
+ pix3 = pixRead("/tmp/lept/pnm/pix2.2.pnm");
+ pixWrite("/tmp/lept/pnm/pix3.2.pnm", pix3, IFF_PNM);
+ pix4 = pixRead("/tmp/lept/pnm/pix3.2.pnm");
+ regTestComparePix(rp, pix2, pix4); /* 2 */
+ /* write PAM */
+ fp = lept_fopen("/tmp/lept/pnm/pix4.2.pnm", "wb");
+ pixWriteStreamPam(fp, pix2);
+ lept_fclose(fp);
+ pix5 = pixRead("/tmp/lept/pnm/pix4.2.pnm");
+ regTestComparePix(rp, pix2, pix5); /* 3 */
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+
+ pix2 = pixThresholdTo4bpp(pix1, 16, 0);
+ fp = lept_fopen("/tmp/lept/pnm/pix2.4.pnm", "wb");
+ pixWriteStreamAsciiPnm(fp, pix2);
+ lept_fclose(fp);
+ pix3 = pixRead("/tmp/lept/pnm/pix2.4.pnm");
+ pixWrite("/tmp/lept/pnm/pix3.4.pnm", pix3, IFF_PNM);
+ pix4 = pixRead("/tmp/lept/pnm/pix3.4.pnm");
+ regTestComparePix(rp, pix2, pix4); /* 4 */
+ /* write PAM */
+ fp = lept_fopen("/tmp/lept/pnm/pix4.4.pnm", "wb");
+ pixWriteStreamPam(fp, pix2);
+ lept_fclose(fp);
+ pix5 = pixRead("/tmp/lept/pnm/pix4.4.pnm");
+ regTestComparePix(rp, pix2, pix5); /* 5 */
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+
+ fp = lept_fopen("/tmp/lept/pnm/pix1.8.pnm", "wb");
+ pixWriteStreamAsciiPnm(fp, pix1);
+ lept_fclose(fp);
+ pix2 = pixRead("/tmp/lept/pnm/pix1.8.pnm");
+ pixWrite("/tmp/lept/pnm/pix2.8.pnm", pix2, IFF_PNM);
+ pix3 = pixRead("/tmp/lept/pnm/pix2.8.pnm");
+ regTestComparePix(rp, pix1, pix3); /* 6 */
+ /* write PAM */
+ fp = lept_fopen("/tmp/lept/pnm/pix3.8.pnm", "wb");
+ pixWriteStreamPam(fp, pix1);
+ lept_fclose(fp);
+ pix4 = pixRead("/tmp/lept/pnm/pix3.8.pnm");
+ regTestComparePix(rp, pix1, pix4); /* 7 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Test ppm (24 bpp rgb) read/write */
+ pix1 = pixRead("marge.jpg");
+ fp = lept_fopen("/tmp/lept/pnm/pix1.24.pnm", "wb");
+ /* write ascii */
+ pixWriteStreamAsciiPnm(fp, pix1);
+ lept_fclose(fp);
+ pix2 = pixRead("/tmp/lept/pnm/pix1.24.pnm");
+ /* write pnm */
+ pixWrite("/tmp/lept/pnm/pix2.24.pnm", pix2, IFF_PNM);
+ pix3 = pixRead("/tmp/lept/pnm/pix2.24.pnm");
+ regTestComparePix(rp, pix1, pix3); /* 8 */
+ pixDestroy(&pix3);
+ /* write mem pnm */
+ pixWriteMemPnm(&data, &size, pix1);
+ pix3 = pixReadMemPnm(data, size);
+ regTestComparePix(rp, pix1, pix3); /* 9 */
+ lept_free(data);
+ /* write pam */
+ fp = lept_fopen("/tmp/lept/pnm/pix3.24.pnm", "wb");
+ pixWriteStreamPam(fp, pix1);
+ lept_fclose(fp);
+ pix4 = pixRead("/tmp/lept/pnm/pix3.24.pnm");
+ regTestComparePix(rp, pix1, pix4); /* 10 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Test pam (32 bpp rgba) read/write */
+ pix1 = pixRead("test32-alpha.png");
+ fp = lept_fopen("/tmp/lept/pnm/pix1.32.pnm", "wb");
+ pixWriteStreamPam(fp, pix1);
+ lept_fclose(fp);
+ pix2 = pixRead("/tmp/lept/pnm/pix1.32.pnm");
+ pixWrite("/tmp/lept/pnm/pix2.32.pnm", pix2, IFF_PNM);
+ pix3 = pixRead("/tmp/lept/pnm/pix2.32.pnm");
+ regTestComparePix(rp, pix1, pix3); /* 11 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/printimage.c b/leptonica/prog/printimage.c
new file mode 100644
index 00000000..9d195b2a
--- /dev/null
+++ b/leptonica/prog/printimage.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * printimage.c
+ *
+ * This prints an image. It rotates and isotropically scales the image,
+ * as necessary, to get a maximum filling when printing onto an
+ * 8.5 x 11 inch page.
+ *
+ * Syntax: printimage <filein> [printer, other lpr args]
+ *
+ * The simplest input would be something like
+ * printimage myfile.jpg
+ * This generates the PostScript file /tmp/print_image.ps, but
+ * does not send it to a printer.
+ *
+ * If you have lpr, you can specify a printer; e.g.
+ * printimage myfile.jpg myprinter
+ *
+ * You can add lpr flags. Two useful ones are:
+ * * to print more than one copy
+ * -#N (prints N copies)
+ * * to print in color (flag is printer-dependent)
+ * -o ColorModel=Color or
+ * -o ColorModel=CMYK
+ *
+ * For example, to make 3 color copies, you might use:
+ * printimage myfile.jpg myprinter -#3 -o ColorModel=Color
+ *
+ * By default, the intermediate PostScript file generated is
+ * level 3 (compressed):
+ * /tmp/print_image.ps
+ *
+ * If your system does not have lpr, it likely has lp. You can run
+ * printimage to make the PostScript file, and then print with lp:
+ * lp -d <printer> /tmp/print_image.ps
+ * lp -d <printer> -o ColorModel=Color /tmp/print_image.ps
+ * etc.
+ *
+ * ***************************************************************
+ * N.B. If a printer is specified, this program invokes lpr via
+ * "system'. It could pose a security vulnerability if used
+ * as a service in a production environment. Consequently,
+ * this program should only be used for debug and testing.
+ * ***************************************************************
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define USE_COMPRESSED 1
+
+static const l_float32 FILL_FACTOR = 0.95; /* fill factor on 8.5 x 11 page */
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *printer, *extra, *fname;
+char buffer[512];
+l_int32 i, w, h, ret, index;
+l_float32 scale;
+FILE *fp;
+PIX *pixs, *pix1;
+SARRAY *sa;
+static char mainName[] = "printimage";
+
+ if (argc < 2)
+ return ERROR_INT(
+ " Syntax: printimage <filein> [printer, other lpr args]",
+ mainName, 1);
+ filein = argv[1];
+ printer = (argc > 2) ? argv[2] : NULL;
+
+ lept_stderr(
+ "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+ " Warning: this program should only be used for testing,\n"
+ " and not in a production environment, because of a\n"
+ " potential vulnerability with the 'system' call.\n"
+ "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n");
+
+ setLeptDebugOK(1);
+ (void)lept_rm(NULL, "print_image.ps");
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ if (w > h) {
+ pix1 = pixRotate90(pixs, 1);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ }
+ else {
+ pix1 = pixClone(pixs);
+ }
+ scale = L_MIN(FILL_FACTOR * 2550 / w, FILL_FACTOR * 3300 / h);
+ fname = genPathname("/tmp", "print_image.ps");
+#if USE_COMPRESSED
+ index = 0;
+ pixWriteCompressedToPS(pix1, fname, (l_int32)(300. / scale), 3, &index);
+#else /* uncompressed, level 1 */
+ fp = lept_fopen(fname, "wb+");
+ pixWriteStreamPS(fp, pix1, NULL, 300, scale);
+ lept_fclose(fp);
+#endif /* USE_COMPRESSED */
+
+ /* Optionally print it out */
+ if (argc > 2) {
+ extra = NULL;
+ if (argc > 3) { /* concatenate the extra args */
+ sa = sarrayCreate(0);
+ for (i = 3; i < argc; i++)
+ sarrayAddString(sa, argv[i], L_COPY);
+ extra = sarrayToString(sa, 2);
+ sarrayDestroy(&sa);
+ }
+ if (!extra) {
+ snprintf(buffer, sizeof(buffer), "lpr %s -P%s &", fname, printer);
+ ret = system(buffer);
+ } else {
+ snprintf(buffer, sizeof(buffer), "lpr %s -P%s %s &",
+ fname, printer, extra);
+ ret = system(buffer);
+ }
+ lept_free(extra);
+ }
+
+ lept_free(fname);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ return 0;
+}
diff --git a/leptonica/prog/printsplitimage.c b/leptonica/prog/printsplitimage.c
new file mode 100644
index 00000000..4c8d35ba
--- /dev/null
+++ b/leptonica/prog/printsplitimage.c
@@ -0,0 +1,150 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * printsplitimage.c
+ *
+ * Syntax: printsplitimage filein nx ny [printer]
+ *
+ * nx = number of horizontal tiles
+ * ny = number of vertical tiles
+ *
+ * If printer is not specified, the only action is that the
+ * image is split into a set of tiles, and these are written
+ * out as a set of compressed (level 3) Postscript files.
+ * The images in the PostScript files are scaled to each fill
+ * an 8.5 x 11 inch page, up to the FILLING_FACTOR fraction
+ * in each direction.
+ *
+ * If printer is specified, these are printed on separate pages,
+ * because some printers cannot print multi-page Postscript of images.
+ *
+ * If your system does not have lpr, it likely has lp. You can run
+ * printsplitimage to make the PostScript files, and print them with lp:
+ * lp -d <printer> /tmp/lept/split/image0.ps
+ * lp -d <printer> /tmp/lept/split/image1.ps
+ * ...
+ * To print in color, see prog/printimage.c.
+ *
+ * ***************************************************************
+ * N.B. If a printer is specified, this program invokes lpr via
+ * "system'. It could pose a security vulnerability if used
+ * as a service in a production environment. Consequently,
+ * this program should only be used for debug and testing.
+ * ***************************************************************
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define USE_COMPRESSED 1
+
+ /* fill factor on 8.5 x 11 inch output page */
+static const l_float32 FILL_FACTOR = 0.95;
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fname, *printer;
+char buf[512];
+l_int32 nx, ny, i, w, h, ws, hs, n, ignore, index;
+l_float32 scale;
+FILE *fp;
+PIX *pixs, *pixt, *pixr;
+PIXA *pixa;
+SARRAY *sa;
+static char mainName[] = "printsplitimage";
+
+ if (argc != 4 && argc != 5)
+ return ERROR_INT(" Syntax: printsplitimage filein nx ny [printer]",
+ mainName, 1);
+ filein = argv[1];
+ nx = atoi(argv[2]);
+ ny = atoi(argv[3]);
+ printer = (argc == 5) ? argv[4] : NULL;
+
+ lept_stderr(
+ "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+ " Warning: this program should only be used for testing,\n"
+ " and not in a production environment, because of a\n"
+ " potential vulnerability with the 'system' call.\n"
+ "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n");
+
+ setLeptDebugOK(1);
+ lept_rmdir("lept/split");
+ lept_mkdir("lept/split");
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+ pixGetDimensions(pixs, &ws, &hs, NULL);
+ if (ny * ws > nx * hs) {
+ pixr = pixRotate90(pixs, 1);
+ pixa = pixaSplitPix(pixr, ny, nx, 0, 0);
+ } else {
+ pixr = pixClone(pixs);
+ pixa = pixaSplitPix(pixr, nx, ny, 0, 0);
+ }
+ pixDestroy(&pixr);
+
+ n = pixaGetCount(pixa);
+ sa = sarrayCreate(n);
+ for (i = 0; i < n; i++) {
+ pixt = pixaGetPix(pixa, i, L_CLONE);
+ pixGetDimensions(pixt, &w, &h, NULL);
+ scale = L_MIN(FILL_FACTOR * 2550 / w, FILL_FACTOR * 3300 / h);
+ snprintf(buf, sizeof(buf), "image%d.ps", i);
+ fname = genPathname("/tmp/lept/split", buf);
+ lept_stderr("fname: %s\n", fname);
+ sarrayAddString(sa, fname, L_INSERT);
+#if USE_COMPRESSED
+ index = 0;
+ pixWriteCompressedToPS(pixt, fname, (l_int32)(300. / scale), 3, &index);
+ index = 0; /* write each out to a separate file */
+#else /* uncompressed, level 1 */
+ fp = lept_fopen(fname, "wb+");
+ pixWriteStreamPS(fp, pixt, NULL, 300, scale);
+ lept_fclose(fp);
+#endif /* USE_COMPRESSED */
+ pixDestroy(&pixt);
+ }
+
+ if (argc == 5) {
+ for (i = 0; i < n; i++) {
+ fname = sarrayGetString(sa, i, L_NOCOPY);
+ snprintf(buf, sizeof(buf), "lpr -P%s %s &", printer, fname);
+ ignore = system(buf);
+ }
+ }
+
+ sarrayDestroy(&sa);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs);
+ return 0;
+}
+
diff --git a/leptonica/prog/printtiff.c b/leptonica/prog/printtiff.c
new file mode 100644
index 00000000..ef60f746
--- /dev/null
+++ b/leptonica/prog/printtiff.c
@@ -0,0 +1,98 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * printtiff.c
+ *
+ * Syntax: printtiff filein [printer]
+ *
+ * Prints a multipage tiff file of 1 bpp images to a printer.
+ * If the tiff is at standard fax resolution, it expands the
+ * vertical size by a factor of two before encapsulating in
+ * ccittg4 encoded PostScript. The PostScript file is left in /tmp,
+ * and erased (deleted, removed, unlinked) on the next invocation.
+ *
+ * If the printer is not specified, this just writes the PostScript
+ * file /tmp/print_tiff.ps.
+ *
+ * If your system does not have lpr, it likely has lp. You can run
+ * printtiff to make the PostScript file, and then print with lp:
+ * lp -d <printer> /tmp/print_tiff.ps
+ * lp -d <printer> -o ColorModel=Color /tmp/print_tiff.ps
+ * etc.
+ *
+ * ***************************************************************
+ * N.B. If a printer is specified, this program invokes lpr via
+ * "system'. It could pose a security vulnerability if used
+ * as a service in a production environment. Consequently,
+ * this program should only be used for debug and testing.
+ * ***************************************************************
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define TEMP_PS "print_tiff.ps" /* in the temp directory */
+#define FILL_FACTOR 0.95
+
+int main(int argc,
+ char **argv)
+{
+l_int32 ret;
+char *filein, *tempfile, *printer;
+char buf[512];
+static char mainName[] = "printtiff";
+
+ if (argc != 2 && argc != 3)
+ return ERROR_INT(" Syntax: printtiff filein [printer]", mainName, 1);
+ filein = argv[1];
+ if (argc == 3)
+ printer = argv[2];
+
+ lept_stderr(
+ "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+ " Warning: this program should only be used for testing,\n"
+ " and not in a production environment, because of a\n"
+ " potential vulnerability with the 'system' call.\n"
+ "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n");
+
+ setLeptDebugOK(1);
+ (void)lept_rm(NULL, TEMP_PS);
+ tempfile = genPathname("/tmp", TEMP_PS);
+ convertTiffMultipageToPS(filein, tempfile, FILL_FACTOR);
+
+ if (argc == 3) {
+ snprintf(buf, sizeof(buf), "lpr -P%s %s &", printer, tempfile);
+ ret = system(buf);
+ }
+
+ lept_free(tempfile);
+ return 0;
+}
+
diff --git a/leptonica/prog/projection_reg.c b/leptonica/prog/projection_reg.c
new file mode 100644
index 00000000..04b267dd
--- /dev/null
+++ b/leptonica/prog/projection_reg.c
@@ -0,0 +1,155 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * projection_reg.c
+ *
+ * Tests projection stats for rows and columns.
+ * Just for interest, a number of different tests are done.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void TestProjection(L_REGPARAMS *rp, PIX *pix);
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pix1, *pix2;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Use for input two different images */
+ pixs = pixRead("projectionstats.jpg");
+ pix1 = pixConvertTo8(pixs, 0);
+ pixDestroy(&pixs);
+ pixs = pixRead("feyn.tif");
+ pix2 = pixScaleToGray4(pixs);
+ pixDestroy(&pixs);
+
+ TestProjection(rp, pix1);
+ TestProjection(rp, pix2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return regTestCleanup(rp);
+}
+
+
+/*
+ * Test both vertical and horizontal projections on this image.
+ * Rotate the image by 90 degrees for the horizontal projection,
+ * so that the two results should be identical.
+ */
+void
+TestProjection(L_REGPARAMS *rp,
+ PIX *pixs)
+{
+NUMA *na1, *na2, *na3, *na4, *na5, *na6;
+NUMA *na7, *na8, *na9, *na10, *na11, *na12;
+PIX *pixd, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+PIX *pix7, *pix8, *pix9, *pix10, *pix11, *pix12;
+PIXA *pixa;
+
+ pixColumnStats(pixs, NULL, &na1, &na3, &na5, &na7, &na9, &na11);
+ pixd = pixRotateOrth(pixs, 1);
+ pixRowStats(pixd, NULL, &na2, &na4, &na6, &na8, &na10, &na12);
+ pixDestroy(&pixd);
+
+ pix1 = gplotSimplePix1(na1, "Mean value");
+ pix2 = gplotSimplePix1(na2, "Mean value");
+ pix3 = gplotSimplePix1(na3, "Median value");
+ pix4 = gplotSimplePix1(na4, "Median value");
+ pix5 = gplotSimplePix1(na5, "Mode value");
+ pix6 = gplotSimplePix1(na6, "Mode value");
+ pix7 = gplotSimplePix1(na7, "Mode count");
+ pix8 = gplotSimplePix1(na8, "Mode count");
+ pix9 = gplotSimplePix1(na9, "Variance");
+ pix10 = gplotSimplePix1(na10, "Variance");
+ pix11 = gplotSimplePix1(na11, "Square Root Variance");
+ pix12 = gplotSimplePix1(na12, "Square Root Variance");
+
+ /* This is run twice, on two different images */
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0, 19 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1, 20 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 2, 21 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 3, 22 */
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 4, 23 */
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 5, 24 */
+ regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 6, 25 */
+ regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 7, 26 */
+ regTestWritePixAndCheck(rp, pix9, IFF_PNG); /* 8, 27 */
+ regTestWritePixAndCheck(rp, pix10, IFF_PNG); /* 9, 28 */
+ regTestWritePixAndCheck(rp, pix11, IFF_PNG); /* 10, 29 */
+ regTestWritePixAndCheck(rp, pix12, IFF_PNG); /* 11, 30 */
+
+ /* Compare by pairs */
+ regTestComparePix(rp, pix1, pix2); /* 12, 31 */
+ regTestComparePix(rp, pix3, pix4); /* 13, 32 */
+ regTestComparePix(rp, pix5, pix6); /* 14, 33 */
+ regTestComparePix(rp, pix7, pix8); /* 15, 34 */
+ regTestComparePix(rp, pix9, pix10); /* 16, 35 */
+ regTestComparePix(rp, pix11, pix12); /* 17, 36 */
+
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ pixaAddPix(pixa, pix5, L_INSERT);
+ pixaAddPix(pixa, pix6, L_INSERT);
+ pixaAddPix(pixa, pix7, L_INSERT);
+ pixaAddPix(pixa, pix8, L_INSERT);
+ pixaAddPix(pixa, pix9, L_INSERT);
+ pixaAddPix(pixa, pix10, L_INSERT);
+ pixaAddPix(pixa, pix11, L_INSERT);
+ pixaAddPix(pixa, pix12, L_INSERT);
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 25, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 18, 37 */
+ pixDisplayWithTitle(pix1, 100, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+ numaDestroy(&na5);
+ numaDestroy(&na6);
+ numaDestroy(&na7);
+ numaDestroy(&na8);
+ numaDestroy(&na9);
+ numaDestroy(&na10);
+ numaDestroy(&na11);
+ numaDestroy(&na12);
+ return;
+}
diff --git a/leptonica/prog/projectionstats.jpg b/leptonica/prog/projectionstats.jpg
new file mode 100644
index 00000000..0d9f23cd
--- /dev/null
+++ b/leptonica/prog/projectionstats.jpg
Binary files differ
diff --git a/leptonica/prog/projective_reg.c b/leptonica/prog/projective_reg.c
new file mode 100644
index 00000000..70fcd040
--- /dev/null
+++ b/leptonica/prog/projective_reg.c
@@ -0,0 +1,234 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static void MakePtas(l_int32 i, PTA **pptas, PTA **pptad);
+
+ /* Sample values.
+ * 1-3: invertability tests
+ * 4: comparison between sampling and sequential
+ * 5: test with large distortion
+ */
+static const l_int32 x1[] = { 300, 300, 300, 300, 32};
+static const l_int32 y1[] = {1200, 1200, 1250, 1250, 934};
+static const l_int32 x2[] = {1200, 1200, 1125, 1300, 487};
+static const l_int32 y2[] = {1100, 1100, 1100, 1250, 934};
+static const l_int32 x3[] = { 200, 200, 200, 250, 32};
+static const l_int32 y3[] = { 200, 200, 200, 300, 67};
+static const l_int32 x4[] = {1200, 1200, 1300, 1250, 332};
+static const l_int32 y4[] = { 400, 200, 200, 300, 57};
+
+static const l_int32 xp1[] = { 300, 300, 1150, 300, 32};
+static const l_int32 yp1[] = {1200, 1400, 1150, 1350, 934};
+static const l_int32 xp2[] = {1100, 1400, 320, 1300, 487};
+static const l_int32 yp2[] = {1000, 1500, 1300, 1200, 904};
+static const l_int32 xp3[] = { 250, 200, 1310, 300, 61};
+static const l_int32 yp3[] = { 200, 300, 250, 325, 83};
+static const l_int32 xp4[] = {1250, 1200, 240, 1250, 412};
+static const l_int32 yp4[] = { 300, 300, 250, 350, 83};
+
+#define ADDED_BORDER_PIXELS 250
+#define ALL 1
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i;
+PIX *pixs, *pixsc, *pixb, *pixg, *pixc, *pixcs, *pix1, *pix2, *pixd;
+PIXA *pixa;
+PTA *ptas, *ptad;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn.tif");
+ pixsc = pixScale(pixs, 0.3, 0.3);
+
+#if ALL
+ /* Test invertability of sampling */
+ lept_stderr("Test invertability of sampling\n");
+ pixa = pixaCreate(0);
+ for (i = 0; i < 3; i++) {
+ pixb = pixAddBorder(pixsc, ADDED_BORDER_PIXELS, 0);
+ MakePtas(i, &ptas, &ptad);
+ pix1 = pixProjectiveSampledPta(pixb, ptad, ptas, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0,3,6 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixProjectiveSampledPta(pix1, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1,4,7 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixd = pixRemoveBorder(pix2, ADDED_BORDER_PIXELS);
+ pixXor(pixd, pixd, pixsc);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 2,5,8 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixb);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ }
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 0.5, 20, 3);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 9 */
+ pixDisplayWithTitle(pix1, 0, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pixsc);
+ pixaDestroy(&pixa);
+#endif
+
+#if ALL
+ /* Test invertability of interpolation on grayscale */
+ lept_stderr("Test invertability of grayscale interpolation\n");
+ pixa = pixaCreate(0);
+ pixg = pixScaleToGray(pixs, 0.2);
+ for (i = 0; i < 2; i++) {
+ pixb = pixAddBorder(pixg, ADDED_BORDER_PIXELS / 2, 255);
+ MakePtas(i, &ptas, &ptad);
+ pix1 = pixProjectivePta(pixb, ptad, ptas, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 10,13 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixProjectivePta(pix1, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 11,14 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixd = pixRemoveBorder(pix2, ADDED_BORDER_PIXELS / 2);
+ pixXor(pixd, pixd, pixg);
+ pixInvert(pixd, pixd);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 12,15 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixb);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ }
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 0.5, 20, 3);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 16 */
+ pixDisplayWithTitle(pix1, 300, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pixg);
+ pixaDestroy(&pixa);
+#endif
+
+#if ALL
+ /* Test invertability of interpolation on color */
+ lept_stderr("Test invertability of color interpolation\n");
+ pixa = pixaCreate(0);
+ pixc = pixRead("test24.jpg");
+ pixcs = pixScale(pixc, 0.3, 0.3);
+ for (i = 0; i < 5; i++) {
+ if (i == 2) continue;
+ pixb = pixAddBorder(pixcs, ADDED_BORDER_PIXELS / 2, 0xffffff00);
+ MakePtas(i, &ptas, &ptad);
+ pix1 = pixProjectivePta(pixb, ptad, ptas, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 17,20,23,26 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixProjectivePta(pix1, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 18,21,24,27 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixd = pixRemoveBorder(pix2, ADDED_BORDER_PIXELS / 2);
+ pixXor(pixd, pixd, pixcs);
+ pixInvert(pixd, pixd);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 19,22,25,28 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixb);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+ }
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 0.5, 20, 3);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 29 */
+ pixDisplayWithTitle(pix1, 600, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pixc);
+ pixDestroy(&pixcs);
+ pixaDestroy(&pixa);
+#endif
+
+#if ALL
+ /* Comparison between sampling and interpolated */
+ lept_stderr("Compare sampling with interpolated\n");
+ MakePtas(3, &ptas, &ptad);
+ pixa = pixaCreate(0);
+ pixg = pixScaleToGray(pixs, 0.2);
+
+ /* Use sampled transform */
+ pix1 = pixProjectiveSampledPta(pixg, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 30 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+
+ /* Use interpolated transforms */
+ pix2 = pixProjectivePta(pixg, ptas, ptad, L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 31 */
+ pixaAddPix(pixa, pix2, L_COPY);
+
+ /* Compare the results */
+ pixXor(pix2, pix2, pix1);
+ pixInvert(pix2, pix2);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 32 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 0.5, 20, 3);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 33 */
+ pixDisplayWithTitle(pix1, 900, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pixg);
+ pixaDestroy(&pixa);
+ ptaDestroy(&ptas);
+ ptaDestroy(&ptad);
+#endif
+
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
+
+static void
+MakePtas(l_int32 i,
+ PTA **pptas,
+ PTA **pptad)
+{
+
+ *pptas = ptaCreate(4);
+ ptaAddPt(*pptas, x1[i], y1[i]);
+ ptaAddPt(*pptas, x2[i], y2[i]);
+ ptaAddPt(*pptas, x3[i], y3[i]);
+ ptaAddPt(*pptas, x4[i], y4[i]);
+ *pptad = ptaCreate(4);
+ ptaAddPt(*pptad, xp1[i], yp1[i]);
+ ptaAddPt(*pptad, xp2[i], yp2[i]);
+ ptaAddPt(*pptad, xp3[i], yp3[i]);
+ ptaAddPt(*pptad, xp4[i], yp4[i]);
+ return;
+}
+
diff --git a/leptonica/prog/ps2jpeg b/leptonica/prog/ps2jpeg
new file mode 100644
index 00000000..8f533ac4
--- /dev/null
+++ b/leptonica/prog/ps2jpeg
@@ -0,0 +1,32 @@
+#!/bin/bash
+# ps2jpeg
+#
+# Rasterizes a postscript file, saving as a set of RGB images
+#
+# input: PostScript file
+# root name of output files
+# output: 24 bpp RGB jpeg files for each page
+#
+# N.B. Requires ghostscript
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " inpsfile outjpgroot"
+ exit -1
+fi
+
+inpsfile=$1
+outjpgroot=$2
+
+# (need mysterious "primer")
+# choose one of the two options below
+
+# output image size depending on resolution
+echo "0 neg 0 neg" translate | /usr/local/bin/gs -sDEVICE=jpeg -sOutputFile=${outjpgroot}%03d.jpg -r300x300 -q - ${inpsfile}
+
+# output fixed image size
+#echo "0 neg 0 neg" translate | gs -sDEVICE=jpeg -sOutputFile=${outjpgroot}%03d.jpg -g2550x3300 -r300x300 -q - ${inpsfile}
+
+
diff --git a/leptonica/prog/ps2png b/leptonica/prog/ps2png
new file mode 100644
index 00000000..bace17a4
--- /dev/null
+++ b/leptonica/prog/ps2png
@@ -0,0 +1,43 @@
+#!/bin/bash
+# ps2png
+#
+# Rasterizes a postscript file, saving as a set of bitmaps
+#
+# input: PostScript file
+# root name of output files
+# output: png binary files for each page
+#
+# Restriction: the input PostScript file must be binary
+#
+# N.B. Requires ghostscript
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " inpsfile outpngroot"
+ exit -1
+fi
+
+inpsfile=$1
+outpngroot=$2
+
+# strip off directory and suffix parts of $1 to use in other names
+basename=${1##*/}
+baseroot=${basename%.*}
+
+# make names for temporary files
+tmppsfile=${baseroot}.$$_.ps
+tmppsroot=${tmppsfile%.*}
+
+# have the temporary files deleted on exit, interrupt, etc:
+trap "/bin/rm -f ${tmppsroot}*" EXIT SIGHUP SIGINT SIGTERM
+
+cp $inpsfile $tmppsfile
+
+# need mysterious "primer"
+echo "0 neg 0 neg" translate | gs -sDEVICE=pngmono -sOutputFile=${outpngroot}%03d.png -g2550x3300 -r300x300 -q - ${tmppsfile}
+
+
+
+
diff --git a/leptonica/prog/ps2png-binary b/leptonica/prog/ps2png-binary
new file mode 100644
index 00000000..fae33e14
--- /dev/null
+++ b/leptonica/prog/ps2png-binary
@@ -0,0 +1,35 @@
+#!/bin/bash
+# ps2png-binary
+#
+# Rasterizes a postscript file, saving as a set of binary png images
+#
+# input: PostScript file
+# root name of output files
+# output: png binary files for each page
+#
+# Restriction: the input PostScript file must be binary
+#
+# N.B. Requires ghostscript
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " inpsfile outpngroot"
+ exit -1
+fi
+
+inpsfile=$1
+outpngroot=$2
+
+# need mysterious "primer"
+# choose one of the two options below
+
+# output image size depending on resolution
+echo "0 neg 0 neg" translate | gs -sDEVICE=pngmono -sOutputFile=${outpngroot}%03d.png -r300x300 -q - ${inpsfile}
+
+# output fixed image size
+#echo "0 neg 0 neg" translate | gs -sDEVICE=pngmono -sOutputFile=${outpngroot}%03d.png -g2550x3300 -r300x300 -q - ${inpsfile}
+
+
+
diff --git a/leptonica/prog/ps2png-color b/leptonica/prog/ps2png-color
new file mode 100644
index 00000000..e9c18941
--- /dev/null
+++ b/leptonica/prog/ps2png-color
@@ -0,0 +1,33 @@
+#!/bin/bash
+# ps2png-color
+#
+# Rasterizes a postscript file, saving as a set of 24 bpp RGB png images
+#
+# input: PostScript file
+# root name of output files
+# output: 24 bpp RGB png files for each page
+#
+# N.B. Requires ghostscript
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " inpsfile outpngroot"
+ exit -1
+fi
+
+inpsfile=$1
+outpngroot=$2
+
+# need mysterious "primer"
+# choose one of the two options below
+
+# output image size depending on resolution
+echo "0 neg 0 neg" translate | gs -sDEVICE=png16m -sOutputFile=${outpngroot}%03d.png -r300x300 -q - ${inpsfile}
+
+# output fixed image size
+echo "0 neg 0 neg" translate | gs -sDEVICE=png16m -sOutputFile=${outpngroot}%03d.png -g2550x3300 -r300x300 -q - ${inpsfile}
+
+
+
diff --git a/leptonica/prog/ps2png-gray b/leptonica/prog/ps2png-gray
new file mode 100644
index 00000000..ab03da81
--- /dev/null
+++ b/leptonica/prog/ps2png-gray
@@ -0,0 +1,33 @@
+#!/bin/bash
+# ps2png-gray
+#
+# Rasterizes a postscript file, saving as a set of 8 bpp png images
+#
+# input: PostScript file
+# root name of output files
+# output: 8 bpp png files for each page
+#
+# N.B. Requires ghostscript
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " inpsfile outpngroot"
+ exit -1
+fi
+
+inpsfile=$1
+outpngroot=$2
+
+# need mysterious "primer"
+# choose one of the two options below
+
+# output image size depending on resolution
+echo "0 neg 0 neg" translate | gs -sDEVICE=pnggray -sOutputFile=${outpngroot}%03d.png -r300x300 -q - ${inpsfile}
+
+# output fixed image size
+#echo "0 neg 0 neg" translate | gs -sDEVICE=pnggray -sOutputFile=${outpngroot}%03d.png -g2550x3300 -r300x300 -q - ${inpsfile}
+
+
+
diff --git a/leptonica/prog/ps2tiff b/leptonica/prog/ps2tiff
new file mode 100644
index 00000000..f6b7c966
--- /dev/null
+++ b/leptonica/prog/ps2tiff
@@ -0,0 +1,42 @@
+#!/bin/bash
+# ps2tiff
+#
+# Rasterizes a postscript file, saving as a set of g4 compressed
+# tiff images
+#
+# input: PostScript file
+# root name of output files
+# output: ccitt-g4 compressed tiff binary files for each page
+#
+# Restriction: the input PostScript file must be binary
+#
+# N.B. Requires ghostscript
+
+scriptname=${0##*/}
+
+if test $# != 2
+then
+ echo "usage: " $scriptname " inpsfile outtifroot"
+ exit -1
+fi
+
+inpsfile=$1
+outtifroot=$2
+
+# assert (input postscript filename ends in .ps)
+if test ${inpsfile##*.} != ps
+then
+ echo $scriptname ": " $inpsfile "does not end in .ps"
+ exit -1
+fi
+
+# output image size depending on resolution
+#echo "0 neg 0 neg" translate | gs -sDEVICE=tiffg4 -sOutputFile=${outtifroot}%03d.tif -r300x300 -q - ${inpsfile}
+echo "0 neg 0 neg" translate | ~/pckg/gs/ghostscript-8.53/bin/gs -sDEVICE=tiffg4 -sOutputFile=${outtifroot}%03d.tif -r300x300 -q - ${inpsfile}
+
+# output fixed image size
+#echo "0 neg 0 neg" translate | gs -sDEVICE=tiffg4 -sOutputFile=${outtifroot}%03d.tif -g2550x3300 -r300x300 -q - ${inpsfile}
+
+# use this to output to a single multipage tiff file
+#tiffcp -c g4 ${outtifroot}*.tif ${outtifroot}.tif
+
diff --git a/leptonica/prog/psio_reg.c b/leptonica/prog/psio_reg.c
new file mode 100644
index 00000000..e14a65b2
--- /dev/null
+++ b/leptonica/prog/psio_reg.c
@@ -0,0 +1,250 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * psio_reg.c
+ *
+ * Tests writing of images in PS, with arbitrary scaling and
+ * translation, in the following formats:
+ *
+ * - uncompressed
+ * - DCT compressed (jpeg for 8 bpp grayscale and RGB)
+ * - CCITT-G4 compressed (g4 fax compression for 1 bpp)
+ * - Flate compressed (gzip compression)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const char *WeaselNames[] = {"weasel2.4c.png",
+ "weasel2.4g.png",
+ "weasel2.png",
+ "weasel4.11c.png",
+ "weasel4.8g.png",
+ "weasel4.16g.png",
+ "weasel8.16g.png",
+ "weasel8.149g.png",
+ "weasel8.240c.png",
+ "weasel8.png",
+ "weasel32.png"};
+int main(int argc,
+ char **argv)
+{
+l_int32 i, w, h;
+l_float32 factor, scale;
+BOX *box;
+FILE *fp1;
+PIX *pixs, *pixt;
+PIXA *pixa;
+SARRAY *sa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "psio_reg");
+ exit(77);
+#endif
+#if !defined(HAVE_LIBJPEG)
+ L_ERROR("This test requires libjpeg to run.\n", "psio_reg");
+ exit(77);
+#endif
+#if !defined(HAVE_LIBTIFF)
+ L_ERROR("This test requires libtiff to run.\n", "psio_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+#if !USE_PSIO
+ lept_stderr("psio writing is not enabled\n"
+ "See environ.h: #define USE_PSIO 1\n\n");
+ regTestCleanup(rp);
+ return 0;
+#endif /* abort */
+
+ factor = 0.95;
+
+ /* Uncompressed PS with scaling but centered on the page */
+ pixs = pixRead("feyn-fract.tif");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ scale = L_MIN(factor * 2550 / w, factor * 3300 / h);
+ fp1 = lept_fopen("/tmp/lept/regout/psio0.ps", "wb+");
+ pixWriteStreamPS(fp1, pixs, NULL, 300, scale);
+ lept_fclose(fp1);
+ regTestCheckFile(rp, "/tmp/lept/regout/psio0.ps"); /* 0 */
+ pixDestroy(&pixs);
+
+ /* Uncompressed PS with scaling, with LL corner at (1500, 1500) mils */
+ pixs = pixRead("weasel4.11c.png");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ scale = L_MIN(factor * 2550 / w, factor * 3300 / h);
+ box = boxCreate(1500, 1500, (l_int32)(1000 * scale * w / 300),
+ (l_int32)(1000 * scale * h / 300));
+ fp1 = lept_fopen("/tmp/lept/regout/psio1.ps", "wb+");
+ pixWriteStreamPS(fp1, pixs, box, 300, 1.0);
+ lept_fclose(fp1);
+ regTestCheckFile(rp, "/tmp/lept/regout/psio1.ps"); /* 1 */
+ boxDestroy(&box);
+ pixDestroy(&pixs);
+
+ /* DCT compressed PS with LL corner at (300, 1000) pixels */
+ pixs = pixRead("marge.jpg");
+ pixt = pixConvertTo32(pixs);
+ pixWrite("/tmp/lept/regout/psio2.jpg", pixt, IFF_JFIF_JPEG);
+ convertJpegToPS("/tmp/lept/regout/psio2.jpg", "/tmp/lept/regout/psio3.ps",
+ "w", 300, 1000, 0, 4.0, 1, 1);
+ regTestCheckFile(rp, "/tmp/lept/regout/psio2.jpg"); /* 2 */
+ regTestCheckFile(rp, "/tmp/lept/regout/psio3.ps"); /* 3 */
+ pixDestroy(&pixt);
+ pixDestroy(&pixs);
+
+ /* For each page, apply tiff g4 image first; then jpeg or png over it */
+ convertG4ToPS("feyn.tif", "/tmp/lept/regout/psio4.ps", "w",
+ 0, 0, 0, 1.0, 1, 1, 0);
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio4.ps",
+ "a", 500, 100, 300, 2.0, 1, 0);
+ convertFlateToPS("weasel4.11c.png", "/tmp/lept/regout/psio4.ps",
+ "a", 300, 400, 300, 6.0, 1, 0);
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio4.ps",
+ "a", 100, 800, 300, 1.5, 1, 1);
+
+ convertG4ToPS("feyn.tif", "/tmp/lept/regout/psio4.ps",
+ "a", 0, 0, 0, 1.0, 2, 1, 0);
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio4.ps",
+ "a", 1000, 700, 300, 2.0, 2, 0);
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio4.ps",
+ "a", 100, 200, 300, 2.0, 2, 1);
+
+ convertG4ToPS("feyn.tif", "/tmp/lept/regout/psio4.ps",
+ "a", 0, 0, 0, 1.0, 3, 1, 0);
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio4.ps",
+ "a", 200, 200, 300, 2.0, 3, 0);
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio4.ps",
+ "a", 200, 900, 300, 2.0, 3, 1);
+ regTestCheckFile(rp, "/tmp/lept/regout/psio4.ps"); /* 4 */
+
+ /* Now apply jpeg first; then paint through a g4 mask.
+ * For gv, the first image with a b.b. determines the
+ * window size for the canvas, so we put down the largest
+ * image first. If we had rendered a small image first,
+ * gv and evince will not show the entire page. However, after
+ * conversion to pdf, everything works fine, regardless of the
+ * order in which images are placed into the PS. That is
+ * because the pdf interpreter is robust to bad hints, ignoring
+ * the page hints and computing the bounding box from the
+ * set of images rendered on the page.
+ *
+ * Concatenate several pages, with colormapped png, color
+ * jpeg and tiffg4 images (with the g4 image acting as a mask
+ * that we're painting black through. If the text layer
+ * is painted first, the following images occlude it; otherwise,
+ * the images remain in the background of the text. */
+ pixs = pixRead("wyom.jpg");
+ pixt = pixScaleToSize(pixs, 2528, 3300);
+ pixWrite("/tmp/lept/regout/psio5.jpg", pixt, IFF_JFIF_JPEG);
+ pixDestroy(&pixs);
+ pixDestroy(&pixt);
+ convertJpegToPS("/tmp/lept/regout/psio5.jpg", "/tmp/lept/regout/psio5.ps",
+ "w", 0, 0, 300, 1.0, 1, 0);
+ convertFlateToPS("weasel8.240c.png", "/tmp/lept/regout/psio5.ps",
+ "a", 100, 100, 300, 5.0, 1, 0);
+ convertFlateToPS("weasel8.149g.png", "/tmp/lept/regout/psio5.ps",
+ "a", 200, 300, 300, 5.0, 1, 0);
+ convertFlateToPS("weasel4.11c.png", "/tmp/lept/regout/psio5.ps",
+ "a", 300, 500, 300, 5.0, 1, 0);
+ convertG4ToPS("feyn.tif", "/tmp/lept/regout/psio5.ps",
+ "a", 0, 0, 0, 1.0, 1, 1, 1);
+
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio5.ps",
+ "a", 500, 100, 300, 2.0, 2, 0);
+ convertFlateToPS("weasel4.11c.png", "/tmp/lept/regout/psio5.ps",
+ "a", 300, 400, 300, 6.0, 2, 0);
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio5.ps",
+ "a", 100, 800, 300, 1.5, 2, 0);
+ convertG4ToPS("feyn.tif", "/tmp/lept/regout/psio5.ps",
+ "a", 0, 0, 0, 1.0, 2, 1, 1);
+
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio5.ps",
+ "a", 500, 100, 300, 2.0, 3, 0);
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio5.ps",
+ "a", 100, 800, 300, 2.0, 3, 0);
+ convertG4ToPS("feyn.tif", "/tmp/lept/regout/psio5.ps",
+ "a", 0, 0, 0, 1.0, 3, 1, 1);
+
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio5.ps",
+ "a", 700, 700, 300, 2.0, 4, 0);
+ convertFlateToPS("weasel8.149g.png", "/tmp/lept/regout/psio5.ps",
+ "a", 400, 400, 300, 5.0, 4, 0);
+ convertG4ToPS("feyn.tif", "/tmp/lept/regout/psio5.ps",
+ "a", 0, 0, 0, 1.0, 4, 1, 0);
+ convertFlateToPS("weasel8.240c.png", "/tmp/lept/regout/psio5.ps",
+ "a", 100, 220, 300, 5.0, 4, 0);
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio5.ps",
+ "a", 100, 200, 300, 2.0, 4, 1);
+
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio5.ps",
+ "a", 200, 200, 300, 1.5, 5, 0);
+ convertFlateToPS("weasel8.240c.png", "/tmp/lept/regout/psio5.ps",
+ "a", 140, 80, 300, 7.0, 5, 0);
+ convertG4ToPS("feyn.tif", "/tmp/lept/regout/psio5.ps",
+ "a", 0, 0, 0, 1.0, 5, 1, 0);
+ convertFlateToPS("weasel8.149g.png", "/tmp/lept/regout/psio5.ps",
+ "a", 280, 310, 300, 5.0, 4, 0);
+ convertJpegToPS("marge.jpg", "/tmp/lept/regout/psio5.ps",
+ "a", 200, 900, 300, 2.0, 5, 1);
+ regTestCheckFile(rp, "/tmp/lept/regout/psio5.ps"); /* 5 */
+
+ /* Generation using segmentation masks */
+ convertSegmentedPagesToPS(".", "lion-page", 10, ".", "lion-mask", 10,
+ 0, 100, 2.0, 0.8, 190,
+ "/tmp/lept/regout/psio6.ps");
+ regTestCheckFile(rp, "/tmp/lept/regout/psio6.ps"); /* 6 */
+
+ /* PS generation for embeddding */
+ convertJpegToPSEmbed("tetons.jpg", "/tmp/lept/regout/psio7.ps");
+ regTestCheckFile(rp, "/tmp/lept/regout/psio7.ps"); /* 7 */
+
+ convertG4ToPSEmbed("feyn-fract.tif", "/tmp/lept/regout/psio8.ps");
+ regTestCheckFile(rp, "/tmp/lept/regout/psio8.ps"); /* 8 */
+
+ convertFlateToPSEmbed("weasel8.240c.png", "/tmp/lept/regout/psio9.ps");
+ regTestCheckFile(rp, "/tmp/lept/regout/psio9.ps"); /* 9 */
+
+ /* Writing compressed from a pixa */
+ sa = sarrayCreate(0);
+ for (i = 0; i < 11; i++)
+ sarrayAddString(sa, WeaselNames[i], L_COPY);
+ pixa = pixaReadFilesSA(sa);
+ pixaWriteCompressedToPS(pixa, "/tmp/lept/regout/psio10.ps", 0, 3);
+ regTestCheckFile(rp, "/tmp/lept/regout/psio10.ps"); /* 10 */
+ pixaDestroy(&pixa);
+ sarrayDestroy(&sa);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/psioseg_reg.c b/leptonica/prog/psioseg_reg.c
new file mode 100644
index 00000000..c0a63fff
--- /dev/null
+++ b/leptonica/prog/psioseg_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * psioseg_reg.c
+ *
+ * This tests the PostScript output for images with mixed
+ * text and images, coming from source of different depths,
+ * with and without colormaps.
+ *
+ * Both convertFilesFittedToPS() and convertSegmentedPagesToPS()
+ * generate a compressed PostScript file from a subset of images in
+ * a directory. However, the latter function can also accept 1 bpp
+ * masks that delineate image (as opposed to text) regions in
+ * the corresponding page image file. Then, for page images that
+ * are not 1 bpp, it generates mixed raster PostScript with
+ * g4 encoding for the text and jpeg ("DCT") encoding for the
+ * maining image parts.
+ *
+ * N.B. Although not required for 'success' on the regression test,
+ * this program uses ps2pdf to generate the pdf output.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char buf[512];
+char *psname, *pdfname;
+l_int32 w, h, wc, hc, ret;
+l_float32 scalefactor;
+PIX *pixs, *pixc, *pixht, *pixtxt, *pixmfull;
+PIX *pix4c, *pix8c, *pix8g, *pix32, *pixcs, *pixcs2;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "psioseg_reg");
+ exit(77);
+#endif
+#if !defined(HAVE_LIBJPEG)
+ L_ERROR("This test requires libjpeg to run.\n", "psioseg_reg");
+ exit(77);
+#endif
+#if !defined(HAVE_LIBTIFF)
+ L_ERROR("This test requires libtiff to run.\n", "psioseg_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+#if !USE_PSIO
+ lept_stderr("psio writing is not enabled\n"
+ "See environ.h: #define USE_PSIO 1\n\n");
+ regTestCleanup(rp);
+ return 0;
+#endif /* abort */
+
+ /* Source for generating images */
+ pixs = pixRead("pageseg2.tif"); /* 1 bpp */
+ pixc = pixRead("tetons.jpg"); /* 32 bpp */
+
+ /* Get a halftone segmentation mask for pixs */
+ pixGetRegionsBinary(pixs, &pixht, NULL, NULL, 0);
+ pixtxt = pixSubtract(NULL, pixs, pixht);
+
+ /* Construct a 32 bpp image in full page size, along with
+ * a mask that can be used to render it. */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixGetDimensions(pixc, &wc, NULL, NULL);
+ scalefactor = (l_float32)w / (l_float32)wc;
+ pixcs = pixScale(pixc, scalefactor, scalefactor);
+ pixGetDimensions(pixcs, &wc, &hc, NULL);
+ pixcs2 = pixCreate(w, h, 32);
+ pixRasterop(pixcs2, 0, 0, w, hc, PIX_SRC, pixcs, 0, 0);
+ pixRasterop(pixcs2, 0, hc, w, hc, PIX_SRC, pixcs, 0, 0);
+ regTestWritePixAndCheck(rp, pixcs2, IFF_JFIF_JPEG); /* 0 */
+ pixmfull = pixCreate(w, h, 1);
+ pixSetAll(pixmfull); /* use as mask to render the color image */
+
+ /* Now make a 32 bpp input image, taking text parts from the
+ * page image and image parts from pixcs2. */
+ pix32 = pixConvertTo32(pixtxt);
+ pixCombineMasked(pix32, pixcs2, pixht);
+ regTestWritePixAndCheck(rp, pix32, IFF_JFIF_JPEG); /* 1 */
+
+ /* Make an 8 bpp gray version */
+ pix8g = pixConvertRGBToLuminance(pix32);
+ regTestWritePixAndCheck(rp, pix8g, IFF_JFIF_JPEG); /* 2 */
+
+ /* Make an 8 bpp colormapped version */
+ pix8c = pixOctreeColorQuant(pix32, 240, 0);
+ regTestWritePixAndCheck(rp, pix8c, IFF_PNG); /* 3 */
+
+ /* Make a 4 bpp colormapped version */
+ pix4c = pixOctreeQuantNumColors(pix32, 16, 4);
+ regTestWritePixAndCheck(rp, pix4c, IFF_PNG); /* 4 */
+
+ /* Write out the files to be imaged */
+ lept_mkdir("lept/psio");
+ pixWrite("/tmp/lept/psio/image_001.tif", pixs, IFF_TIFF_G4);
+ pixWrite("/tmp/lept/psio/image_002.tif", pixht, IFF_TIFF_G4);
+ pixWrite("/tmp/lept/psio/image_003.tif", pixtxt, IFF_TIFF_G4);
+ pixWrite("/tmp/lept/psio/image_004.jpg", pixcs2, IFF_JFIF_JPEG);
+ pixWrite("/tmp/lept/psio/mask_004.tif", pixmfull, IFF_TIFF_G4);
+ pixWrite("/tmp/lept/psio/image_005.jpg", pix32, IFF_JFIF_JPEG);
+ pixWrite("/tmp/lept/psio/mask_005.tif", pixht, IFF_TIFF_G4);
+ pixWrite("/tmp/lept/psio/image_006.jpg", pix8g, IFF_JFIF_JPEG);
+ pixWrite("/tmp/lept/psio/mask_006.tif", pixht, IFF_TIFF_G4);
+ pixWrite("/tmp/lept/psio/image_007.png", pix8c, IFF_PNG);
+ pixWrite("/tmp/lept/psio/mask_007.tif", pixht, IFF_TIFF_G4);
+ pixWrite("/tmp/lept/psio/image_008.png", pix4c, IFF_PNG);
+ pixWrite("/tmp/lept/psio/mask_008.tif", pixht, IFF_TIFF_G4);
+ pixDestroy(&pixs);
+ pixDestroy(&pixc);
+ pixDestroy(&pixht);
+ pixDestroy(&pixtxt);
+ pixDestroy(&pixcs);
+ pixDestroy(&pixcs2);
+ pixDestroy(&pixmfull);
+ pixDestroy(&pix32);
+ pixDestroy(&pix8g);
+ pixDestroy(&pix8c);
+ pixDestroy(&pix4c);
+
+ /* Generate the 8 page ps */
+ convertSegmentedPagesToPS("/tmp/lept/psio", "image_", 6, "/tmp/lept/psio",
+ "mask_", 5, 0, 10, 2.0, 0.15, 190,
+ "/tmp/lept/regout/psioseg.5.ps");
+ regTestCheckFile(rp, "/tmp/lept/regout/psioseg.5.ps"); /* 5 */
+ L_INFO("Output ps: /tmp/lept/regout/psioseg.5.ps\n", rp->testname);
+
+ /* For convenience, also generate a pdf of this, using ps2pdf */
+ psname = genPathname("/tmp/lept/regout", "psioseg.5.ps");
+ pdfname = genPathname("/tmp/lept/regout", "psioseg.5.pdf");
+ snprintf(buf, sizeof(buf), "ps2pdf %s %s", psname, pdfname);
+ ret = system(buf); /* ps2pdf */
+ lept_free(psname);
+ lept_free(pdfname);
+ if (!ret)
+ L_INFO("Output pdf: /tmp/lept/regout/psioseg.5.pdf\n", rp->testname);
+ else
+ L_WARNING("ps2pdf failed to generate pdf\n", rp->testname);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/pta_reg.c b/leptonica/prog/pta_reg.c
new file mode 100644
index 00000000..74b045d2
--- /dev/null
+++ b/leptonica/prog/pta_reg.c
@@ -0,0 +1,228 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * pta_reg.c
+ *
+ * This tests several ptaa functions, including:
+ * - ptaaGetBoundaryPixels()
+ * - pixRenderRandomCmapPtaa()
+ * - pixDisplayPtaa()
+ *
+ * Also tests these pta functions:
+ * - pixRenderPtaArb()
+ * - ptaRotate()
+ * - ptaSort()
+ * - ptaSort2d()
+ * - ptaEqual()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static PIX *PtaDisplayRotate(PIX *pixs, l_float32 xc, l_float32 yc);
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, nbox, npta, fgcount, bgcount, count, w, h, x, y, same;
+BOXA *boxa;
+PIX *pixs, *pixfg, *pixbg, *pixc, *pixb, *pixd;
+PIX *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa;
+PTA *pta, *pta1, *pta2, *pta3;
+PTAA *ptaafg, *ptaabg;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn-fract.tif");
+ boxa = pixConnComp(pixs, NULL, 8);
+ nbox = boxaGetCount(boxa);
+ regTestCompareValues(rp, nbox, 464, 0); /* 0 */
+
+ /* Get fg and bg boundary pixels */
+ pixfg = pixMorphSequence(pixs, "e3.3", 0);
+ pixXor(pixfg, pixfg, pixs);
+ pixCountPixels(pixfg, &fgcount, NULL);
+ regTestCompareValues(rp, fgcount, 58764, 0); /* 1 */
+
+ pixbg = pixMorphSequence(pixs, "d3.3", 0);
+ pixXor(pixbg, pixbg, pixs);
+ pixCountPixels(pixbg, &bgcount, NULL);
+ regTestCompareValues(rp, bgcount, 60335, 0); /* 2 */
+
+ /* Get ptaa of fg pixels */
+ ptaafg = ptaaGetBoundaryPixels(pixs, L_BOUNDARY_FG, 8, NULL, NULL);
+ npta = ptaaGetCount(ptaafg);
+ regTestCompareValues(rp, npta, nbox, 0); /* 3 */
+ count = 0;
+ for (i = 0; i < npta; i++) {
+ pta = ptaaGetPta(ptaafg, i, L_CLONE);
+ count += ptaGetCount(pta);
+ ptaDestroy(&pta);
+ }
+ regTestCompareValues(rp, fgcount, count, 0); /* 4 */
+
+ /* Get ptaa of bg pixels. Note that the number of bg pts
+ * is, in general, larger than the number of bg boundary pixels,
+ * because bg boundary pixels are shared by two c.c. that
+ * are 1 pixel apart. */
+ ptaabg = ptaaGetBoundaryPixels(pixs, L_BOUNDARY_BG, 8, NULL, NULL);
+ npta = ptaaGetCount(ptaabg);
+ regTestCompareValues(rp, npta, nbox, 0); /* 5 */
+ count = 0;
+ for (i = 0; i < npta; i++) {
+ pta = ptaaGetPta(ptaabg, i, L_CLONE);
+ count += ptaGetCount(pta);
+ ptaDestroy(&pta);
+ }
+ regTestCompareValues(rp, count, 60602, 0); /* 6 */
+
+ /* Render the fg boundary pixels on top of pixs. */
+ pixa = pixaCreate(4);
+ pixc = pixRenderRandomCmapPtaa(pixs, ptaafg, 0, 0, 0);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 7 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+
+ /* Render the bg boundary pixels on top of pixs. */
+ pixc = pixRenderRandomCmapPtaa(pixs, ptaabg, 0, 0, 0);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 8 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixClearAll(pixs);
+
+ /* Render the fg boundary pixels alone. */
+ pixc = pixRenderRandomCmapPtaa(pixs, ptaafg, 0, 0, 0);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 9 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+
+ /* Verify that the fg pixels are the same set as we
+ * originally started with. */
+ pixb = pixConvertTo1(pixc, 255);
+ regTestComparePix(rp, pixb, pixfg); /* 10 */
+ pixDestroy(&pixb);
+
+ /* Render the bg boundary pixels alone. */
+ pixc = pixRenderRandomCmapPtaa(pixs, ptaabg, 0, 0, 0);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 11 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+
+ /* Verify that the bg pixels are the same set as we
+ * originally started with. */
+ pixb = pixConvertTo1(pixc, 255);
+ regTestComparePix(rp, pixb, pixbg); /* 12 */
+ pixDestroy(&pixb);
+
+ pixd = pixaDisplayTiledInColumns(pixa, 1, 1.0, 30, 2);
+ pixDisplayWithTitle(pixd, 0, 0, NULL, rp->display);
+ ptaaDestroy(&ptaafg);
+ ptaaDestroy(&ptaabg);
+ pixDestroy(&pixs);
+ pixDestroy(&pixfg);
+ pixDestroy(&pixbg);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ boxaDestroy(&boxa);
+
+ /* Test rotation */
+ pix1 = pixRead("feyn-word.tif");
+ pix2 = pixAddBorderGeneral(pix1, 200, 200, 200, 200, 0);
+ pixa = pixaCreate(0);
+ pix3 = PtaDisplayRotate(pix2, 0, 0);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix3 = PtaDisplayRotate(pix2, 500, 100);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix3 = PtaDisplayRotate(pix2, 100, 410);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix3 = PtaDisplayRotate(pix2, 500, 410);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix4 = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 30, 2);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 13 */
+ pixDisplayWithTitle(pix4, 800, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix4);
+ pixaDestroy(&pixa);
+
+ /* Test pta sort and pta equality */
+ pix1 = pixRead("feyn-word.tif");
+ pixGetDimensions(pix1, &w, &h, NULL);
+ pta1 = ptaGetPixelsFromPix(pix1, NULL);
+ ptaGetIPt(pta1, 0, &x, &y); /* add copy of first point */
+ ptaAddPt(pta1, x, y);
+ pta2 = ptaCyclicPerm(pta1, x, y); /* first/last points must be the same */
+ ptaEqual(pta1, pta2, &same);
+ regTestCompareValues(rp, same, 1, 0.0); /* 14 */
+ pta3 = ptaReverse(pta2, 1);
+ ptaEqual(pta1, pta3, &same);
+ regTestCompareValues(rp, same, 1, 0.0); /* 15 */
+ pixDestroy(&pix1);
+ ptaDestroy(&pta1);
+ ptaDestroy(&pta2);
+ ptaDestroy(&pta3);
+
+ return regTestCleanup(rp);
+}
+
+
+static PIX *
+PtaDisplayRotate(PIX *pixs,
+ l_float32 xc,
+ l_float32 yc)
+{
+l_int32 i, w, h;
+PIX *pix1, *pix2;
+PTA *pta1, *pta2, *pta3, *pta4;
+PTAA *ptaa;
+
+ /* Save rotated sets of pixels */
+ pta1 = ptaGetPixelsFromPix(pixs, NULL);
+ ptaa = ptaaCreate(0);
+ for (i = 0; i < 9; i++) {
+ pta2 = ptaRotate(pta1, xc, yc, -0.8 + 0.2 * i);
+ ptaaAddPta(ptaa, pta2, L_INSERT);
+ }
+ ptaDestroy(&pta1);
+
+ /* Render them */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pix1 = pixCreate(w, h, 32);
+ pixSetAll(pix1);
+ pta3 = generatePtaFilledCircle(4);
+ pta4 = ptaTranslate(pta3, xc, yc);
+ pixRenderPtaArb(pix1, pta4, 255, 0, 0); /* circle at rotation center */
+ pix2 = pixDisplayPtaa(pix1, ptaa); /* rotated sets */
+
+ pixDestroy(&pix1);
+ ptaDestroy(&pta3);
+ ptaDestroy(&pta4);
+ ptaaDestroy(&ptaa);
+ return pix2;
+}
diff --git a/leptonica/prog/ptra1_reg.c b/leptonica/prog/ptra1_reg.c
new file mode 100644
index 00000000..defaf0f9
--- /dev/null
+++ b/leptonica/prog/ptra1_reg.c
@@ -0,0 +1,488 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * ptra1_reg.c
+ *
+ * Testing basic ptra operations
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static void MakePtrasFromPixa(PIXA *pixa, L_PTRA **ppapix, L_PTRA **ppabox,
+ l_int32 copyflag);
+static PIXA *ReconstructPixa1(L_REGPARAMS *rp, L_PTRA *papix, L_PTRA *pabox);
+static PIXA *ReconstructPixa2(L_REGPARAMS *rp, L_PTRA *papix, L_PTRA *pabox);
+static PIX *SaveResult(PIXA *pixac, PIXA **ppixa, l_int32 w, l_int32 h,
+ l_int32 newline);
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, n, w, h, nactual, imax;
+BOX *box;
+BOXA *boxa;
+PIX *pixs, *pixd, *pix;
+PIXA *pixas, *pixa1, *pixa2, *pixac1, *pixac2;
+L_PTRA *papix, *pabox, *papix2, *pabox2;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixac1 = pixaCreate(0);
+ pixac2 = pixaCreate(0);
+
+ pixs = pixRead("lucasta.1.300.tif");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ boxa = pixConnComp(pixs, &pixas, 8);
+ pixDestroy(&pixs);
+ boxaDestroy(&boxa);
+ n = pixaGetCount(pixas);
+
+ /* Fill ptras with clones and reconstruct */
+ if (rp->display)
+ lept_stderr("Fill with clones and reconstruct\n");
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_CLONE);
+ pixa1 = ReconstructPixa1(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_CLONE);
+ pixa2 = ReconstructPixa2(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac1, &pixa1, w, h, 1);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 0 */
+ pixDestroy(&pixd);
+ pixd = SaveResult(pixac2, &pixa2, w, h, 1);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 1 */
+ pixDestroy(&pixd);
+
+ /* Remove every other one for the first half;
+ * with compaction at each removal */
+ if (rp->display)
+ lept_stderr("Remove every other in 1st half, with compaction\n");
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_COPY);
+ for (i = 0; i < n / 2; i++) {
+ if (i % 2 == 0) {
+ pix = (PIX *)ptraRemove(papix, i, L_COMPACTION);
+ box = (BOX *)ptraRemove(pabox, i, L_COMPACTION);
+ pixDestroy(&pix);
+ boxDestroy(&box);
+ }
+ }
+ pixa1 = ReconstructPixa1(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac1, &pixa1, w, h, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 2 */
+ pixDestroy(&pixd);
+
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_COPY);
+ for (i = 0; i < n / 2; i++) {
+ if (i % 2 == 0) {
+ pix = (PIX *)ptraRemove(papix, i, L_COMPACTION);
+ box = (BOX *)ptraRemove(pabox, i, L_COMPACTION);
+ pixDestroy(&pix);
+ boxDestroy(&box);
+ }
+ }
+ pixa2 = ReconstructPixa2(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac2, &pixa2, w, h, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 3 */
+ pixDestroy(&pixd);
+
+ /* Remove every other one for the entire set,
+ * but without compaction at each removal */
+ if (rp->display)
+ lept_stderr("Remove every other in 1st half, "
+ "without & then with compaction\n");
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_COPY);
+ for (i = 0; i < n; i++) {
+ if (i % 2 == 0) {
+ pix = (PIX *)ptraRemove(papix, i, L_NO_COMPACTION);
+ box = (BOX *)ptraRemove(pabox, i, L_NO_COMPACTION);
+ pixDestroy(&pix);
+ boxDestroy(&box);
+ }
+ }
+ ptraCompactArray(papix); /* now do the compaction */
+ ptraCompactArray(pabox);
+ pixa1 = ReconstructPixa1(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac1, &pixa1, w, h, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 4 */
+ pixDestroy(&pixd);
+
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_COPY);
+ for (i = 0; i < n; i++) {
+ if (i % 2 == 0) {
+ pix = (PIX *)ptraRemove(papix, i, L_NO_COMPACTION);
+ box = (BOX *)ptraRemove(pabox, i, L_NO_COMPACTION);
+ pixDestroy(&pix);
+ boxDestroy(&box);
+ }
+ }
+ ptraCompactArray(papix); /* now do the compaction */
+ ptraCompactArray(pabox);
+ pixa2 = ReconstructPixa2(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac2, &pixa2, w, h, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 5 */
+ pixDestroy(&pixd);
+
+ /* Fill ptras using insert at head, and reconstruct */
+ if (rp->display)
+ lept_stderr("Insert at head and reconstruct\n");
+ papix = ptraCreate(n);
+ pabox = ptraCreate(n);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixas, i, L_CLONE);
+ box = pixaGetBox(pixas, i, L_CLONE);
+ ptraInsert(papix, 0, pix, L_MIN_DOWNSHIFT);
+ ptraInsert(pabox, 0, box, L_FULL_DOWNSHIFT);
+ }
+ pixa1 = ReconstructPixa1(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac1, &pixa1, w, h, 1);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 6 */
+ pixDestroy(&pixd);
+
+ papix = ptraCreate(n);
+ pabox = ptraCreate(n);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixas, i, L_CLONE);
+ box = pixaGetBox(pixas, i, L_CLONE);
+ ptraInsert(papix, 0, pix, L_MIN_DOWNSHIFT);
+ ptraInsert(pabox, 0, box, L_FULL_DOWNSHIFT);
+ }
+ pixa2 = ReconstructPixa2(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac2, &pixa2, w, h, 1);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 7 */
+ pixDestroy(&pixd);
+
+ /* Reverse the arrays by swapping */
+ if (rp->display)
+ lept_stderr("Reverse by swapping\n");
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_CLONE);
+ for (i = 0; i < n / 2; i++) {
+ ptraSwap(papix, i, n - i - 1);
+ ptraSwap(pabox, i, n - i - 1);
+ }
+ ptraCompactArray(papix); /* already compact; shouldn't do anything */
+ ptraCompactArray(pabox);
+ pixa1 = ReconstructPixa1(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac1, &pixa1, w, h, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 8 */
+ pixDestroy(&pixd);
+
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_CLONE);
+ for (i = 0; i < n / 2; i++) {
+ ptraSwap(papix, i, n - i - 1);
+ ptraSwap(pabox, i, n - i - 1);
+ }
+ ptraCompactArray(papix); /* already compact; shouldn't do anything */
+ ptraCompactArray(pabox);
+ pixa2 = ReconstructPixa2(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac2, &pixa2, w, h, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 9 */
+ pixDestroy(&pixd);
+
+ /* Remove at the top of the array and push the hole to the end
+ * by neighbor swapping (!). This is O(n^2), so it's not a
+ * recommended way to copy a ptra. [joke] */
+ if (rp->display)
+ lept_stderr(
+ "Remove at top, pushing hole to end by swapping -- O(n^2)\n");
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_CLONE);
+ papix2 = ptraCreate(0);
+ pabox2 = ptraCreate(0);
+ while (1) {
+ ptraGetActualCount(papix, &nactual);
+ if (nactual == 0) break;
+ ptraGetMaxIndex(papix, &imax);
+ pix = (PIX *)ptraRemove(papix, 0, L_NO_COMPACTION);
+ box = (BOX *)ptraRemove(pabox, 0, L_NO_COMPACTION);
+ ptraAdd(papix2, pix);
+ ptraAdd(pabox2, box);
+ for (i = 1; i <= imax; i++) {
+ ptraSwap(papix, i - 1, i);
+ ptraSwap(pabox, i - 1, i);
+ }
+ }
+ ptraCompactArray(papix); /* should be empty */
+ ptraCompactArray(pabox); /* ditto */
+ pixa1 = ReconstructPixa1(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac1, &pixa1, w, h, 1); /* nothing there */
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 10 */
+ pixDestroy(&pixd);
+
+ pixa1 = ReconstructPixa1(rp, papix2, pabox2);
+ ptraDestroy(&papix2, 0, 1);
+ ptraDestroy(&pabox2, 0, 1);
+ pixd = SaveResult(pixac1, &pixa1, w, h, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 11 */
+ pixDestroy(&pixd);
+
+ /* Remove and insert one position above, allowing minimum downshift.
+ * If you specify L_AUTO_DOWNSHIFT, because there is only 1 hole,
+ * it will do a full downshift at each insert. This is a
+ * situation where the heuristic (expected number of holes)
+ * fails to do the optimal thing. */
+ if (rp->display)
+ lept_stderr("Remove and insert one position above (min downshift)\n");
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_CLONE);
+ for (i = 1; i < n; i++) {
+ pix = (PIX *)ptraRemove(papix, i, L_NO_COMPACTION);
+ box = (BOX *)ptraRemove(pabox, i, L_NO_COMPACTION);
+ ptraInsert(papix, i - 1, pix, L_MIN_DOWNSHIFT);
+ ptraInsert(pabox, i - 1, box, L_MIN_DOWNSHIFT);
+ }
+ pixa1 = ReconstructPixa1(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac1, &pixa1, w, h, 1);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 12 */
+ pixDestroy(&pixd);
+
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_CLONE);
+ for (i = 1; i < n; i++) {
+ pix = (PIX *)ptraRemove(papix, i, L_NO_COMPACTION);
+ box = (BOX *)ptraRemove(pabox, i, L_NO_COMPACTION);
+ ptraInsert(papix, i - 1, pix, L_MIN_DOWNSHIFT);
+ ptraInsert(pabox, i - 1, box, L_MIN_DOWNSHIFT);
+ }
+ pixa2 = ReconstructPixa2(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac2, &pixa2, w, h, 1);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 13 */
+ pixDestroy(&pixd);
+
+ /* Remove and insert one position above, but this time
+ * forcing a full downshift at each step. */
+ if (rp->display)
+ lept_stderr("Remove and insert one position above (full downshift)\n");
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_CLONE);
+ for (i = 1; i < n; i++) {
+ pix = (PIX *)ptraRemove(papix, i, L_NO_COMPACTION);
+ box = (BOX *)ptraRemove(pabox, i, L_NO_COMPACTION);
+ ptraInsert(papix, i - 1, pix, L_AUTO_DOWNSHIFT);
+ ptraInsert(pabox, i - 1, box, L_AUTO_DOWNSHIFT);
+ }
+ pixa1 = ReconstructPixa1(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac1, &pixa1, w, h, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 14 */
+ pixDestroy(&pixd);
+
+ MakePtrasFromPixa(pixas, &papix, &pabox, L_CLONE);
+ for (i = 1; i < n; i++) {
+ pix = (PIX *)ptraRemove(papix, i, L_NO_COMPACTION);
+ box = (BOX *)ptraRemove(pabox, i, L_NO_COMPACTION);
+ ptraInsert(papix, i - 1, pix, L_AUTO_DOWNSHIFT);
+ ptraInsert(pabox, i - 1, box, L_AUTO_DOWNSHIFT);
+ }
+ pixa2 = ReconstructPixa2(rp, papix, pabox);
+ ptraDestroy(&papix, 0, 1);
+ ptraDestroy(&pabox, 0, 1);
+ pixd = SaveResult(pixac2, &pixa2, w, h, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 15 */
+ pixDestroy(&pixd);
+
+ pixd = pixaDisplayTiledInColumns(pixac1, 10, 0.5, 15, 2);
+ pixDisplayWithTitle(pixd, 0, 100, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 16 */
+ pixDestroy(&pixd);
+ pixd = pixaDisplayTiledInColumns(pixac2, 10, 0.5, 15, 2);
+ pixDisplayWithTitle(pixd, 800, 100, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 17 */
+ pixDestroy(&pixd);
+ pixaDestroy(&pixac1);
+ pixaDestroy(&pixac2);
+ pixaDestroy(&pixas);
+
+ return regTestCleanup(rp);
+}
+
+
+static void
+MakePtrasFromPixa(PIXA *pixa,
+ L_PTRA **ppapix,
+ L_PTRA **ppabox,
+ l_int32 copyflag)
+{
+l_int32 i, n;
+BOX *box;
+PIX *pix;
+L_PTRA *papix, *pabox;
+
+ n = pixaGetCount(pixa);
+ papix = ptraCreate(n);
+ pabox = ptraCreate(n);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixa, i, copyflag);
+ box = pixaGetBox(pixa, i, copyflag);
+ ptraAdd(papix, pix);
+ ptraAdd(pabox, box);
+ }
+
+ *ppapix = papix;
+ *ppabox = pabox;
+ return;
+}
+
+
+ /* Reconstruction without compaction */
+static PIXA *
+ReconstructPixa1(L_REGPARAMS *rp,
+ L_PTRA *papix,
+ L_PTRA *pabox)
+{
+l_int32 i, imax, nactual;
+BOX *box;
+PIX *pix;
+PIXA *pixat;
+
+ ptraGetMaxIndex(papix, &imax);
+ ptraGetActualCount(papix, &nactual);
+ if (rp->display)
+ lept_stderr("Before removal: imax = %4d, actual = %4d\n",
+ imax, nactual);
+
+ pixat = pixaCreate(imax + 1);
+ for (i = 0; i <= imax; i++) {
+ pix = (PIX *)ptraRemove(papix, i, L_NO_COMPACTION);
+ box = (BOX *)ptraRemove(pabox, i, L_NO_COMPACTION);
+ if (pix) pixaAddPix(pixat, pix, L_INSERT);
+ if (box) pixaAddBox(pixat, box, L_INSERT);
+ }
+
+ ptraGetMaxIndex(papix, &imax);
+ ptraGetActualCount(papix, &nactual);
+ if (rp->display)
+ lept_stderr("After removal: imax = %4d, actual = %4d\n\n",
+ imax, nactual);
+
+ return pixat;
+}
+
+
+ /* Reconstruction with compaction */
+static PIXA *
+ReconstructPixa2(L_REGPARAMS *rp,
+ L_PTRA *papix,
+ L_PTRA *pabox)
+{
+l_int32 i, imax, nactual;
+BOX *box;
+PIX *pix;
+PIXA *pixat;
+
+ ptraGetMaxIndex(papix, &imax);
+ ptraGetActualCount(papix, &nactual);
+ if (rp->display)
+ lept_stderr("Before removal: imax = %4d, actual = %4d\n",
+ imax, nactual);
+
+ /* Remove half */
+ pixat = pixaCreate(imax + 1);
+ for (i = 0; i <= imax; i++) {
+ if (i % 2 == 0) {
+ pix = (PIX *)ptraRemove(papix, i, L_NO_COMPACTION);
+ box = (BOX *)ptraRemove(pabox, i, L_NO_COMPACTION);
+ if (pix) pixaAddPix(pixat, pix, L_INSERT);
+ if (box) pixaAddBox(pixat, box, L_INSERT);
+ }
+ }
+
+ /* Compact */
+ ptraGetMaxIndex(papix, &imax);
+ ptraGetActualCount(papix, &nactual);
+ if (rp->display)
+ lept_stderr("Before compaction: imax = %4d, actual = %4d\n",
+ imax, nactual);
+ ptraCompactArray(papix);
+ ptraCompactArray(pabox);
+ ptraGetMaxIndex(papix, &imax);
+ ptraGetActualCount(papix, &nactual);
+ if (rp->display)
+ lept_stderr("After compaction: imax = %4d, actual = %4d\n",
+ imax, nactual);
+
+ /* Remove the rest (and test compaction with removal) */
+ while (1) {
+ ptraGetActualCount(papix, &nactual);
+ if (nactual == 0) break;
+
+ pix = (PIX *)ptraRemove(papix, 0, L_COMPACTION);
+ box = (BOX *)ptraRemove(pabox, 0, L_COMPACTION);
+ pixaAddPix(pixat, pix, L_INSERT);
+ pixaAddBox(pixat, box, L_INSERT);
+ }
+
+ ptraGetMaxIndex(papix, &imax);
+ ptraGetActualCount(papix, &nactual);
+ if (rp->display)
+ lept_stderr("After removal: imax = %4d, actual = %4d\n\n",
+ imax, nactual);
+
+ return pixat;
+}
+
+
+PIX *
+SaveResult(PIXA *pixac,
+ PIXA **ppixa,
+ l_int32 w,
+ l_int32 h,
+ l_int32 newline)
+{
+PIX *pixd;
+
+ pixd = pixaDisplay(*ppixa, w, h);
+ pixaAddPix(pixac, pixd, L_COPY);
+ pixaDestroy(ppixa);
+ return pixd;
+}
diff --git a/leptonica/prog/ptra2_reg.c b/leptonica/prog/ptra2_reg.c
new file mode 100644
index 00000000..e823265f
--- /dev/null
+++ b/leptonica/prog/ptra2_reg.c
@@ -0,0 +1,266 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * ptra2_reg.c
+ *
+ * Testing:
+ * - basic ptra and ptraa operations
+ * - bin sort using ptra
+ * - boxaEqual() and pixaEqual()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void BoxaSortTest(L_REGPARAMS *rp, const char *fname, l_int32 index,
+ const char *text);
+void PixaSortTest(L_REGPARAMS *rp, const char *fname, l_int32 index,
+ const char *text);
+
+int main(int argc,
+ char **argv)
+{
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "ptra2_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/ptra");
+
+ /* 0 - 8 */
+ BoxaSortTest(rp, "feyn-fract.tif", 1, "Boxa sort test on small image");
+ /* 9 - 17 */
+ BoxaSortTest(rp, "feyn.tif", 2, "Boxa sort test on large image");
+ /* 18 - 27 */
+ PixaSortTest(rp, "feyn-fract.tif", 3, "Pixa sort test on small image");
+ /* 28 - 37 */
+ PixaSortTest(rp, "feyn.tif", 4, "Pixa sort test on large image");
+ return regTestCleanup(rp);
+}
+
+
+void
+BoxaSortTest(L_REGPARAMS *rp,
+ const char *fname,
+ l_int32 index,
+ const char *text)
+{
+l_int32 i, n, m, imax, w, h, x, count, same;
+BOX *box;
+BOXA *boxa, *boxa1, *boxa2, *boxa3;
+NUMA *na, *nad1, *nad2, *nad3, *naindex;
+PIX *pixs;
+L_PTRA *pa, *pad, *paindex;
+L_PTRAA *paa;
+char buf[256];
+
+ lept_stderr("Test %d: %s\n", index, text);
+ pixs = pixRead(fname);
+ boxa = pixConnComp(pixs, NULL, 8);
+
+ /* Sort by x */
+ boxa1 = boxaSort(boxa, L_SORT_BY_X, L_SORT_INCREASING, &nad1);
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/boxa1.%d.ba", index);
+ boxaWrite(buf, boxa1);
+ regTestCheckFile(rp, buf); /* 0 */
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/nad1.%d.na", index);
+ numaWrite(buf, nad1);
+ regTestCheckFile(rp, buf); /* 1 */
+
+ boxa2 = boxaBinSort(boxa, L_SORT_BY_X, L_SORT_INCREASING, &nad2);
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/boxa2.%d.ba", index);
+ boxaWrite(buf, boxa2);
+ regTestCheckFile(rp, buf); /* 2 */
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/nad2.%d.na", index);
+ numaWrite(buf, nad2);
+ regTestCheckFile(rp, buf); /* 3 */
+
+ boxaEqual(boxa1, boxa2, 0, NULL, &same);
+ regTestCompareValues(rp, 1, same, 0.0); /* 4 */
+ if (rp->display && same)
+ lept_stderr("boxa1 and boxa2 are identical\n");
+ boxaEqual(boxa1, boxa2, 2, &naindex, &same);
+ regTestCompareValues(rp, 1, same, 0.0); /* 5 */
+ if (rp->display && same)
+ lept_stderr("boxa1 and boxa2 are same at maxdiff = 2\n");
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/naindex.%d.na", index);
+ numaWrite(buf, naindex);
+ regTestCheckFile(rp, buf); /* 6 */
+ numaDestroy(&naindex);
+ boxaDestroy(&boxa1);
+ numaDestroy(&nad1);
+ numaDestroy(&nad2);
+
+ /* Now do this stuff with ptra and ptraa */
+ /* First, store the boxes in a ptraa, where each ptra contains
+ * the boxes, and store the sort index in a ptra of numa */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ paa = ptraaCreate(w);
+ paindex = ptraCreate(w);
+ n = boxaGetCount(boxa);
+ if (rp->display) lept_stderr("n = %d\n", n);
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxa, i, L_CLONE);
+ boxGetGeometry(box, &x, NULL, NULL, NULL);
+ pa = ptraaGetPtra(paa, x, L_HANDLE_ONLY);
+ na = (NUMA *)ptraGetPtrToItem(paindex, x);
+ if (!pa) { /* na also needs to be made */
+ pa = ptraCreate(1);
+ ptraaInsertPtra(paa, x, pa);
+ na = numaCreate(1);
+ ptraInsert(paindex, x, na, L_MIN_DOWNSHIFT);
+ }
+ ptraAdd(pa, box);
+ numaAddNumber(na, i);
+ }
+ ptraGetActualCount(paindex, &count);
+ if (rp->display) lept_stderr("count = %d\n", count);
+
+ /* Flatten the ptraa to a ptra containing all the boxes
+ * in sorted order, and put them in a boxa */
+ pad = ptraaFlattenToPtra(paa);
+ ptraaDestroy(&paa, FALSE, FALSE);
+ ptraGetActualCount(pad, &m);
+ if (m != n)
+ lept_stderr("n(orig) = %d, m(new) = %d\n", n, m);
+ boxa3 = boxaCreate(m);
+ for (i = 0; i < m; i++) {
+ box = (BOX *)ptraRemove(pad, i, L_NO_COMPACTION);
+ boxaAddBox(boxa3, box, L_INSERT);
+ }
+ ptraDestroy(&pad, FALSE, FALSE);
+
+ /* Extract the data from the ptra of Numa, putting it into
+ * a single Numa */
+ ptraGetMaxIndex(paindex, &imax);
+ nad3 = numaCreate(0);
+ if (rp->display) lept_stderr("imax = %d\n\n", imax);
+ for (i = 0; i <= imax; i++) {
+ na = (NUMA *)ptraRemove(paindex, i, L_NO_COMPACTION);
+ numaJoin(nad3, na, 0, -1);
+ numaDestroy(&na);
+ }
+
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/boxa3.%d.ba", index);
+ boxaWrite(buf, boxa3);
+ regTestCheckFile(rp, buf); /* 7 */
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/nad3.%d.na", index);
+ numaWrite(buf, nad3);
+ regTestCheckFile(rp, buf); /* 8 */
+
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+ numaDestroy(&nad3);
+ ptraDestroy(&paindex, FALSE, FALSE);
+ pixDestroy(&pixs);
+ boxaDestroy(&boxa);
+ return;
+}
+
+
+void
+PixaSortTest(L_REGPARAMS *rp,
+ const char *fname,
+ l_int32 index,
+ const char *text)
+{
+l_int32 same;
+BOXA *boxa, *boxa1, *boxa2;
+NUMA *nap1, *nap2, *naindex;
+PIX *pixs;
+PIXA *pixa, *pixa1, *pixa2;
+char buf[256];
+
+ lept_stderr("Test %d: %s\n", index, text);
+ pixs = pixRead(fname);
+ boxa = pixConnComp(pixs, &pixa, 8);
+
+ pixa1 = pixaSort(pixa, L_SORT_BY_X, L_SORT_INCREASING, &nap1, L_CLONE);
+ boxa1 = pixaGetBoxa(pixa1, L_CLONE);
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/bap1.%d.ba", index);
+ boxaWrite(buf, boxa1);
+ regTestCheckFile(rp, buf); /* 0 */
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/nap1.%d.na", index);
+ numaWrite(buf, nap1);
+ regTestCheckFile(rp, buf); /* 1 */
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/pixa1.%d.pa", index);
+ pixaWrite(buf, pixa1);
+ regTestCheckFile(rp, buf); /* 2 */
+
+ pixa2 = pixaBinSort(pixa, L_SORT_BY_X, L_SORT_INCREASING, &nap2, L_CLONE);
+ boxa2 = pixaGetBoxa(pixa2, L_CLONE);
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/bap2.%d.ba", index);
+ boxaWrite(buf, boxa2);
+ regTestCheckFile(rp, buf); /* 3 */
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/nap2.%d.na", index);
+ numaWrite(buf, nap2);
+ regTestCheckFile(rp, buf); /* 4 */
+ snprintf(buf, sizeof(buf), "/tmp/lept/ptra/pixa2.%d.pa", index);
+ pixaWrite(buf, pixa2);
+ regTestCheckFile(rp, buf); /* 5 */
+
+ boxaEqual(boxa1, boxa2, 0, &naindex, &same);
+ regTestCompareValues(rp, 1, same, 0.0); /* 6 */
+ if (rp->display && same)
+ lept_stderr("boxa1 and boxa2 are identical\n");
+ numaDestroy(&naindex);
+ boxaEqual(boxa1, boxa2, 3, &naindex, &same);
+ regTestCompareValues(rp, 1, same, 0.0); /* 7 */
+ if (rp->display && same)
+ lept_stderr("boxa1 and boxa2 are same at maxdiff = 2\n");
+ numaDestroy(&naindex);
+
+ pixaEqual(pixa1, pixa2, 0, &naindex, &same);
+ regTestCompareValues(rp, 1, same, 0.0); /* 8 */
+ if (rp->display && same)
+ lept_stderr("pixa1 and pixa2 are identical\n");
+ numaDestroy(&naindex);
+ pixaEqual(pixa1, pixa2, 3, &naindex, &same);
+ regTestCompareValues(rp, 1, same, 0.0); /* 9 */
+ if (rp->display && same)
+ lept_stderr("pixa1 and pixa2 are same at maxdiff = 2\n\n");
+ numaDestroy(&naindex);
+
+ boxaDestroy(&boxa);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ numaDestroy(&nap1);
+ numaDestroy(&nap2);
+ pixaDestroy(&pixa);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixDestroy(&pixs);
+ return;
+}
diff --git a/leptonica/prog/quadtree_reg.c b/leptonica/prog/quadtree_reg.c
new file mode 100644
index 00000000..e5ef57f5
--- /dev/null
+++ b/leptonica/prog/quadtree_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * quadtreetest.c
+ *
+ * This tests quadtree statistical functions
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_uint8 *data;
+l_int32 i, j, w, h, error;
+l_float32 val1, val2;
+l_float32 val00, val10, val01, val11, valc00, valc10, valc01, valc11;
+size_t size;
+PIX *pixs, *pixg, *pix1, *pix2, *pix3, *pix4, *pix5;
+FPIXA *fpixam, *fpixav, *fpixarv;
+BOXAA *baa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "quadtree_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/quad");
+
+ /* Test generation of quadtree regions. */
+ baa = boxaaQuadtreeRegions(1000, 500, 3);
+ boxaaWriteMem(&data, &size, baa);
+ regTestWriteDataAndCheck(rp, data, size, "baa"); /* 0 */
+ if (rp->display) boxaaWriteStream(stderr, baa);
+ boxaaDestroy(&baa);
+ lept_free(data);
+ baa = boxaaQuadtreeRegions(1001, 501, 3);
+ boxaaWriteMem(&data, &size, baa);
+ regTestWriteDataAndCheck(rp, data, size, "baa"); /* 1 */
+ boxaaDestroy(&baa);
+ lept_free(data);
+
+ /* Test quadtree stats generation */
+ pixs = pixRead("rabi.png");
+ pixg = pixScaleToGray4(pixs);
+ pixDestroy(&pixs);
+ pixQuadtreeMean(pixg, 8, NULL, &fpixam);
+ pix1 = fpixaDisplayQuadtree(fpixam, 2, 10);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix1, 100, 0, NULL, rp->display);
+ pixQuadtreeVariance(pixg, 8, NULL, NULL, &fpixav, &fpixarv);
+ pix2 = fpixaDisplayQuadtree(fpixav, 2, 10);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pix2, 100, 200, NULL, rp->display);
+ pix3 = fpixaDisplayQuadtree(fpixarv, 2, 10);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix3, 100, 400, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ fpixaDestroy(&fpixav);
+ fpixaDestroy(&fpixarv);
+
+ /* Compare with fixed-size tiling at a resolution corresponding
+ * to the deepest level of the quadtree above */
+ pix4 = pixGetAverageTiled(pixg, 5, 6, L_MEAN_ABSVAL);
+ pix5 = pixExpandReplicate(pix4, 4);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix5, 800, 0, NULL, rp->display);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pix4 = pixGetAverageTiled(pixg, 5, 6, L_STANDARD_DEVIATION);
+ pix5 = pixExpandReplicate(pix4, 4);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pix5, 800, 400, NULL, rp->display);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pixg);
+
+ /* Test quadtree parent/child access */
+ error = FALSE;
+ fpixaGetFPixDimensions(fpixam, 4, &w, &h);
+ for (i = 0; i < w; i += 2) {
+ for (j = 0; j < h; j += 2) {
+ quadtreeGetParent(fpixam, 4, j, i, &val1);
+ fpixaGetPixel(fpixam, 3, j / 2, i / 2, &val2);
+ if (val1 != val2) error = TRUE;
+ }
+ }
+ regTestCompareValues(rp, 0, error, 0.0); /* 7 */
+ error = FALSE;
+ for (i = 0; i < w; i++) {
+ for (j = 0; j < h; j++) {
+ quadtreeGetChildren(fpixam, 4, j, i,
+ &val00, &val10, &val01, &val11);
+ fpixaGetPixel(fpixam, 5, 2 * j, 2 * i, &valc00);
+ fpixaGetPixel(fpixam, 5, 2 * j + 1, 2 * i, &valc10);
+ fpixaGetPixel(fpixam, 5, 2 * j, 2 * i + 1, &valc01);
+ fpixaGetPixel(fpixam, 5, 2 * j + 1, 2 * i + 1, &valc11);
+ if ((val00 != valc00) || (val10 != valc10) ||
+ (val01 != valc01) || (val11 != valc11))
+ error = TRUE;
+ }
+ }
+ regTestCompareValues(rp, 0, error, 0.0); /* 8 */
+ fpixaDestroy(&fpixam);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/rabi-tiny.png b/leptonica/prog/rabi-tiny.png
new file mode 100644
index 00000000..faf2b35d
--- /dev/null
+++ b/leptonica/prog/rabi-tiny.png
Binary files differ
diff --git a/leptonica/prog/rabi.png b/leptonica/prog/rabi.png
new file mode 100644
index 00000000..b4e41a10
--- /dev/null
+++ b/leptonica/prog/rabi.png
Binary files differ
diff --git a/leptonica/prog/raggededge.png b/leptonica/prog/raggededge.png
new file mode 100644
index 00000000..d4c134fa
--- /dev/null
+++ b/leptonica/prog/raggededge.png
Binary files differ
diff --git a/leptonica/prog/rank_reg.c b/leptonica/prog/rank_reg.c
new file mode 100644
index 00000000..ca0b4d80
--- /dev/null
+++ b/leptonica/prog/rank_reg.c
@@ -0,0 +1,226 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Tests grayscale and color rank functions:
+ * (1) pixRankFilterGray()
+ * (1) pixRankFilterRGB()
+ * (2) pixScaleGrayMinMax()
+ * (3) pixScaleGrayRank2()
+ * (3) pixScaleGrayRankCascade()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, w, h;
+l_float32 t1, t2;
+BOX *box;
+GPLOT *gplot;
+NUMA *nax, *nay1, *nay2;
+PIX *pixs, *pix0, *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "rank_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/rank");
+
+ pixs = pixRead("lucasta.150.jpg");
+ pixGetDimensions(pixs, &w, &h, NULL);
+
+ startTimer();
+ pix1 = pixRankFilterGray(pixs, 15, 15, 0.4);
+ t1 = stopTimer();
+ lept_stderr("pixRankFilterGray: %7.3f MPix/sec\n", 0.000001 * w * h / t1);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pixs, 0, 0, NULL, rp->display);
+ pixDisplayWithTitle(pix1, 600, 0, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ /* ---------- Compare grayscale morph with rank operator ---------- */
+ /* Get results for dilation */
+ startTimer();
+ pix1 = pixDilateGray(pixs, 15, 15);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1 */
+ t2 = stopTimer();
+ lept_stderr("Rank filter time = %7.3f, Dilation time = %7.3f sec\n",
+ t1, t2);
+
+ /* Get results for erosion */
+ pix2 = pixErodeGray(pixs, 15, 15);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 2 */
+
+ /* Get results using the rank filter for rank = 0.0 and 1.0.
+ * Don't use 0.0 or 1.0, because those are dispatched
+ * automatically to erosion and dilation! */
+ pix3 = pixRankFilterGray(pixs, 15, 15, 0.0001);
+ pix4 = pixRankFilterGray(pixs, 15, 15, 0.9999);
+ regTestComparePix(rp, pix1, pix4); /* 3 */
+ regTestComparePix(rp, pix2, pix3); /* 4 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* ------------- Timing and filter size experiments --------- */
+ box = boxCreate(20, 200, 500, 125);
+ pix0 = pixClipRectangle(pixs, box, NULL);
+ boxDestroy(&box);
+ nax = numaMakeSequence(1, 1, 20);
+ nay1 = numaCreate(20);
+ nay2 = numaCreate(20);
+ gplot = gplotCreate("/tmp/lept/rank/plots", GPLOT_PNG,
+ "sec/MPix vs filter size", "size", "time");
+ pixa = pixaCreate(20);
+ for (i = 1; i <= 20; i++) {
+ t1 = t2 = 0.0;
+ for (j = 0; j < 5; j++) {
+ startTimer();
+ pix1 = pixRankFilterGray(pix0, i, 20 + 1, 0.5);
+ t1 += stopTimer();
+ pixDestroy(&pix1);
+ startTimer();
+ pix1 = pixRankFilterGray(pix0, 20 + 1, i, 0.5);
+ t2 += stopTimer();
+ if (j == 0)
+ pixaAddPix(pixa, pix1, L_CLONE);
+ pixDestroy(&pix1);
+ }
+ numaAddNumber(nay1, 1000000. * t1 / (5. * w * h));
+ numaAddNumber(nay2, 1000000. * t2 / (5. * w * h));
+ }
+ gplotAddPlot(gplot, nax, nay1, GPLOT_LINES, "vertical");
+ gplotAddPlot(gplot, nax, nay2, GPLOT_LINES, "horizontal");
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ pix1 = pixRead("/tmp/lept/rank/plots.png");
+ pixDisplayWithTitle(pix1, 100, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix0);
+ numaDestroy(&nax);
+ numaDestroy(&nay1);
+ numaDestroy(&nay2);
+
+ /* Display tiled */
+ pix1 = pixaDisplayTiledAndScaled(pixa, 8, 250, 5, 0, 25, 2);
+ pixDisplayWithTitle(pix1, 100, 600, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs);
+
+ /* ------------------ Gray tests ------------------ */
+ pixs = pixRead("test8.jpg");
+ pixa = pixaCreate(4);
+ for (i = 1; i <= 4; i++) {
+ pix1 = pixScaleGrayRank2(pixs, i);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ }
+ pix1 = pixaDisplayTiledInRows(pixa, 8, 1500, 1.0, 0, 20, 2);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 5 */
+ pixDisplayWithTitle(pix1, 100, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa);
+
+ pixs = pixRead("test24.jpg");
+ pix1 = pixConvertRGBToLuminance(pixs);
+ pix2 = pixScale(pix1, 1.5, 1.5);
+ pixa = pixaCreate(5);
+ for (i = 1; i <= 4; i++) {
+ for (j = 1; j <= 4; j++) {
+ pix3 = pixScaleGrayRankCascade(pix2, i, j, 0, 0);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ }
+ }
+ pix4 = pixaDisplayTiledInRows(pixa, 8, 1500, 0.7, 0, 20, 2);
+ regTestWritePixAndCheck(rp, pix4, IFF_JFIF_JPEG); /* 6 */
+ pixDisplayWithTitle(pix4, 100, 700, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix4);
+ pixaDestroy(&pixa);
+
+ /* ---------- Compare color morph with rank operator ---------- */
+ pixs = pixRead("wyom.jpg");
+ box = boxCreate(400, 220, 300, 250);
+ pix0 = pixClipRectangle(pixs, box, NULL);
+ boxDestroy(&box);
+ pix1 = pixColorMorph(pix0, L_MORPH_DILATE, 11, 11);
+ pix2 = pixColorMorph(pix0, L_MORPH_ERODE, 11, 11);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 7 */
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 8 */
+
+ /* Get results using the rank filter for rank = 0.0 and 1.0.
+ * Don't use 0.0 or 1.0, because those are dispatched
+ * automatically to erosion and dilation! */
+ pix3 = pixRankFilter(pix0, 11, 11, 0.0001);
+ pix4 = pixRankFilter(pix0, 11, 11, 0.9999);
+ regTestComparePix(rp, pix1, pix4); /* 9 */
+ regTestComparePix(rp, pix2, pix3); /* 10 */
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* Show color results for different rank values */
+ if (rp->display) {
+ pixa = pixaCreate(10);
+ pix1 = pixColorMorph(pix0, L_MORPH_ERODE, 13, 13);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ for (i = 0; i <= 10; i++) {
+ pix1 = pixRankFilter(pix0, 13, 13, 0.1 * i);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ }
+ pix1 = pixColorMorph(pix0, L_MORPH_DILATE, 13, 13);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = pixaDisplayTiledAndScaled(pixa, 32, 400, 3, 0, 25, 2);
+ pixDisplayWithTitle(pix1, 500, 0, NULL, 1);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+ }
+ pixDestroy(&pix0);
+
+ return regTestCleanup(rp);
+}
+
+
diff --git a/leptonica/prog/rankbin_reg.c b/leptonica/prog/rankbin_reg.c
new file mode 100644
index 00000000..4f02956c
--- /dev/null
+++ b/leptonica/prog/rankbin_reg.c
@@ -0,0 +1,204 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * rankbin_reg.c
+ *
+ * Tests rank bin functions:
+ * (1) numaDiscretizeInBins()
+ * (2) numaGetRankBinValues()
+ * (3) pixRankBinByStrip()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, n, w, h, nbins;
+l_uint8 *data;
+l_uint32 *carray;
+size_t nbytes;
+BOXA *boxa1, *boxa2, *boxa3;
+NUMA *naindex, *na1, *na2, *na3, *na4;
+PIX *pixs, *pix1, *pix2, *pix3;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "rankbin_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Generate arrays of word widths and heights */
+ pixs = pixRead("feyn.tif");
+ pix1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
+ pixGetWordBoxesInTextlines(pix1, 6, 6, 500, 50, &boxa1, &naindex);
+ n = boxaGetCount(boxa1);
+ na1 = numaCreate(0);
+ na2 = numaCreate(0);
+ for (i = 0; i < n; i++) {
+ boxaGetBoxGeometry(boxa1, i, NULL, NULL, &w, &h);
+ numaAddNumber(na1, w);
+ numaAddNumber(na2, h);
+ }
+ boxaDestroy(&boxa1);
+ numaDestroy(&naindex);
+ pixDestroy(&pix1);
+
+ /* Make the rank bin arrays of average values, with 10 bins */
+ pixa = pixaCreate(0);
+ numaGetRankBinValues(na1, 10, &na3);
+ numaGetRankBinValues(na2, 10, &na4);
+ pix1 = gplotSimplePix1(na3, "width vs rank bins (10)");
+ pix2 = gplotSimplePix1(na4, "height vs rank bins (10)");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ /* Make the rank bin arrays of average values, with 30 bins */
+ numaGetRankBinValues(na1, 30, &na3);
+ numaGetRankBinValues(na2, 30, &na4);
+ pix1 = gplotSimplePix1(na3, "width vs rank bins (30)");
+ pix2 = gplotSimplePix1(na4, "height vs rank bins (30)");
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 3 */
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ /* Display results for debugging */
+ if (rp->display) {
+ pix3 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 25, 0);
+ pixDisplayWithTitle(pix3, 0, 0, NULL, 1);
+ pixDestroy(&pix3);
+ }
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+
+ /* Test pixRankBinByStrip */
+ pix1 = pixRead("pancrazi.15.jpg");
+ pixa = pixaCreate(3);
+ pix2 = pixRankBinByStrip(pix1, L_SCAN_HORIZONTAL, 16, 10, L_SELECT_HUE);
+ pix3 = pixExpandReplicate(pix2, 20);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ pix2 = pixRankBinByStrip(pix1, L_SCAN_HORIZONTAL, 16, 10,
+ L_SELECT_SATURATION);
+ pix3 = pixExpandReplicate(pix2, 20);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ pix2 = pixRankBinByStrip(pix1, L_SCAN_HORIZONTAL, 16, 10, L_SELECT_RED);
+ pix3 = pixExpandReplicate(pix2, 20);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ pix2 = pixaDisplayTiledInRows(pixa, 32, 800, 1.0, 0, 30, 2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix2, 100, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa);
+
+ /* Test numaGetRankBinValues() and numaDiscretize functions */
+ boxa1 = boxaRead("boxa4.ba");
+ boxaSplitEvenOdd(boxa1, 0, &boxa2, &boxa3);
+ boxaGetSizes(boxa2, &na1, NULL); /* 26 elements */
+ numaWriteMem(&data, &nbytes, na1);
+ regTestWriteDataAndCheck(rp, data, nbytes, ".na"); /* 5 */
+ lept_free(data);
+ n = numaGetCount(na1);
+ nbins = L_MAX(5, n / 50);
+ numaGetRankBinValues(na1, nbins, &na2);
+ numaWriteMem(&data, &nbytes, na2);
+ regTestWriteDataAndCheck(rp, data, nbytes, ".na"); /* 6 */
+ lept_free(data);
+ numaDestroy(&na2);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+
+ na3 = numaSort(NULL, na1, L_SORT_INCREASING);
+ numaDiscretizeSortedInBins(na3, nbins, &na4);
+ numaWriteMem(&data, &nbytes, na4);
+ regTestWriteDataAndCheck(rp, data, nbytes, ".na"); /* 7 */
+ lept_free(data);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+
+ na3 = numaMakeHistogram(na1, 100000, NULL, NULL);
+ numaDiscretizeHistoInBins(na3, nbins, &na4, NULL);
+ numaWriteMem(&data, &nbytes, na4);
+ regTestWriteDataAndCheck(rp, data, nbytes, ".na"); /* 8 */
+ lept_free(data);
+ regTestCompareFiles(rp, 6, 7); /* 9 */
+ regTestCompareFiles(rp, 6, 8); /* 10 */
+ numaDestroy(&na1);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+
+ pixa = pixaCreate(4);
+ pix1 = pixRead("karen8.jpg");
+ na1 = pixGetGrayHistogram(pix1, 1);
+ numaDiscretizeHistoInBins(na1, 1000, &na2, &na3);
+ pix2 = gplotSimplePix1(na3, "rank vs gray");
+ pix3 = gplotSimplePix1(na2, "gray vs rank-binval");
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 11 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 12 */
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+
+ pix1 = pixRead("wyom.jpg");
+ pixGetRankColorArray(pix1, 20, L_SELECT_RED, 5,
+ &carray, NULL, 0);
+ pix2 = pixDisplayColorArray(carray, 20, 200, 5, 6);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 13 */
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix3 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 30, 2);
+ pixDisplayWithTitle(pix3, 800, 20, NULL, rp->display);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+ lept_free(carray);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/rankhisto_reg.c b/leptonica/prog/rankhisto_reg.c
new file mode 100644
index 00000000..58a031c8
--- /dev/null
+++ b/leptonica/prog/rankhisto_reg.c
@@ -0,0 +1,135 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * rankhisto_reg.c
+ *
+ * Tests grayscale rank functions:
+ * (1) pixGetRankColorArray()
+ * (2) numaDiscretizeHistoInBins()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, w, h, nbins, factor;
+l_int32 spike;
+l_uint32 *array, *marray;
+NUMA *na, *nabinval, *narank;
+PIX *pixs, *pix1, *pix2;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "rankhisto_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Find the rank bin colors */
+ pixs = pixRead("map1.jpg");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ factor = L_MAX(1, (l_int32)sqrt((l_float64)(w * h / 20000.0)));
+ nbins = 10;
+ pixa = pixaCreate(0);
+ pixGetRankColorArray(pixs, nbins, L_SELECT_MIN, factor, &array, pixa, 6);
+ if (!array)
+ return ERROR_INT("\n\n\nFAILURE!\n\n\n", rp->testname, 1);
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix1, 1000, 100, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+ for (i = 0; i < nbins; i++)
+ lept_stderr("%d: %x\n", i, array[i]);
+ pix1 = pixDisplayColorArray(array, nbins, 200, 5, 6);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix1, 0, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ /* Modify the rank bin colors by mapping them such
+ * that the lightest color is mapped to white */
+ marray = (l_uint32 *)lept_calloc(nbins, sizeof(l_uint32));
+ for (i = 0; i < nbins; i++)
+ pixelLinearMapToTargetColor(array[i], array[nbins - 1],
+ 0xffffff00, &marray[i]);
+ pix1 = pixDisplayColorArray(marray, nbins, 200, 5, 6);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix1, 0, 600, NULL, rp->display);
+ pixDestroy(&pix1);
+ lept_free(marray);
+
+ /* Map to the lightest bin; then do TRC adjustment */
+ pix1 = pixLinearMapToTargetColor(NULL, pixs, array[nbins - 1], 0xffffff00);
+ pix2 = pixGammaTRC(NULL, pix1, 1.0, 0, 240);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pix2, 1000, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Now test the edge case where all the histo data is piled up
+ * at one place. We only require that the result be sensible. */
+ pixa = pixaCreate(0);
+ for (i = 0; i < 3; i++) {
+ if (i == 0)
+ spike = 1;
+ else if (i == 1)
+ spike = 50;
+ else
+ spike = 99;
+ na = numaMakeConstant(0, 100);
+ numaReplaceNumber(na, spike, 200.0);
+ numaDiscretizeHistoInBins(na, 10, &nabinval, &narank);
+ pix1 = gplotSimplePix1(na, "Histogram");
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = gplotSimplePix1(nabinval, "Gray value vs rank bin");
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix1 = gplotSimplePix1(narank, "rank value vs. gray value");
+ pixaAddPix(pixa, pix1, L_INSERT);
+ numaDestroy(&na);
+ numaDestroy(&nabinval);
+ numaDestroy(&narank);
+ }
+ pix1 = pixaDisplayTiledInColumns(pixa, 3, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix1, 1000, 800, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+
+ pixDestroy(&pixs);
+ lept_free(array);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/rasterop_reg.c b/leptonica/prog/rasterop_reg.c
new file mode 100644
index 00000000..e52a09b3
--- /dev/null
+++ b/leptonica/prog/rasterop_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * rasterop_reg.c
+ *
+ * This is a fairly rigorous test of rasterop.
+ * It demonstrates both that the results are correct
+ * with many different rop configurations, and,
+ * if done under valgrind, that no memory violations occur.
+ * We use it on an image with FG extending to the edges.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, w, h, same, width, height, cx, cy;
+l_uint32 val;
+BOX *box;
+PIX *pix0, *pixs, *pixse, *pixd1, *pixd2;
+SEL *sel;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pix0 = pixRead("feyn-fract.tif");
+ box = boxCreate(293, 37, pixGetWidth(pix0) - 691, pixGetHeight(pix0) -145);
+ pixs = pixClipRectangle(pix0, box, NULL);
+ boxDestroy(&box);
+ if (rp->display) pixDisplay(pixs, 100, 100);
+
+ /* Test 63 different sizes */
+ for (width = 1; width <= 25; width += 3) { /* 9 values */
+ for (height = 1; height <= 25; height += 4) { /* 7 values */
+
+ cx = width / 2;
+ cy = height / 2;
+
+ /* Dilate using an actual sel */
+ sel = selCreateBrick(height, width, cy, cx, SEL_HIT);
+ pixd1 = pixDilate(NULL, pixs, sel);
+
+ /* Dilate using a pix as a sel */
+ pixse = pixCreate(width, height, 1);
+ pixSetAll(pixse);
+ pixd2 = pixCopy(NULL, pixs);
+ w = pixGetWidth(pixs);
+ h = pixGetHeight(pixs);
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ pixGetPixel(pixs, j, i, &val);
+ if (val)
+ pixRasterop(pixd2, j - cx, i - cy, width, height,
+ PIX_SRC | PIX_DST, pixse, 0, 0);
+ }
+ }
+
+ pixEqual(pixd1, pixd2, &same);
+ regTestCompareValues(rp, 1, same, 0.0); /* 0 - 62 */
+ if (same == 0)
+ lept_stderr("Results differ for SE (width,height) = (%d,%d)\n",
+ width, height);
+
+ pixDestroy(&pixse);
+ pixDestroy(&pixd1);
+ pixDestroy(&pixd2);
+ selDestroy(&sel);
+ }
+ }
+ pixDestroy(&pix0);
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/rasteropip_reg.c b/leptonica/prog/rasteropip_reg.c
new file mode 100644
index 00000000..d46c9480
--- /dev/null
+++ b/leptonica/prog/rasteropip_reg.c
@@ -0,0 +1,81 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * rasteropip_reg.c
+ *
+ * Tests in-place operation using the general 2-image pixRasterop().
+ * The in-place operation works because there is no overlap
+ * between the src and dest rectangles.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j;
+PIX *pixs, *pixt, *pixd;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("test8.jpg");
+ pixt = pixCopy(NULL, pixs);
+
+ /* Copy, in-place and one COLUMN at a time, from the right
+ side to the left side. */
+ for (j = 0; j < 200; j++)
+ pixRasterop(pixs, 20 + j, 20, 1, 250, PIX_SRC, pixs, 250 + j, 20);
+ pixDisplayWithTitle(pixs, 50, 50, "in-place copy", rp->display);
+
+ /* Copy, in-place and one ROW at a time, from the right
+ side to the left side. */
+ for (i = 0; i < 250; i++)
+ pixRasterop(pixt, 20, 20 + i, 200, 1, PIX_SRC, pixt, 250, 20 + i);
+
+ /* Test */
+ regTestComparePix(rp, pixs, pixt); /* 0 */
+ pixDestroy(&pixs);
+ pixDestroy(&pixt);
+
+ /* Show the mirrored border, which uses the general
+ pixRasterop() on an image in-place. */
+ pixs = pixRead("test8.jpg");
+ pixt = pixRemoveBorder(pixs, 40);
+ pixd = pixAddMirroredBorder(pixt, 40, 40, 40, 40);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pixd, 650, 50, "mirrored border", rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pixt);
+ pixDestroy(&pixd);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/rasteroptest.c b/leptonica/prog/rasteroptest.c
new file mode 100644
index 00000000..07ced9ce
--- /dev/null
+++ b/leptonica/prog/rasteroptest.c
@@ -0,0 +1,143 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * rasteroptest.c
+ *
+ * This is in essence a fuzzing test for rasterop.
+ *
+ * These timings are for 1000 iterations of the inner loop.
+ * rasterop:
+ * optimizing: 0.35 sec
+ * valgrind: 12 sec
+ * rasteropIP:
+ * optimizing: 0.18 sec (two calls)
+ * valgrind: 13 sec (two calls)
+ *
+ * This has been tested with valgrind on:
+ * * all ops with niters = 10,000
+ * * op = PIX_SRC with niters = 100,000
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void GeneralTest(PIX *pix1, BOX *box1, BOX *box2, l_int32 op, l_int32 niters);
+void InplaceTest(PIX *pix1, BOX *box1, BOX *box2, l_int32 op, l_int32 niters);
+
+int main(int argc,
+ char **argv)
+{
+BOX *box1, *box2;
+PIX *pix1;
+l_int32 niters, op, selectop;
+
+ setLeptDebugOK(1);
+
+ pix1 = pixRead("test24.jpg");
+ box1 = boxCreate(243, 127, 513, 359);
+ box2 = boxCreate(541, 312, 513, 359);
+ niters = 10000;
+ selectop = PIX_SRC;
+
+#if 1
+ /* Basic rasterop */
+ for (op = 0; op < 16; op++)
+ GeneralTest(pix1, box1, box2, op, niters);
+#endif
+
+#if 1
+ /* In-place rasterop */
+ for (op = 0; op < 16; op++)
+ InplaceTest(pix1, box1, box2, op, niters);
+#endif
+
+#if 0
+ /* Basic rasterop; single operation */
+ GeneralTest(pix1, box1, box2, selectop, niters);
+#endif
+
+ pixDestroy(&pix1);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ return 0;
+}
+
+/* ------------------------------------------------------------------- */
+void GeneralTest(PIX *pix1, BOX *box1, BOX *box2, l_int32 op, l_int32 niters)
+{
+PIX *pix2, *pix3;
+l_int32 i, val1, val2, val3, val4, val5, val6;
+
+ startTimer();
+ for (i = 0; i < niters; i++) {
+ pix2 = pixClipRectangle(pix1, box1, NULL);
+ pix3 = pixClipRectangle(pix1, box2, NULL);
+ genRandomIntOnInterval(-42, 403, 0, &val1);
+ genRandomIntOnInterval(-18, 289, 0, &val2);
+ genRandomIntOnInterval(13, 289, 0, &val3);
+ genRandomIntOnInterval(13, 403, 0, &val4);
+ genRandomIntOnInterval(-34, 289, 0, &val5);
+ genRandomIntOnInterval(-38, 403, 0, &val6);
+ pixRasterop(pix3, val1, val2, val3, val4, op, pix2, val5, val6);
+ if (i == op) {
+ lept_stderr("Rasterop: op = %d ", op);
+ pixDisplay(pix3, 100 * i, 100);
+ }
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ lept_stderr("Time = %7.3f sec\n", stopTimer());
+}
+
+/* ------------------------------------------------------------------- */
+void InplaceTest(PIX *pix1, BOX *box1, BOX *box2, l_int32 op, l_int32 niters)
+{
+PIX *pix2;
+l_int32 i, val1, val2, val3, val4, val5, val6;
+
+ startTimer();
+ for (i = 0; i < niters; i++) {
+ pix2 = pixClipRectangle(pix1, box1, NULL);
+ genRandomIntOnInterval(-217, 113, 0, &val1);
+ genRandomIntOnInterval(1, 211, 0, &val2);
+ genRandomIntOnInterval(-217, 143, 0, &val3);
+ genRandomIntOnInterval(-247, 113, 0, &val4);
+ genRandomIntOnInterval(1, 241, 0, &val5);
+ genRandomIntOnInterval(-113, 163, 0, &val6);
+ pixRasteropHip(pix2, val1, val2, val3, L_BRING_IN_WHITE);
+ pixRasteropVip(pix2, val4, val5, val6, L_BRING_IN_BLACK);
+ if (i == op) {
+ lept_stderr("Rasterop: op = %d ", op);
+ pixDisplay(pix2, 100 * i, 500);
+ }
+ pixDestroy(&pix2);
+ }
+ lept_stderr("Time = %7.3f sec\n", stopTimer());
+}
diff --git a/leptonica/prog/rbtreetest.c b/leptonica/prog/rbtreetest.c
new file mode 100644
index 00000000..3f5968e8
--- /dev/null
+++ b/leptonica/prog/rbtreetest.c
@@ -0,0 +1,111 @@
+/*====================================================================*
+ - 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/).
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define PRINT_FULL_TREE 0
+#define TRACE 0
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i;
+RB_TYPE x, y;
+RB_TYPE *pval;
+L_RBTREE *t;
+
+ setLeptDebugOK(1);
+
+ t = l_rbtreeCreate(L_INT_TYPE);
+ l_rbtreePrint(stderr, t);
+
+ /* Build the tree */
+ for (i = 0; i < 5000; i++) {
+ x.itype = rand() % 10000;
+ y.itype = rand() % 10000;
+#if TRACE
+ l_rbtreePrint(stderr, t);
+ printf("Inserting %d -> %d\n\n", x.itype, y.itype);
+#endif /* TRACE */
+ l_rbtreeInsert(t, x, y);
+ pval = l_rbtreeLookup(t, x);
+ if (pval->itype != y.itype)
+ L_ERROR("val %lld doesn't agree for key %lld\n", "main",
+ pval->itype, x.itype);
+ }
+
+ /* Count the nodes in the tree */
+ lept_stderr("count = %d\n", l_rbtreeGetCount(t));
+
+#if PRINT_FULL_TREE
+ l_rbtreePrint(stderr, t); /* very big */
+#endif /* PRINT_FULL_TREE */
+
+ /* Destroy the tree and count the remaining nodes */
+ l_rbtreeDestroy(&t);
+ l_rbtreePrint(stderr, t); /* should give an error message */
+ lept_stderr("count = %d\n", l_rbtreeGetCount(t));
+
+ /* Build another tree */
+ t = l_rbtreeCreate(L_INT_TYPE);
+ for (i = 0; i < 6000; i++) {
+ x.itype = rand() % 10000;
+ y.itype = rand() % 10000;
+ l_rbtreeInsert(t, x, y);
+ }
+
+ /* Count the nodes in the tree */
+ lept_stderr("count = %d\n", l_rbtreeGetCount(t));
+
+ /* Delete lots of nodes randomly from the tree and recount.
+ * Deleting 80,000 random points gets them all; deleting
+ * 60,000 removes all but 7 points. */
+ for (i = 0; i < 60000; i++) {
+ x.itype = rand() % 10000;
+#if TRACE
+ l_rbtreePrint(stderr, t);
+ printf("Deleting key %d\n\n", x.itype);
+#endif /* TRACE */
+ l_rbtreeDelete(t, x);
+ }
+ lept_stderr("count = %d\n", l_rbtreeGetCount(t));
+ l_rbtreePrint(stderr, t);
+ lept_free(t);
+
+ return 0;
+}
+
diff --git a/leptonica/prog/recog/digits/bootnum1.pa b/leptonica/prog/recog/digits/bootnum1.pa
new file mode 100644
index 00000000..cd3579ba
--- /dev/null
+++ b/leptonica/prog/recog/digits/bootnum1.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/bootnum2.pa b/leptonica/prog/recog/digits/bootnum2.pa
new file mode 100644
index 00000000..f3bb0cd0
--- /dev/null
+++ b/leptonica/prog/recog/digits/bootnum2.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/bootnum3.pa b/leptonica/prog/recog/digits/bootnum3.pa
new file mode 100644
index 00000000..61880ce6
--- /dev/null
+++ b/leptonica/prog/recog/digits/bootnum3.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/bootnum4.pa b/leptonica/prog/recog/digits/bootnum4.pa
new file mode 100644
index 00000000..06996917
--- /dev/null
+++ b/leptonica/prog/recog/digits/bootnum4.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit0.comp.tif b/leptonica/prog/recog/digits/digit0.comp.tif
new file mode 100644
index 00000000..091e617c
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit0.comp.tif
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit1.comp.tif b/leptonica/prog/recog/digits/digit1.comp.tif
new file mode 100644
index 00000000..32db55da
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit1.comp.tif
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit2.comp.tif b/leptonica/prog/recog/digits/digit2.comp.tif
new file mode 100644
index 00000000..4a1391ee
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit2.comp.tif
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit3.comp.tif b/leptonica/prog/recog/digits/digit3.comp.tif
new file mode 100644
index 00000000..ba4fd807
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit3.comp.tif
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit4.comp.tif b/leptonica/prog/recog/digits/digit4.comp.tif
new file mode 100644
index 00000000..c5c0129e
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit4.comp.tif
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit5.comp.tif b/leptonica/prog/recog/digits/digit5.comp.tif
new file mode 100644
index 00000000..f839cc9d
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit5.comp.tif
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit5.orig-25.pa b/leptonica/prog/recog/digits/digit5.orig-25.pa
new file mode 100644
index 00000000..19afb361
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit5.orig-25.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit6.comp.tif b/leptonica/prog/recog/digits/digit6.comp.tif
new file mode 100644
index 00000000..d72570d0
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit6.comp.tif
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit7.comp.tif b/leptonica/prog/recog/digits/digit7.comp.tif
new file mode 100644
index 00000000..5bc50acb
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit7.comp.tif
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit8.comp.tif b/leptonica/prog/recog/digits/digit8.comp.tif
new file mode 100644
index 00000000..c3036215
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit8.comp.tif
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit9.comp.tif b/leptonica/prog/recog/digits/digit9.comp.tif
new file mode 100644
index 00000000..66a79d80
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit9.comp.tif
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set01.pa b/leptonica/prog/recog/digits/digit_set01.pa
new file mode 100644
index 00000000..e22b056e
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set01.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set02.pa b/leptonica/prog/recog/digits/digit_set02.pa
new file mode 100644
index 00000000..a72e499f
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set02.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set03.pa b/leptonica/prog/recog/digits/digit_set03.pa
new file mode 100644
index 00000000..22a9f424
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set03.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set04.pa b/leptonica/prog/recog/digits/digit_set04.pa
new file mode 100644
index 00000000..42aa8c64
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set04.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set05.pa b/leptonica/prog/recog/digits/digit_set05.pa
new file mode 100644
index 00000000..87f5d982
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set05.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set06.pa b/leptonica/prog/recog/digits/digit_set06.pa
new file mode 100644
index 00000000..ef6aae7f
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set06.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set07.pa b/leptonica/prog/recog/digits/digit_set07.pa
new file mode 100644
index 00000000..85b3d874
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set07.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set08.pa b/leptonica/prog/recog/digits/digit_set08.pa
new file mode 100644
index 00000000..2a776ca7
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set08.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set09.pa b/leptonica/prog/recog/digits/digit_set09.pa
new file mode 100644
index 00000000..cd1f4e6a
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set09.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set10.pa b/leptonica/prog/recog/digits/digit_set10.pa
new file mode 100644
index 00000000..39078a84
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set10.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set11.pa b/leptonica/prog/recog/digits/digit_set11.pa
new file mode 100644
index 00000000..19c21c08
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set11.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set12.pa b/leptonica/prog/recog/digits/digit_set12.pa
new file mode 100644
index 00000000..53e29c46
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set12.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set13.pa b/leptonica/prog/recog/digits/digit_set13.pa
new file mode 100644
index 00000000..a701c5a2
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set13.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set14.pa b/leptonica/prog/recog/digits/digit_set14.pa
new file mode 100644
index 00000000..cff1b405
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set14.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/digit_set15.pa b/leptonica/prog/recog/digits/digit_set15.pa
new file mode 100644
index 00000000..4bd753cb
--- /dev/null
+++ b/leptonica/prog/recog/digits/digit_set15.pa
Binary files differ
diff --git a/leptonica/prog/recog/digits/page.306.png b/leptonica/prog/recog/digits/page.306.png
new file mode 100644
index 00000000..a234783c
--- /dev/null
+++ b/leptonica/prog/recog/digits/page.306.png
Binary files differ
diff --git a/leptonica/prog/recog/digits/page.590.png b/leptonica/prog/recog/digits/page.590.png
new file mode 100644
index 00000000..5ae2edd6
--- /dev/null
+++ b/leptonica/prog/recog/digits/page.590.png
Binary files differ
diff --git a/leptonica/prog/recog/sets/samples06.png b/leptonica/prog/recog/sets/samples06.png
new file mode 100644
index 00000000..23b7df96
--- /dev/null
+++ b/leptonica/prog/recog/sets/samples06.png
Binary files differ
diff --git a/leptonica/prog/recog/sets/test01.pa b/leptonica/prog/recog/sets/test01.pa
new file mode 100644
index 00000000..d1e4161f
--- /dev/null
+++ b/leptonica/prog/recog/sets/test01.pa
Binary files differ
diff --git a/leptonica/prog/recog/sets/test02.pa b/leptonica/prog/recog/sets/test02.pa
new file mode 100644
index 00000000..79d7f506
--- /dev/null
+++ b/leptonica/prog/recog/sets/test02.pa
Binary files differ
diff --git a/leptonica/prog/recog/sets/test03.pa b/leptonica/prog/recog/sets/test03.pa
new file mode 100644
index 00000000..c9c7157b
--- /dev/null
+++ b/leptonica/prog/recog/sets/test03.pa
Binary files differ
diff --git a/leptonica/prog/recog/sets/test05.pa b/leptonica/prog/recog/sets/test05.pa
new file mode 100644
index 00000000..c8a93dd5
--- /dev/null
+++ b/leptonica/prog/recog/sets/test05.pa
Binary files differ
diff --git a/leptonica/prog/recog/sets/test06.pa b/leptonica/prog/recog/sets/test06.pa
new file mode 100644
index 00000000..09a55bd8
--- /dev/null
+++ b/leptonica/prog/recog/sets/test06.pa
Binary files differ
diff --git a/leptonica/prog/recog/sets/train01.pa b/leptonica/prog/recog/sets/train01.pa
new file mode 100644
index 00000000..ad0891a5
--- /dev/null
+++ b/leptonica/prog/recog/sets/train01.pa
Binary files differ
diff --git a/leptonica/prog/recog/sets/train02.pa b/leptonica/prog/recog/sets/train02.pa
new file mode 100644
index 00000000..807d7609
--- /dev/null
+++ b/leptonica/prog/recog/sets/train02.pa
Binary files differ
diff --git a/leptonica/prog/recog/sets/train03.pa b/leptonica/prog/recog/sets/train03.pa
new file mode 100644
index 00000000..22a9f424
--- /dev/null
+++ b/leptonica/prog/recog/sets/train03.pa
Binary files differ
diff --git a/leptonica/prog/recog/sets/train04.pa b/leptonica/prog/recog/sets/train04.pa
new file mode 100644
index 00000000..47373bc9
--- /dev/null
+++ b/leptonica/prog/recog/sets/train04.pa
Binary files differ
diff --git a/leptonica/prog/recog/sets/train05.pa b/leptonica/prog/recog/sets/train05.pa
new file mode 100644
index 00000000..19c21c08
--- /dev/null
+++ b/leptonica/prog/recog/sets/train05.pa
Binary files differ
diff --git a/leptonica/prog/recog/sets/train06.pa b/leptonica/prog/recog/sets/train06.pa
new file mode 100644
index 00000000..6c22bfb0
--- /dev/null
+++ b/leptonica/prog/recog/sets/train06.pa
Binary files differ
diff --git a/leptonica/prog/recog_bootnum1.c b/leptonica/prog/recog_bootnum1.c
new file mode 100644
index 00000000..4143fe49
--- /dev/null
+++ b/leptonica/prog/recog_bootnum1.c
@@ -0,0 +1,320 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * recog_bootnum.c
+ *
+ * This does two things:
+ *
+ * (1) It makes bootnum1.pa and bootnum2.pa from stored labelled data.
+ *
+ * (2) Using these, as well as bootnum3.pa, it makes code for
+ * generating and compiling the the pixas, which are used by the
+ * boot digit recognizer.
+ * The output of the code generator is files such as autogen_101.*.
+ * These files have been edited to combine the .c and .h files into
+ * a single .c file:
+ * autogen_101.* --> src/bootnumgen1.c
+ * autogen_102.* --> src/bootnumgen2.c
+ * autogen_103.* --> src/bootnumgen3.c
+ *
+ * To add another set of templates to bootnumgen1.c:
+ * (a) Add a new .pa file: prog/recog/digits/digit_setN.pa (N > 15)
+ * (b) Add code to MakeBootnum1() for this set, selecting with the
+ * string those templates you want to use.
+ * (c) Run recog_bootnum.
+ * * This makes a new /tmp/lept/recog/digits/bootnum1.pa.
+ * Replace prog/recog/digits/bootnum1.pa with this.
+ * * This makes new files: /tmp/lept/auto/autogen.101.{h,c}.
+ * The .h file is the only one we need to use.
+ * Replace the encoded string in src/bootnumgen1.c with the
+ * one in autogen.101.h, and recompile.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+#include "bmfdata.h"
+
+static PIXA *MakeBootnum1(void);
+static PIXA *MakeBootnum2(void);
+
+l_int32 main(int argc,
+ char **argv)
+{
+PIX *pix1;
+PIXA *pixa1, *pixa2, *pixa3;
+L_STRCODE *strc;
+
+ if (argc != 1) {
+ lept_stderr(" Syntax: recog_bootnum\n");
+ return 1;
+ }
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/recog/digits");
+
+ /* ----------------------- Bootnum 1 --------------------- */
+ /* Make the bootnum pixa from the images */
+ pixa1 = MakeBootnum1();
+ pixaWrite("/tmp/lept/recog/digits/bootnum1.pa", pixa1);
+ pix1 = pixaDisplayTiledWithText(pixa1, 1500, 1.0, 10, 2, 6, 0xff000000);
+ pixDisplay(pix1, 100, 0);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+
+ /* Generate the code to make the bootnum1 pixa.
+ * Note: the actual code we use is in bootnumgen1.c, and
+ * has already been compiled into the library. */
+ strc = strcodeCreate(101); /* arbitrary integer */
+ strcodeGenerate(strc, "/tmp/lept/recog/digits/bootnum1.pa", "PIXA");
+ strcodeFinalize(&strc, "/tmp/lept/auto");
+ lept_free(strc);
+
+ /* Generate the bootnum1 pixa from the generated code */
+ pixa1 = l_bootnum_gen1();
+ pix1 = pixaDisplayTiledWithText(pixa1, 1500, 1.0, 10, 2, 6, 0xff000000);
+/* pix1 = pixaDisplayTiled(pixa1, 1500, 0, 30); */
+ pixDisplay(pix1, 100, 0);
+ pixDestroy(&pix1);
+
+ /* Extend the bootnum1 pixa by erosion */
+ pixa3 = pixaExtendByMorph(pixa1, L_MORPH_ERODE, 2, NULL, 1);
+ pix1 = pixaDisplayTiledWithText(pixa3, 1500, 1.0, 10, 2, 6, 0xff000000);
+ pixDisplay(pix1, 100, 0);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa3);
+
+ /* ----------------------- Bootnum 2 --------------------- */
+ /* Read bootnum 2 */
+ pixa2 = pixaRead("recog/digits/bootnum2.pa");
+ pixaWrite("/tmp/lept/recog/digits/bootnum2.pa", pixa2);
+ pix1 = pixaDisplayTiledWithText(pixa2, 1500, 1.0, 10, 2, 6, 0xff000000);
+ pixDisplay(pix1, 100, 700);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa2);
+
+ /* Generate the code to make the bootnum2 pixa.
+ * Note: the actual code we use is in bootnumgen2.c. */
+ strc = strcodeCreate(102); /* another arbitrary integer */
+ strcodeGenerate(strc, "/tmp/lept/recog/digits/bootnum2.pa", "PIXA");
+ strcodeFinalize(&strc, "/tmp/lept/auto");
+ lept_free(strc);
+
+ /* Generate the bootnum2 pixa from the generated code */
+ pixa2 = l_bootnum_gen2();
+/* pix1 = pixaDisplayTiled(pixa2, 1500, 0, 30); */
+ pix1 = pixaDisplayTiledWithText(pixa2, 1500, 1.0, 10, 2, 6, 0xff000000);
+ pixDisplay(pix1, 100, 700);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa2);
+
+ /* ----------------------- Bootnum 3 --------------------- */
+ /* Read bootnum 3 */
+ pixa1 = pixaRead("recog/digits/bootnum3.pa");
+ pix1 = pixaDisplayTiledWithText(pixa1, 1500, 1.0, 10, 2, 6, 0xff000000);
+ pixDisplay(pix1, 1000, 0);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+
+ /* Generate the code that, when deserializes, gives you bootnum3.pa.
+ * Note: the actual code we use is in bootnumgen3.c, and
+ * has already been compiled into the library. */
+ strc = strcodeCreate(103); /* arbitrary integer */
+ strcodeGenerate(strc, "recog/digits/bootnum3.pa", "PIXA");
+ strcodeFinalize(&strc, "/tmp/lept/auto");
+ lept_free(strc);
+
+ /* Generate the bootnum3 pixa from the generated code */
+ pixa1 = l_bootnum_gen3();
+ pix1 = pixaDisplayTiledWithText(pixa1, 1500, 1.0, 10, 2, 6, 0xff000000);
+ pixDisplay(pix1, 1000, 0);
+ pixDestroy(&pix1);
+
+ /* Extend the bootnum3 pixa twice by erosion */
+ pixa3 = pixaExtendByMorph(pixa1, L_MORPH_ERODE, 2, NULL, 1);
+ pix1 = pixaDisplayTiledWithText(pixa3, 1500, 1.0, 10, 2, 6, 0xff000000);
+ pixDisplay(pix1, 1000, 0);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa3);
+
+#if 0
+ pixa1 = l_bootnum_gen1();
+/* pixa1 = pixaRead("recog/digits/bootnum1.pa"); */
+ pixaWrite("/tmp/lept/junk.pa", pixa1);
+ pixa2 = pixaRead("/tmp/lept/junk.pa");
+ pixaWrite("/tmp/lept/junk1.pa", pixa2);
+ pixa3 = pixaRead("/tmp/lept/junk1.pa");
+ n = pixaGetCount(pixa3);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixa3, i, L_CLONE);
+ lept_stderr("i = %d, text = %s\n", i, pixGetText(pix));
+ pixDestroy(&pix);
+ }
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+#endif
+
+ return 0;
+}
+
+
+PIXA *MakeBootnum1(void)
+{
+const char *str;
+PIXA *pixa1, *pixa2, *pixa3;
+
+ pixa1 = pixaRead("recog/digits/digit_set02.pa");
+ str = "10, 27, 35, 45, 48, 74, 79, 97, 119, 124, 148";
+ pixa3 = pixaSelectWithString(pixa1, str, NULL);
+ pixaDestroy(&pixa1);
+
+ pixa1 = pixaRead("recog/digits/digit_set03.pa");
+ str = "2, 15, 30, 50, 60, 75, 95, 105, 121, 135";
+ pixa2 = pixaSelectWithString(pixa1, str, NULL);
+ pixaJoin(pixa3, pixa2, 0, -1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+
+ pixa1 = pixaRead("recog/digits/digit_set05.pa");
+ str = "0, 15, 30, 49, 60, 75, 90, 105, 120, 135";
+ pixa2 = pixaSelectWithString(pixa1, str, NULL);
+ pixaJoin(pixa3, pixa2, 0, -1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+
+ pixa1 = pixaRead("recog/digits/digit_set06.pa");
+ str = "4, 15, 30, 48, 60, 78, 90, 105, 120, 135";
+ pixa2 = pixaSelectWithString(pixa1, str, NULL);
+ pixaJoin(pixa3, pixa2, 0, -1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+
+ pixa1 = pixaRead("recog/digits/digit_set07.pa");
+ str = "3, 15, 30, 45, 60, 77, 78, 91, 105, 120, 149";
+ pixa2 = pixaSelectWithString(pixa1, str, NULL);
+ pixaJoin(pixa3, pixa2, 0, -1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+
+ pixa1 = pixaRead("recog/digits/digit_set08.pa");
+ str = "0, 20, 30, 45, 60, 75, 90, 106, 121, 135";
+ pixa2 = pixaSelectWithString(pixa1, str, NULL);
+ pixaJoin(pixa3, pixa2, 0, -1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+
+ pixa1 = pixaRead("recog/digits/digit_set09.pa");
+ str = "0, 20, 32, 47, 54, 63, 75, 91, 105, 125, 136";
+ pixa2 = pixaSelectWithString(pixa1, str, NULL);
+ pixaJoin(pixa3, pixa2, 0, -1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+
+ pixa1 = pixaRead("recog/digits/digit_set11.pa");
+ str = "0, 15, 36, 46, 62, 63, 76, 91, 106, 123, 135";
+ pixa2 = pixaSelectWithString(pixa1, str, NULL);
+ pixaJoin(pixa3, pixa2, 0, -1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+
+ pixa1 = pixaRead("recog/digits/digit_set12.pa");
+ str = "1, 20, 31, 45, 61, 75, 95, 107, 120, 135";
+ pixa2 = pixaSelectWithString(pixa1, str, NULL);
+ pixaJoin(pixa3, pixa2, 0, -1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+
+ pixa1 = pixaRead("recog/digits/digit_set13.pa");
+ str = "1, 16, 31, 48, 63, 78, 98, 105, 123, 136";
+ pixa2 = pixaSelectWithString(pixa1, str, NULL);
+ pixaJoin(pixa3, pixa2, 0, -1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+
+ pixa1 = pixaRead("recog/digits/digit_set14.pa");
+ str = "1, 14, 24, 37, 53, 62, 74, 83, 98, 114";
+ pixa2 = pixaSelectWithString(pixa1, str, NULL);
+ pixaJoin(pixa3, pixa2, 0, -1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+
+ pixa1 = pixaRead("recog/digits/digit_set15.pa");
+ str = "0, 1, 3, 5, 7, 8, 13, 25, 35";
+ pixa2 = pixaSelectWithString(pixa1, str, NULL);
+ pixaJoin(pixa3, pixa2, 0, -1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+
+ return pixa3;
+}
+
+
+PIXA *MakeBootnum2(void)
+{
+char *fname;
+l_int32 i, n, w, h;
+BOX *box;
+PIX *pix;
+PIXA *pixa;
+L_RECOG *recog;
+SARRAY *sa;
+
+ /* Phase 1: generate recog from the digit data */
+ recog = recogCreate(0, 40, 0, 128, 1);
+ sa = getSortedPathnamesInDirectory("recog/bootnums", "png", 0, 0);
+ n = sarrayGetCount(sa);
+ for (i = 0; i < n; i++) {
+ /* Read each pix: grayscale, multi-character, labelled */
+ fname = sarrayGetString(sa, i, L_NOCOPY);
+ if ((pix = pixRead(fname)) == NULL) {
+ lept_stderr("Can't read %s\n", fname);
+ continue;
+ }
+
+ /* Convert to a set of 1 bpp, single character, labelled */
+ pixGetDimensions(pix, &w, &h, NULL);
+ box = boxCreate(0, 0, w, h);
+ recogTrainLabeled(recog, pix, box, NULL, 0);
+ pixDestroy(&pix);
+ boxDestroy(&box);
+ }
+ recogTrainingFinished(&recog, 1, -1, -1.0);
+ sarrayDestroy(&sa);
+
+ /* Phase 2: generate pixa consisting of 1 bpp, single character pix */
+ pixa = recogExtractPixa(recog);
+ pixaWrite("/tmp/lept/recog/digits/bootnum2.pa", pixa);
+ recogDestroy(&recog);
+ return pixa;
+}
+
+
diff --git a/leptonica/prog/recog_bootnum2.c b/leptonica/prog/recog_bootnum2.c
new file mode 100644
index 00000000..18dc3332
--- /dev/null
+++ b/leptonica/prog/recog_bootnum2.c
@@ -0,0 +1,183 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * recog_bootnum2.c
+ *
+ * This shows how to convert from a pixa of digit images to
+ * a very compressed representation, including a filtering step
+ * where selected pix are removed. This method was used to
+ * generate the recog/digits/digit*.comp.tif image mosaics.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+static const char *removeset = "4,7,9,21";
+
+void ProcessDigits(l_int32 i);
+void PixaDisplayNumbered(PIXA *pixa, const char *rootname);
+
+l_int32 main(int argc,
+ char **argv)
+{
+ setLeptDebugOK(1);
+ lept_mkdir("lept/digit");
+ ProcessDigits(5);
+ return 0;
+}
+
+/* ----------------------------------------------------- */
+void ProcessDigits(l_int32 index)
+{
+char rootname[8] = "digit5";
+char buf[64];
+l_int32 i, nc, ns, same;
+NUMA *na1;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+PIXA *pixa1, *pixa2, *pixa3;
+
+ /* Read the unfiltered, unscaled pixa of twenty-five 5s */
+ snprintf(buf, sizeof(buf), "digits/%s.orig-25.pa", rootname);
+ pixa1 = pixaRead(buf);
+
+ /* Number and show the input images */
+ snprintf(buf, sizeof(buf), "/tmp/lept/digit/%s.orig-num", rootname);
+ PixaDisplayNumbered(pixa1, buf);
+
+ /* Remove some of them */
+ na1 = numaCreateFromString(removeset);
+ pixaRemoveSelected(pixa1, na1);
+ numaDestroy(&na1);
+ snprintf(buf, sizeof(buf), "/tmp/lept/digit/%s.filt.pa", rootname);
+ pixaWrite(buf, pixa1);
+
+ /* Number and show the filtered images */
+ snprintf(buf, sizeof(buf), "/tmp/lept/digit/%s.filt-num", rootname);
+ PixaDisplayNumbered(pixa1, buf);
+
+ /* Extract the largest c.c., clip to the foreground,
+ * and scale the result to a fixed size. */
+ nc = pixaGetCount(pixa1);
+ pixa2 = pixaCreate(nc);
+ for (i = 0; i < nc; i++) {
+ pix1 = pixaGetPix(pixa1, i, L_CLONE);
+ /* A threshold of 140 gives reasonable results */
+ pix2 = pixThresholdToBinary(pix1, 140);
+ /* Join nearly touching pieces */
+ pix3 = pixCloseSafeBrick(NULL, pix2, 5, 5);
+ /* Take the largest (by area) connected component */
+ pix4 = pixFilterComponentBySize(pix3, 0, L_SELECT_BY_AREA, 8, NULL);
+ /* Extract the original 1 bpp pixels that have been
+ * covered by the closing operation */
+ pixAnd(pix4, pix4, pix2);
+ /* Grab the result as an image with no surrounding whitespace */
+ pixClipToForeground(pix4, &pix5, NULL);
+ /* Rescale the result to the canonical size */
+ pix6 = pixScaleToSize(pix5, 20, 30);
+ pixaAddPix(pixa2, pix6, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ }
+
+ /* Add the index (a "5") in the text field of each pix; save pixa2 */
+ snprintf(buf, sizeof(buf), "%d", index);
+ for (i = 0; i < nc; i++) {
+ pix1 = pixaGetPix(pixa2, i, L_CLONE);
+ pixSetText(pix1, buf);
+ pixDestroy(&pix1);
+ }
+ snprintf(buf, sizeof(buf), "/tmp/lept/digit/%s.comp.pa", rootname);
+ pixaWrite(buf, pixa2);
+
+ /* Number and show the resulting binary templates */
+ snprintf(buf, sizeof(buf), "/tmp/lept/digit/%s.comp-num", rootname);
+ PixaDisplayNumbered(pixa2, buf);
+
+ /* Save the binary templates as a packed tiling (tiff g4).
+ * This is the most efficient way to represent the templates. */
+ pix1 = pixaDisplayOnLattice(pixa2, 20, 30, NULL, NULL);
+ pixDisplay(pix1, 1000, 500);
+ snprintf(buf, sizeof(buf), "/tmp/lept/digit/%s.comp.tif", rootname);
+ pixWrite(buf, pix1, IFF_TIFF_G4);
+
+ /* The number of templates is in the pix text string; check it. */
+ pix2 = pixRead(buf);
+ if (sscanf(pixGetText(pix2), "n = %d", &ns) != 1)
+ lept_stderr("Failed to read the number of templates!\n");
+ if (ns != nc)
+ lept_stderr("(stored = %d) != (actual number = %d)\n", ns, nc);
+
+ /* Reconstruct the pixa of templates from the tiled compressed
+ * image, and verify that the resulting pixa is the same. */
+ pixa3 = pixaMakeFromTiledPix(pix1, 20, 30, 0, 0, NULL);
+ pixaEqual(pixa2, pixa3, 0, NULL, &same);
+ if (!same)
+ lept_stderr("Pixa are not the same!\n");
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+}
+
+
+/* ----------------------------------------------------- */
+void PixaDisplayNumbered(PIXA *pixa,
+ const char *basename)
+{
+char buf[64];
+l_int32 fill, color, d;
+L_BMF *bmf;
+PIX *pix1;
+PIXA *pixa1, *pixa2;
+
+ bmf = bmfCreate(NULL, 4);
+ pixaGetPixDimensions(pixa, 0, NULL, NULL, &d);
+ fill = (d == 8) ? 0xff : 0;
+ color = (d == 8) ? 0x00000000 : 0xffffff00;
+ pixa1 = pixaAddBorderGeneral(NULL, pixa, 10, 10, 0, 0, fill);
+ pixa2 = pixaAddTextNumber(pixa1, bmf, NULL, color, L_ADD_BELOW);
+ snprintf(buf, sizeof(buf), "%s.pa", basename);
+ pixaWrite(buf, pixa2);
+ pix1 = pixaDisplayTiledInColumns(pixa2, 20, 2.5, 15, 2);
+ snprintf(buf, sizeof(buf), "%s.png", basename);
+ pixWrite(buf, pix1, IFF_PNG);
+ pixDisplay(pix1, 500, 500);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ bmfDestroy(&bmf);
+}
+
diff --git a/leptonica/prog/recog_bootnum3.c b/leptonica/prog/recog_bootnum3.c
new file mode 100644
index 00000000..365d3f34
--- /dev/null
+++ b/leptonica/prog/recog_bootnum3.c
@@ -0,0 +1,90 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * recog_bootnum3.c
+ *
+ * This does two things.
+ *
+ * (1) It makes recog/digits/bootnum4.pa, a pixa of 100 samples
+ * from each of the 10 digits. These are stored as 10 mosaics
+ * where the 100 samples are packed in 20x30 pixel tiles.
+ *
+ * (2) It generates the code that is able to generate a pixa with
+ * any number from 1 to 100 of samples for each digit. This
+ * new pixa has one pix for each sample (the tiled pix in the
+ * input pixa have been split out), so it can have up to 1000 pix.
+ * The compressed string of data and the code for deserializing
+ * it are auto-generated with the stringcode utility.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+l_int32 main(int argc,
+ char **argv)
+{
+char buf[64];
+l_int32 i;
+PIX *pix1, *pix2;
+PIXA *pixa1, *pixa2;
+L_STRCODE *strc;
+
+ if (argc != 1) {
+ lept_stderr(" Syntax: recog_bootnum3\n");
+ return 1;
+ }
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/digit");
+
+ /* Make a pixa of the first 100 samples for each digit.
+ * This will be saved to recog/digits/bootnum4.pa. */
+ pixa1 = pixaCreate(10);
+ for (i = 0; i < 10; i++) {
+ snprintf(buf, sizeof(buf), "recog/digits/digit%d.comp.tif", i);
+ pix1 = pixRead(buf);
+ pixa2 = pixaMakeFromTiledPix(pix1, 20, 30, 0, 100, NULL);
+ pix2 = pixaDisplayOnLattice(pixa2, 20, 30, NULL, NULL);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa2);
+ }
+ /* Write it out (and copy to recog/digits/bootnum4.pa) */
+ pixaWrite("/tmp/lept/digit/bootnum4.pa", pixa1);
+ pixaDestroy(&pixa1);
+
+ /* Generate the stringcode in two files for this pixa.
+ * Both files are then assempled into the source file
+ * bootnumgen4.c, which is compiled into the library. */
+ strc = strcodeCreate(212); // arbitrary integer
+ strcodeGenerate(strc, "/tmp/lept/digit/bootnum4.pa", "PIXA");
+ strcodeFinalize(&strc, ".");
+ return 0;
+}
diff --git a/leptonica/prog/recogsort.c b/leptonica/prog/recogsort.c
new file mode 100644
index 00000000..00233763
--- /dev/null
+++ b/leptonica/prog/recogsort.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * recogsort.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+char *boxatxt;
+l_int32 i;
+BOXA *boxa1, *boxa2, *boxa3;
+BOXAA *baa, *baa1;
+NUMAA *naa1;
+PIX *pixdb, *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa1, *pixa2, *pixa3, *pixat;
+L_RECOG *recog;
+SARRAY *sa1;
+
+ /* ----- Example identifying samples using training data ----- */
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/recog");
+
+ /* Read the training data */
+ pixat = pixaRead("recog/sets/train06.pa");
+ recog = recogCreateFromPixa(pixat, 0, 0, 0, 128, 1);
+ recogAverageSamples(&recog, 0); /* required for splitting characters */
+ pix1 = pixaDisplayTiledWithText(pixat, 1500, 1.0, 10, 1, 8, 0xff000000);
+ pixDisplay(pix1, 0, 0);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixat);
+
+ /* Read the data from all samples */
+ pix1 = pixRead("recog/sets/samples06.png");
+ boxatxt = pixGetText(pix1);
+ lept_stderr("%s\n", boxatxt);
+ boxa1 = boxaReadMem((l_uint8 *)boxatxt, strlen(boxatxt));
+ pixa1 = pixaCreateFromBoxa(pix1, boxa1, 0, 0, NULL);
+ pixDestroy(&pix1); /* destroys boxa1 */
+
+ /* Identify components in the sample data */
+ pixa2 = pixaCreate(0);
+ pixa3 = pixaCreate(0);
+ for (i = 0; i < 9; i++) {
+/* if (i != 4) continue; */ /* dots form separate boxa */
+/* if (i != 8) continue; */ /* broken 2 in '24' */
+ if (i != 8) continue;
+ pix1 = pixaGetPix(pixa1, i, L_CLONE);
+
+ /* Show the 2d box data in the sample */
+ boxa2 = pixConnComp(pix1, NULL, 8);
+ baa = boxaSort2d(boxa2, NULL, 6, 6, 5);
+ pix2 = boxaaDisplay(pix1, baa, 3, 1, 0xff000000, 0x00ff0000, 0, 0);
+ pixaAddPix(pixa3, pix2, L_INSERT);
+ boxaaDestroy(&baa);
+ boxaDestroy(&boxa2);
+
+ /* Get the numbers in the sample */
+ recogIdentifyMultiple(recog, pix1, 0, 0, &boxa3, NULL, &pixdb, 0);
+ sa1 = recogExtractNumbers(recog, boxa3, 0.7, -1, &baa1, &naa1);
+ sarrayWriteStream(stderr, sa1);
+ boxaaWriteStream(stderr, baa1);
+ numaaWriteStream(stderr, naa1);
+ pixaAddPix(pixa2, pixdb, L_INSERT);
+/* pixaWrite("/tmp/pixa.pa", pixa2); */
+ pixDestroy(&pix1);
+ boxaWriteStream(stderr, boxa3);
+ boxaDestroy(&boxa3);
+ boxaaDestroy(&baa1);
+ numaaDestroy(&naa1);
+ sarrayDestroy(&sa1);
+ }
+
+ pix3 = pixaDisplayLinearly(pixa2, L_VERT, 1.0, 0, 20, 1, NULL);
+ pixWrite("/tmp/lept/recog/pix3.png", pix3, IFF_PNG);
+ pix4 = pixaDisplayTiledInRows(pixa3, 32, 1500, 1.0, 0, 20, 2);
+ pixDisplay(pix4, 500, 0);
+ pixWrite("/tmp/lept/recog/pix4.png", pix4, IFF_PNG);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixaDestroy(&pixa1);
+ boxaDestroy(&boxa1);
+ recogDestroy(&recog);
+
+ return 0;
+}
+
+
diff --git a/leptonica/prog/recogtest1.c b/leptonica/prog/recogtest1.c
new file mode 100644
index 00000000..1d36cf4f
--- /dev/null
+++ b/leptonica/prog/recogtest1.c
@@ -0,0 +1,176 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * recogtest1.c
+ *
+ * Tests the recog utility using the bootstrap number set,
+ * for both training and identification
+ *
+ * An example of greedy splitting of touching characters is given.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+static const l_int32 scaledw = 0;
+static const l_int32 scaledh = 40;
+
+static const l_float32 MinScore[] = {0.6f, 0.7f, 0.9f};
+static const l_int32 MinTarget[] = {4, 5, 4};
+static const l_int32 MinSize[] = {3, 2, 3};
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i, linew, same;
+BOXA *boxat;
+PIX *pixd, *pix1, *pix2, *pixdb;
+PIXA *pixa1, *pixa2, *pixa3;
+L_RECOG *recog1, *recog2;
+
+ if (argc != 1) {
+ lept_stderr(" Syntax: recogtest1\n");
+ return 1;
+ }
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/digits");
+ recog1 = NULL;
+ recog2 = NULL;
+
+#if 0
+ linew = 5; /* for lines */
+#else
+ linew = 0; /* scanned image */
+#endif
+
+#if 1
+ pixa1 = pixaRead("recog/digits/bootnum1.pa");
+ recog1 = recogCreateFromPixa(pixa1, scaledw, scaledh, linew, 120, 1);
+ pix1 = pixaDisplayTiledWithText(pixa1, 1400, 1.0, 10, 2, 6, 0xff000000);
+ pixWrite("/tmp/lept/digits/bootnum1.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 800, 800);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+#endif
+
+#if 1
+ lept_stderr("Print Stats 1\n");
+ recogShowContent(stderr, recog1, 1, 1);
+#endif
+
+#if 1
+ lept_stderr("AverageSamples\n");
+ recogAverageSamples(&recog1, 1);
+ recogShowAverageTemplates(recog1);
+ pix1 = pixaGetPix(recog1->pixadb_ave, 0, L_CLONE);
+ pixWrite("/tmp/lept/digits/unscaled_ave.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ pix1 = pixaGetPix(recog1->pixadb_ave, 1, L_CLONE);
+ pixWrite("/tmp/lept/digits/scaled_ave.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+#endif
+
+#if 1
+ recogDebugAverages(&recog1, 0);
+ recogShowMatchesInRange(recog1, recog1->pixa_tr, 0.65, 1.0, 0);
+ pixWrite("/tmp/lept/digits/match_ave1.png", recog1->pixdb_range, IFF_PNG);
+ recogShowMatchesInRange(recog1, recog1->pixa_tr, 0.0, 1.0, 0);
+ pixWrite("/tmp/lept/digits/match_ave2.png", recog1->pixdb_range, IFF_PNG);
+#endif
+
+#if 1
+ lept_stderr("Print stats 2\n");
+ recogShowContent(stderr, recog1, 2, 1);
+ recogWrite("/tmp/lept/digits/rec1.rec", recog1);
+ recog2 = recogRead("/tmp/lept/digits/rec1.rec");
+ recogShowContent(stderr, recog2, 3, 1);
+ recogWrite("/tmp/lept/digits/rec2.rec", recog2);
+ filesAreIdentical("/tmp/lept/digits/rec1.rec",
+ "/tmp/lept/digits/rec2.rec", &same);
+ if (!same)
+ lept_stderr("Error in serialization!\n");
+ recogDestroy(&recog2);
+#endif
+
+#if 1
+ /* Three sets of parameters:
+ * 0.6, 0.3 : removes a few poor matches
+ * 0.8, 0.2 : remove many based on matching; remove some based on
+ * requiring retention of 20% of templates in each class
+ * 0.9, 0.01 : remove most based on matching; saved 1 in each class */
+ lept_stderr("Remove outliers\n");
+ pixa2 = recogExtractPixa(recog1);
+ for (i = 0; i < 3; i++) {
+ pixa3 = pixaRemoveOutliers1(pixa2, MinScore[i], MinTarget[i],
+ MinSize[i], &pix1, &pix2);
+ pixDisplay(pix1, 900, 250 * i);
+ pixDisplay(pix2, 1300, 250 * i);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa3);
+ }
+ pixaDestroy(&pixa2);
+#endif
+
+#if 1
+ /* Split touching characters */
+ lept_stderr("Split touching\n");
+ pixd = pixRead("recog/digits/page.590.png"); /* 590 or 306 */
+ recogIdentifyMultiple(recog1, pixd, 0, 0, &boxat, &pixa2, &pixdb, 1);
+ pixDisplay(pixdb, 800, 800);
+ boxaWriteStream(stderr, boxat);
+ pix1 = pixaDisplay(pixa2, 0, 0);
+ pixDisplay(pix1, 1200, 800);
+ pixDestroy(&pixdb);
+ pixDestroy(&pix1);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa2);
+ boxaDestroy(&boxat);
+#endif
+
+#if 1
+ lept_stderr("Reading new training set and computing averages\n");
+ lept_stderr("Print stats 3\n");
+ pixa1 = pixaRead("recog/sets/train03.pa");
+ recog2 = recogCreateFromPixa(pixa1, 0, 40, 0, 128, 1);
+ recogShowContent(stderr, recog2, 3, 1);
+ recogDebugAverages(&recog2, 3);
+ pixWrite("/tmp/lept/digits/averages.png", recog2->pixdb_ave, IFF_PNG);
+ recogShowAverageTemplates(recog2);
+ pixaDestroy(&pixa1);
+ recogDestroy(&recog2);
+#endif
+
+ recogDestroy(&recog1);
+ recogDestroy(&recog2);
+ return 0;
+}
diff --git a/leptonica/prog/recogtest2.c b/leptonica/prog/recogtest2.c
new file mode 100644
index 00000000..3e7c2342
--- /dev/null
+++ b/leptonica/prog/recogtest2.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * recogtest2.c
+ *
+ * Test bootstrap recognizer (BSR) to train a book-adapted
+ * recognizer (BAR), starting with unlabeled bitmaps from the book.
+ *
+ * Several BSRs are used.
+ * The BAR images are taken from recog/sets/train*.pa. We really
+ * know their classes, but pretend we don't, by erasing the labels.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+ /* Sets for training using boot recognizers */
+static char trainset1[] = "recog/sets/train04.pa"; /* partial set */
+static char trainset2[] = "recog/sets/train05.pa"; /* full set */
+
+ /* Use scanned images or width-normalized lines */
+#if 1
+static const l_int32 linew = 0; /* use scanned bitmaps */
+#else
+static const l_int32 linew = 5; /* use generated lines */
+#endif
+
+l_int32 main(int argc,
+ char **argv)
+{
+char *fname;
+l_int32 i;
+BOXA *boxa1;
+BOXAA *baa;
+NUMAA *naa;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa1, *pixa2, *pixa3;
+L_RECOG *recogboot, *recog1;
+SARRAY *sa;
+
+ if (argc != 1) {
+ lept_stderr(" Syntax: recogtest2\n");
+ return 1;
+ }
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/recog");
+
+ /* Files with 'unlabeled' templates from book */
+ sa = sarrayCreate(2);
+ sarrayAddString(sa, trainset1, L_COPY);
+ sarrayAddString(sa, trainset2, L_COPY);
+
+ /* ----------------------------------------------------------- */
+ /* Do operations with a simple bootstrap recognizer */
+ /* ----------------------------------------------------------- */
+
+ /* Generate a BSR (boot-strap recog), and show the unscaled
+ * and scaled versions of the templates */
+ pixa1 = (PIXA *)l_bootnum_gen1(); /* from recog/digits/bootnum1.pa */
+ recogboot = recogCreateFromPixa(pixa1, 0, 40, linew, 128, 1);
+ recogWrite("/tmp/lept/recog/boot1.rec", recogboot);
+ recogShowContent(stderr, recogboot, 1, 1);
+ pixaDestroy(&pixa1);
+
+ /* Generate a BAR (book-adapted recog) for a set of images from
+ * one book. Select a set of digit images. These happen to
+ * be labeled, so we clear the text field from each pix before
+ * running it through the boot recognizer. */
+ for (i = 0; i < 2; i++) {
+ fname = sarrayGetString(sa, i, L_NOCOPY);
+ pixa2 = pixaRead(fname);
+ pixaSetText(pixa2, NULL, NULL);
+
+ /* Train a new recognizer from the boot and unlabeled samples */
+ pixa3 = recogTrainFromBoot(recogboot, pixa2, 0.65, 128, 1);
+ recog1 = recogCreateFromPixa(pixa3, 0, 40, linew, 128, 1);
+ recogShowContent(stderr, recog1, 2, 1);
+ if (i == 0)
+ recogWrite("/tmp/lept/recog/recog1.rec", recog1);
+ else /* i == 1 */
+ recogWrite("/tmp/lept/recog/recog2.rec", recog1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ recogDestroy(&recog1);
+ }
+ recogDestroy(&recogboot);
+
+ /* ----------------------------------------------------------- */
+ /* Do operations with a larger bootstrap recognizer */
+ /* ----------------------------------------------------------- */
+
+ /* Generate the boot recog, and show the unscaled and scaled
+ * versions of the templates */
+ recogboot = recogMakeBootDigitRecog(0, 40, linew, 1, 1);
+ recogWrite("/tmp/lept/recog/boot2.rec", recogboot);
+ recogShowContent(stderr, recogboot, 3, 1);
+
+ /* Generate a BAR for a set of images from one book.
+ * Select a set of digit images and erase the text field. */
+ for (i = 0; i < 2; i++) {
+ fname = sarrayGetString(sa, i, L_NOCOPY);
+ pixa2 = pixaRead(fname);
+ pixaSetText(pixa2, NULL, NULL);
+
+ /* Train a new recognizer from the boot and unlabeled samples */
+ pixa3 = recogTrainFromBoot(recogboot, pixa2, 0.65, 128, 1);
+ recog1 = recogCreateFromPixa(pixa3, 0, 40, linew, 128, 1);
+ recogShowContent(stderr, recog1, 4, 1);
+ if (i == 0)
+ recogWrite("/tmp/lept/recog/recog3.rec", recog1);
+ else if (i == 1)
+ recogWrite("/tmp/lept/recog/recog4.rec", recog1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ recogDestroy(&recog1);
+ }
+ recogDestroy(&recogboot);
+ sarrayDestroy(&sa);
+
+#if 0
+ recogShowMatchesInRange(recog, recog->pixa_tr, 0.0, 1.0, 1);
+ recogShowContent(stderr, recog, 1);
+
+ /* Now use minscore = 0.75 to remove the outliers in the BAR,
+ * and show what is left. */
+ lept_stderr("initial size: %d\n", recog->num_samples);
+ pix1 = pix2 = NULL;
+ recogRemoveOutliers1(&recog, 0.75, 5, 3, &pix1, &pix2);
+ pixDisplay(pix1, 500, 0);
+ pixDisplay(pix2, 500, 500);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ lept_stderr("final size: %d\n", recog->num_samples);
+ recogDebugAverages(&recog, 1);
+ recogShowContent(stderr, recog, 1);
+ recogShowMatchesInRange(recog, recog->pixa_tr, 0.75, 1.0, 1);
+ pixWrite("/tmp/lept/recog/range.png", recog->pixdb_range, IFF_PNG);
+#endif
+
+ /* ----------------------------------------------------------- */
+ /* Show operation of the default bootstrap recognizer */
+ /* ----------------------------------------------------------- */
+
+ recog1 = recogMakeBootDigitRecog(0, 40, 0, 1, 0);
+ pix1 = pixRead("test-87220.59.png");
+ recogIdentifyMultiple(recog1, pix1, 0, 1, &boxa1, NULL, NULL, 0);
+ sa = recogExtractNumbers(recog1, boxa1, 0.75, -1, &baa, &naa);
+ pixa1 = showExtractNumbers(pix1, sa, baa, naa, &pix3);
+ pix2 = pixaDisplayTiledInRows(pixa1, 32, 600, 1.0, 0, 20, 2);
+ pixDisplay(pix2, 0, 1000);
+ pixDisplay(pix3, 600, 1000);
+ pixWrite("/tmp/lept/recog/extract.png", pix3, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa1);
+ sarrayDestroy(&sa);
+ boxaDestroy(&boxa1);
+ boxaaDestroy(&baa);
+ numaaDestroy(&naa);
+ recogDestroy(&recog1);
+
+ return 0;
+}
diff --git a/leptonica/prog/recogtest3.c b/leptonica/prog/recogtest3.c
new file mode 100644
index 00000000..c54be397
--- /dev/null
+++ b/leptonica/prog/recogtest3.c
@@ -0,0 +1,182 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * recogtest3.c
+ *
+ * Test padding of book-adapted recognizer (BAR) using templates
+ * from a bootstrap recognizer (BSR) to identify unlabeled samples
+ * from the book.
+ *
+ * Terminology note:
+ * templates: labeled character images that can be inserted
+ * into a recognizer.
+ * samples: unlabeled character images that must be labeled by
+ * a recognizer before they can be used as templates.
+ *
+ * This demonstrates the following operations:
+ * (1) Making a BAR from labeled book templates (as a pixa).
+ * (2) Making a hybrid BAR/BSR from scaled templates in the BAR,
+ * supplemented with similarly scaled bootstrap templates for those
+ * classes where the BAR templates are either missing or not
+ * of sufficient quantity.
+ * (3) Using the BAR/BSR to label unlabeled book sampless.
+ * (4) Adding the pixa of the original set of labeled book
+ * templates to the pixa of the newly labeled templates, and
+ * making a BAR from the joined pixa. The BAR would then
+ * work to identify unscaled samples from the book.
+ * (5) Removing outliers from the BAR.
+ *
+ * Note that if this final BAR were not to have a sufficient number
+ * of templates in each class, it can again be augmented with BSR
+ * templates, and the hybrid BAR/BSR would be the final recognizer
+ * that is used to identify unknown (scaled) samples.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+char *text;
+l_int32 histo[10];
+l_int32 i, n, ival, same;
+PIX *pix1, *pix2;
+PIXA *pixa1, *pixa2, *pixa3, *pixa4;
+L_RECOG *recog1, *recog2, *recog3;
+
+ if (argc != 1) {
+ lept_stderr(" Syntax: recogtest3\n");
+ return 1;
+ }
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/recog");
+
+ /* Read templates and split them into two sets. Use one to
+ * make a BAR recog that needs padding; use the other with a
+ * hybrid BAR/BSR to make more labeled templates to augment
+ * the BAR */
+ pixa1 = pixaRead("recog/sets/train05.pa");
+ pixa2 = pixaCreate(0); /* to generate a small BAR */
+ pixa3 = pixaCreate(0); /* for templates to be labeled and
+ * added to the BAR */
+ n = pixaGetCount(pixa1);
+ for (i = 0; i < 10; i++)
+ histo[i] = 0;
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa1, i, L_COPY);
+ text = pixGetText(pix1);
+ ival = text[0] - '0';
+ /* remove all 4's, and all but 2 7's and 9's */
+ if (ival == 4 || (ival == 7 && histo[7] == 2) ||
+ (ival == 9 && histo[9] == 2)) {
+ pixaAddPix(pixa3, pix1, L_INSERT);
+ } else {
+ pixaAddPix(pixa2, pix1, L_INSERT);
+ histo[ival]++;
+ }
+ }
+ pix1 = pixaDisplayTiledWithText(pixa3, 1500, 1.0, 15, 2, 6, 0xff000000);
+ pixDisplay(pix1, 500, 0);
+ pixDestroy(&pix1);
+
+ /* Make a BAR from the small set */
+ recog1 = recogCreateFromPixa(pixa2, 0, 40, 0, 128, 1);
+ recogShowContent(stderr, recog1, 0, 1);
+
+ /* Pad with BSR templates to make a hybrid BAR/BSR */
+ recogPadDigitTrainingSet(&recog1, 40, 0);
+ recogShowContent(stderr, recog1, 1, 1);
+
+ /* Use the BAR/BSR to label the left-over templates from the book */
+ pixa4 = recogTrainFromBoot(recog1, pixa3, 0.75, 128, 1);
+
+ /* Join the two sets */
+ pixaJoin(pixa1, pixa4, 0, 0);
+ pixaDestroy(&pixa4);
+
+ /* Make a new BAR that uses unscaled templates.
+ * This now has all the templates from pixa1, before deletions */
+ recog2 = recogCreateFromPixa(pixa1, 0, 0, 5, 128, 1);
+ recogShowContent(stderr, recog2, 2, 1);
+
+ /* Test recog serialization */
+ recogWrite("/tmp/lept/recog/recog2.rec", recog2);
+ recog3 = recogRead("/tmp/lept/recog/recog2.rec");
+ recogWrite("/tmp/lept/recog/recog3.rec", recog3);
+ filesAreIdentical("/tmp/lept/recog/recog2.rec",
+ "/tmp/lept/recog/recog3.rec", &same);
+ if (!same)
+ lept_stderr("Error in serialization!\n");
+ recogDestroy(&recog3);
+
+ /* Remove outliers: method 1 */
+ pixa4 = pixaRemoveOutliers1(pixa1, 0.8, 4, 3, &pix1, &pix2);
+ pixDisplay(pix1, 500, 0);
+ pixDisplay(pix2, 500, 500);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ recog3 = recogCreateFromPixa(pixa4, 0, 0, 0, 128, 1);
+ recogShowContent(stderr, recog3, 3, 1);
+ pixaDestroy(&pixa4);
+ recogDestroy(&recog3);
+
+ /* Relabel a few templates to put them in the wrong classes */
+ pix1 = pixaGetPix(pixa1, 7, L_CLONE);
+ pixSetText(pix1, "4");
+ pixDestroy(&pix1);
+ pix1 = pixaGetPix(pixa1, 38, L_CLONE);
+ pixSetText(pix1, "9");
+ pixDestroy(&pix1);
+ pix1 = pixaGetPix(pixa1, 61, L_CLONE);
+ pixSetText(pix1, "2");
+ pixDestroy(&pix1);
+
+ /* Remove outliers: method 2 */
+ pixa4 = pixaRemoveOutliers2(pixa1, 0.65, 3, &pix1, &pix2);
+ pixDisplay(pix1, 900, 0);
+ pixDisplay(pix2, 900, 500);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ recog3 = recogCreateFromPixa(pixa4, 0, 0, 0, 128, 1);
+ recogShowContent(stderr, recog3, 3, 1);
+ pixaDestroy(&pixa4);
+ recogDestroy(&recog3);
+
+ recogDestroy(&recog1);
+ recogDestroy(&recog2);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ return 0;
+}
diff --git a/leptonica/prog/recogtest4.c b/leptonica/prog/recogtest4.c
new file mode 100644
index 00000000..594fe025
--- /dev/null
+++ b/leptonica/prog/recogtest4.c
@@ -0,0 +1,131 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * recogtest4.c
+ *
+ * Test document image decoding (DID) approach to splitting characters
+ * This tests the low-level recogDecode() function.
+ * Splitting succeeds for both with and without character height scaling.
+ *
+ * But cf. recogtest5.c. Note that recogIdentifyMultiple(), which
+ * does prefiltering and splitting before character identification,
+ * does not accept input that has been scaled. That is because
+ * the only reason for scaling the templates is that the recognizer
+ * is a hybrid BAR/BSR, where we've used a mixture of templates from
+ * a single source and bootstrap templates from many sources.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+static PIX *GetBigComponent(PIX *pixs);
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+char buf[256];
+l_int32 i, item;
+l_int32 example[6] = {17, 20, 21, 22, 23, 24}; /* for decoding */
+BOXA *boxa;
+PIX *pix1, *pix2, *pixdb;
+PIXA *pixa1, *pixa2;
+L_RECOG *recog;
+
+ if (argc != 1) {
+ lept_stderr(" Syntax: recogtest4\n");
+ return 1;
+ }
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/recog");
+
+ /* Generate the recognizer */
+ pixa1 = pixaRead("recog/sets/train01.pa");
+#if 1 /* scale to fixed height */
+ recog = recogCreateFromPixa(pixa1, 0, 40, 0, 128, 1);
+#else /* no scaling */
+ recog = recogCreateFromPixa(pixa1, 0, 0, 0, 128, 1);
+#endif
+ recogAverageSamples(&recog, 1);
+ recogWrite("/tmp/lept/recog/rec1.rec", recog);
+
+ /* Show the templates */
+ recogDebugAverages(&recog, 1);
+ if (!recog) {
+ lept_stderr("Averaging failed!!\n");
+ return 1;
+ }
+ recogShowMatchesInRange(recog, recog->pixa_tr, 0.0, 1.0, 1);
+
+ /* Get a set of problem images to decode */
+ pixa2 = pixaRead("recog/sets/test01.pa");
+
+
+ /* Decode a subset of them. It takes about 1 ms to decode a
+ * 4 digit number, with both Viterbi and rescoring (debug off). */
+ for (i = 0; i < 6; i++) {
+/* if (i != 3) continue; */ /* remove this comment to do all 6 */
+ item = example[i];
+ pix1 = pixaGetPix(pixa2, item, L_CLONE);
+ pixDisplay(pix1, 100, 100);
+ pix2 = GetBigComponent(pix1);
+ boxa = recogDecode(recog, pix2, 2, &pixdb);
+ pixDisplay(pixdb, 300, 100);
+ snprintf(buf, sizeof(buf), "/tmp/lept/recog/did-%d.png", item);
+ pixWrite(buf, pixdb, IFF_PNG);
+ pixDestroy(&pixdb);
+ boxaDestroy(&boxa);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ recogDestroy(&recog);
+ return 0;
+}
+
+static PIX *
+GetBigComponent(PIX *pixs)
+{
+BOX *box;
+PIX *pix1, *pixd;
+
+ pix1 = pixMorphSequence(pixs, "c40.7 + o20.15 + d25.1", 0);
+ pixClipToForeground(pix1, NULL, &box);
+ pixd = pixClipRectangle(pixs, box, NULL);
+ pixDestroy(&pix1);
+ boxDestroy(&box);
+ return pixd;
+}
+
+
diff --git a/leptonica/prog/recogtest5.c b/leptonica/prog/recogtest5.c
new file mode 100644
index 00000000..6f46c6a9
--- /dev/null
+++ b/leptonica/prog/recogtest5.c
@@ -0,0 +1,115 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * recogtest5.c
+ *
+ * Test document image decoding (DID) approach to splitting characters
+ *
+ * This uses recogIdentifyMultiple() to first split the touching
+ * characters and then do the identification on the resulting
+ * single characters. Compare with recogtest4.c.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+static PIX *GetBigComponent(PIX *pixs);
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i, item;
+l_int32 example[6] = {17, 20, 21, 22, 23, 24}; /* for decoding */
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa1, *pixa2, *pixa3;
+L_RECOG *recog;
+
+ if (argc != 1) {
+ lept_stderr(" Syntax: recogtest5\n");
+ return 1;
+ }
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/recog");
+
+ /* Generate the recognizer */
+ pixa1 = pixaRead("recog/sets/train01.pa");
+ recog = recogCreateFromPixa(pixa1, 0, 0, 0, 128, 1); /* no scaling */
+ recogAverageSamples(&recog, 1);
+ recogWrite("/tmp/lept/recog/rec1.rec", recog);
+
+ /* Show the templates */
+ recogDebugAverages(&recog, 1);
+ recogShowMatchesInRange(recog, recog->pixa_tr, 0.0, 1.0, 1);
+
+ /* Get a set of problem images to decode */
+ pixa2 = pixaRead("recog/sets/test01.pa");
+
+ /* Decode a subset of them. It takes about 2 ms to decode a
+ * 4 digit number (Viterbi for splitting; identification against
+ * all templates; debug off. */
+ for (i = 0; i < 6; i++) {
+/* if (i != 3) continue; */ /* remove this comment to do all 6 */
+ item = example[i];
+ pix1 = pixaGetPix(pixa2, item, L_CLONE);
+ pixDisplay(pix1, 100, 100);
+ pix2 = GetBigComponent(pix1);
+ recogIdentifyMultiple(recog, pix2, 0, 0, NULL, &pixa3, NULL, 1);
+ pix3 = pixaDisplayTiledInColumns(pixa3, 1, 1.0, 20, 2);
+ pixDisplay(pix3, 800, 100);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa3);
+ }
+
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ recogDestroy(&recog);
+ return 0;
+}
+
+static PIX *
+GetBigComponent(PIX *pixs)
+{
+BOX *box;
+PIX *pix1, *pixd;
+
+ pix1 = pixMorphSequence(pixs, "c40.7 + o20.15 + d25.1", 0);
+ pixClipToForeground(pix1, NULL, &box);
+ pixd = pixClipRectangle(pixs, box, NULL);
+ pixDestroy(&pix1);
+ boxDestroy(&box);
+ return pixd;
+}
+
+
diff --git a/leptonica/prog/recogtest6.c b/leptonica/prog/recogtest6.c
new file mode 100644
index 00000000..9efcc9ec
--- /dev/null
+++ b/leptonica/prog/recogtest6.c
@@ -0,0 +1,134 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * recogtest6.c
+ *
+ * Another test of character splitting. This will test both DID
+ * and greedy splitting. To test greedy splitting, in recogident.c,
+ * #define SPLIT_WITH_DID 0
+ *
+ * The timing info is used to measure the time to split touching
+ * characters and identify them. One set of 4 digits takes about 1 ms
+ * with DID and 7 ms with greedy splitting. Because DID is about
+ * 5x faster than greedy splitting, DID is the default that is used.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+static PIX *GetBigComponent(PIX *pixs);
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 item, debug, i;
+l_int32 example[6] = {17, 20, 21, 22, 23, 24}; /* for decoding */
+BOXA *boxa;
+NUMA *nascore;
+PIX *pix1, *pix2, *pix3, *pixdb;
+PIXA *pixa1, *pixa2;
+L_RECOG *recog;
+
+ if (argc != 1) {
+ lept_stderr(" Syntax: recogtest6\n");
+ return 1;
+ }
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/recog");
+
+ /* Generate the recognizer */
+ pixa1 = pixaRead("recog/sets/train01.pa");
+ recog = recogCreateFromPixa(pixa1, 0, 0, 0, 128, 1);
+ recogAverageSamples(&recog, 0);
+
+ /* Show the templates */
+ recogDebugAverages(&recog, 1);
+ recogShowMatchesInRange(recog, recog->pixa_tr, 0.0, 1.0, 1);
+
+ /* Get a set of problem images to decode */
+ pixa2 = pixaRead("recog/sets/test01.pa");
+
+ /* Decode a subset of them */
+ debug = 1;
+ for (i = 0; i < 6; i++) {
+/* if (i != 3) continue; */
+ item = example[i];
+ pix1 = pixaGetPix(pixa2, item, L_CLONE);
+ pixDisplay(pix1, 100, 100);
+ pix2 = GetBigComponent(pix1);
+ if (debug) {
+ recogIdentifyMultiple(recog, pix2, 0, 0, &boxa, NULL, &pixdb, 1);
+ rchaExtract(recog->rcha, NULL, &nascore, NULL, NULL,
+ NULL, NULL, NULL);
+ pixDisplay(pixdb, 300, 500);
+ boxaWriteStream(stderr, boxa);
+ numaWriteStream(stderr, nascore);
+ numaDestroy(&nascore);
+ pixDestroy(&pixdb);
+ } else { /* just get the timing */
+ startTimer();
+ recogIdentifyMultiple(recog, pix2, 0, 0, &boxa, NULL, NULL, 0);
+ lept_stderr("Time: %5.3f\n", stopTimer());
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxaDestroy(&boxa);
+ }
+ if (debug) {
+ pix3 = pixaDisplayTiledInRows(recog->pixadb_split, 1, 200,
+ 1.0, 0, 20, 3);
+ pixDisplay(pix3, 0, 0);
+ pixDestroy(&pix3);
+ }
+
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ recogDestroy(&recog);
+ return 0;
+}
+
+
+static PIX *
+GetBigComponent(PIX *pixs)
+{
+BOX *box;
+PIX *pix1, *pixd;
+
+ pix1 = pixMorphSequence(pixs, "c40.7 + o20.15 + d25.1", 0);
+ pixClipToForeground(pix1, NULL, &box);
+ pixd = pixClipRectangle(pixs, box, NULL);
+ pixDestroy(&pix1);
+ boxDestroy(&box);
+ return pixd;
+}
+
diff --git a/leptonica/prog/recogtest7.c b/leptonica/prog/recogtest7.c
new file mode 100644
index 00000000..575aae36
--- /dev/null
+++ b/leptonica/prog/recogtest7.c
@@ -0,0 +1,144 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * recogtest7.c
+ *
+ * Tests the boot recog utility using the bootstrap templates
+ * from the mosaics (bootnum4.pa) and from the stringcode version
+ * (bootnumgen4.c).
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "string.h"
+#include "allheaders.h"
+
+ /* All input templates are scaled to 20x30. Here, we rescale the
+ * height to 45 and let the width scale isotropically. */
+static const l_int32 scaledw = 0;
+static const l_int32 scaledh = 45;
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 same;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa1, *pixa2, *pixa3;
+L_RECOG *recog1, *recog2;
+
+ PROCNAME("recogtest7");
+
+ if (argc != 1) {
+ lept_stderr(" Syntax: recogtest7\n");
+ return 1;
+ }
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/digits");
+ recog1 = NULL;
+ recog2 = NULL;
+
+#if 1
+ pixa1 = pixaRead("recog/digits/bootnum4.pa");
+ pixa2 = pixaMakeFromTiledPixa(pixa1, 0, 0, 100);
+ pixa3 = l_bootnum_gen4(100);
+ pixaEqual(pixa2, pixa3, 0, NULL, &same);
+ if (!same) L_ERROR("Bad! The pixa differ!\n", procName);
+ pix1 = pixaDisplayTiledWithText(pixa1, 1400, 1.0, 10, 2, 6, 0xff000000);
+ pixDisplay(pix1, 100, 100);
+ pix2 = pixaDisplayTiledWithText(pixa2, 1400, 1.0, 10, 2, 6, 0xff000000);
+ pix3 = pixaDisplayTiledWithText(pixa3, 1400, 1.0, 10, 2, 6, 0xff000000);
+ pixEqual(pix2, pix3, &same);
+ if (!same) L_ERROR("Bad! The displayed pix differ!\n", procName);
+ pixWrite("/tmp/lept/digits/pix1.png", pix1, IFF_PNG);
+ pixWrite("/tmp/lept/digits/bootnum4.png", pix1, IFF_PNG);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+#endif
+
+#if 1
+ lept_stderr("Show recog content\n");
+ recog1 = recogCreateFromPixa(pixa3, scaledw, scaledh, 0, 120, 1);
+ recogShowContent(stderr, recog1, 1, 1);
+ pixaDestroy(&pixa3);
+#endif
+
+#if 1
+ lept_stderr("\nShow averaged samples\n");
+ recogAverageSamples(&recog1, 1);
+ recogShowAverageTemplates(recog1);
+ pix1 = pixaGetPix(recog1->pixadb_ave, 0, L_CLONE);
+ pixWrite("/tmp/lept/digits/unscaled_ave.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ pix1 = pixaGetPix(recog1->pixadb_ave, 1, L_CLONE);
+ pixWrite("/tmp/lept/digits/scaled_ave.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ recogDestroy(&recog1);
+#endif
+
+#if 1
+ /* Make a tiny recognizer and test it against itself */
+ pixa1 = l_bootnum_gen4(5);
+ pix1 = pixaDisplayTiledWithText(pixa1, 1400, 1.0, 10, 2, 6, 0xff000000);
+ pixDisplay(pix1, 1000, 100);
+ pixDestroy(&pix1);
+ recog1 = recogCreateFromPixa(pixa1, scaledw, scaledh, 0, 120, 1);
+ lept_stderr("\nShow matches against all inputs for given range\n");
+ recogDebugAverages(&recog1, 0);
+ recogShowMatchesInRange(recog1, recog1->pixa_tr, 0.85, 1.00, 1);
+ pixWrite("/tmp/lept/digits/match_input.png", recog1->pixdb_range, IFF_PNG);
+ lept_stderr("\nShow best match against average template\n");
+ recogShowMatchesInRange(recog1, recog1->pixa_u, 0.65, 1.00, 1);
+ pixWrite("/tmp/lept/digits/match_ave.png", recog1->pixdb_range, IFF_PNG);
+ pixaDestroy(&pixa1);
+#endif
+
+#if 1
+ lept_stderr("\nContents of recog before write/read:\n");
+ recogShowContent(stderr, recog1, 2, 1);
+
+ lept_stderr("\nTest serialization\n");
+ recogWrite("/tmp/lept/digits/rec1.rec", recog1);
+ recog2 = recogRead("/tmp/lept/digits/rec1.rec");
+ lept_stderr("Contents of recog after write/read:\n");
+ recogShowContent(stderr, recog2, 3, 1);
+ recogWrite("/tmp/lept/digits/rec2.rec", recog2);
+ filesAreIdentical("/tmp/lept/digits/rec1.rec",
+ "/tmp/lept/digits/rec2.rec", &same);
+ if (!same)
+ lept_stderr("Error in serialization!\n");
+ recogDestroy(&recog1);
+ recogDestroy(&recog2);
+#endif
+
+ return 0;
+}
diff --git a/leptonica/prog/rectangle_reg.c b/leptonica/prog/rectangle_reg.c
new file mode 100644
index 00000000..cfbdcb24
--- /dev/null
+++ b/leptonica/prog/rectangle_reg.c
@@ -0,0 +1,182 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * rectangle_reg.c
+ *
+ * Tests the largest rectangle in bg or fg.
+ *
+ * Also tests finding rectangles associated with single
+ * connected components.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32 NBoxes = 20;
+static const l_int32 Polarity = 0; /* background */
+
+int main(int argc,
+ char **argv)
+{
+char buf[64];
+char *newpath;
+l_int32 i, bx, by, bw, bh, index, rval, gval, bval;
+BOX *box1, *box2;
+BOXA *boxa;
+PIX *pixs, *pix1, *pix2, *pix3;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* ---------------- Largest rectangles in image ---------------- */
+ pixs = pixRead("test1.png");
+ pix1 = pixConvertTo8(pixs, FALSE);
+ cmap = pixcmapCreateRandom(8, 1, 1);
+ pixSetColormap(pix1, cmap);
+
+ boxa = boxaCreate(0);
+ for (i = 0; i < NBoxes; i++) {
+ pixFindLargestRectangle(pixs, Polarity, &box1, NULL);
+ boxGetGeometry(box1, &bx, &by, &bw, &bh);
+ pixSetInRect(pixs, box1);
+ if (rp->display)
+ lept_stderr("bx = %5d, by = %5d, bw = %5d, bh = %5d, area = %d\n",
+ bx, by, bw, bh, bw * bh);
+ boxaAddBox(boxa, box1, L_INSERT);
+ }
+
+ for (i = 0; i < NBoxes; i++) {
+ index = 32 + (i & 254);
+ pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+ box1 = boxaGetBox(boxa, i, L_CLONE);
+ pixRenderHashBoxArb(pix1, box1, 6, 2, L_NEG_SLOPE_LINE, 1,
+ rval, gval, bval);
+ boxDestroy(&box1);
+ }
+ pix2 = pixAddBorder(pix1, 2, 0x0);
+ pix3 = pixAddBorder(pix2, 20, 0xffffff00);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix3, 0, 0, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ boxaDestroy(&boxa);
+
+ /* ----------- Rectangle(s) from connected component ----------- */
+ pixs = pixRead("singlecc.tif");
+ pix1 = pixScale(pixs, 0.5, 0.5);
+ boxa = pixConnCompBB(pix1, 8);
+ box1 = boxaGetBox(boxa, 0, L_COPY);
+
+ /* Do 4 cases with vertical scan */
+ box2 = pixFindRectangleInCC(pix1, box1, 0.75, L_SCAN_VERTICAL,
+ L_GEOMETRIC_UNION, TRUE);
+ boxDestroy(&box2);
+ snprintf(buf, sizeof(buf), "rectangle.%02d.png", 2);
+ lept_cp("/tmp/lept/rect/fitrect.png", "lept/regout", buf, &newpath);
+ regTestCheckFile(rp, newpath); /* 1 */
+ if (rp->display) l_fileDisplay(newpath, 0, 500, 0.4);
+ lept_free(newpath);
+
+ box2 = pixFindRectangleInCC(pix1, box1, 0.75, L_SCAN_VERTICAL,
+ L_GEOMETRIC_INTERSECTION, TRUE);
+ boxDestroy(&box2);
+ snprintf(buf, sizeof(buf), "rectangle.%02d.png", 3);
+ lept_cp("/tmp/lept/rect/fitrect.png", "lept/regout", buf, &newpath);
+ regTestCheckFile(rp, newpath); /* 2 */
+ if (rp->display) l_fileDisplay(newpath, 200, 500, 0.4);
+ lept_free(newpath);
+
+ box2 = pixFindRectangleInCC(pix1, box1, 0.75, L_SCAN_VERTICAL,
+ L_LARGEST_AREA, TRUE);
+ boxDestroy(&box2);
+ snprintf(buf, sizeof(buf), "rectangle.%02d.png", 4);
+ lept_cp("/tmp/lept/rect/fitrect.png", "lept/regout", buf, &newpath);
+ regTestCheckFile(rp, newpath); /* 3 */
+ if (rp->display) l_fileDisplay(newpath, 400, 500, 0.4);
+ lept_free(newpath);
+
+ box2 = pixFindRectangleInCC(pix1, box1, 0.75, L_SCAN_VERTICAL,
+ L_SMALLEST_AREA, TRUE);
+ boxDestroy(&box2);
+ snprintf(buf, sizeof(buf), "rectangle.%02d.png", 5);
+ lept_cp("/tmp/lept/rect/fitrect.png", "lept/regout", buf, &newpath);
+ regTestCheckFile(rp, newpath); /* 4 */
+ if (rp->display) l_fileDisplay(newpath, 600, 500, 0.4);
+ lept_free(newpath);
+
+ /* Do 4 cases with horizontal scan */
+ box2 = pixFindRectangleInCC(pix1, box1, 0.75, L_SCAN_HORIZONTAL,
+ L_GEOMETRIC_UNION, TRUE);
+ boxDestroy(&box2);
+ snprintf(buf, sizeof(buf), "rectangle.%02d.png", 6);
+ lept_cp("/tmp/lept/rect/fitrect.png", "lept/regout", buf, &newpath);
+ regTestCheckFile(rp, newpath); /* 5 */
+ if (rp->display) l_fileDisplay(newpath, 800, 500, 0.4);
+ lept_free(newpath);
+
+ box2 = pixFindRectangleInCC(pix1, box1, 0.75, L_SCAN_HORIZONTAL,
+ L_GEOMETRIC_INTERSECTION, TRUE);
+ boxDestroy(&box2);
+ snprintf(buf, sizeof(buf), "rectangle.%02d.png", 7);
+ lept_cp("/tmp/lept/rect/fitrect.png", "lept/regout", buf, &newpath);
+ regTestCheckFile(rp, newpath); /* 6 */
+ if (rp->display) l_fileDisplay(newpath, 1000, 500, 0.4);
+ lept_free(newpath);
+
+ box2 = pixFindRectangleInCC(pix1, box1, 0.75, L_SCAN_HORIZONTAL,
+ L_LARGEST_AREA, TRUE);
+ boxDestroy(&box2);
+ snprintf(buf, sizeof(buf), "rectangle.%02d.png", 8);
+ lept_cp("/tmp/lept/rect/fitrect.png", "lept/regout", buf, &newpath);
+ regTestCheckFile(rp, newpath); /* 7 */
+ if (rp->display) l_fileDisplay(newpath, 1200, 500, 0.4);
+ lept_free(newpath);
+
+ box2 = pixFindRectangleInCC(pix1, box1, 0.75, L_SCAN_HORIZONTAL,
+ L_SMALLEST_AREA, TRUE);
+ boxDestroy(&box2);
+ snprintf(buf, sizeof(buf), "rectangle.%02d.png", 9);
+ lept_cp("/tmp/lept/rect/fitrect.png", "lept/regout", buf, &newpath);
+ regTestCheckFile(rp, newpath); /* 8 */
+ if (rp->display) l_fileDisplay(newpath, 1400, 500, 0.4);
+ lept_free(newpath);
+
+ boxDestroy(&box1);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ boxaDestroy(&boxa);
+ return regTestCleanup(rp);
+}
+
diff --git a/leptonica/prog/redcover.jpg b/leptonica/prog/redcover.jpg
new file mode 100644
index 00000000..5628ee9e
--- /dev/null
+++ b/leptonica/prog/redcover.jpg
Binary files differ
diff --git a/leptonica/prog/reducetest.c b/leptonica/prog/reducetest.c
new file mode 100644
index 00000000..7e60d64f
--- /dev/null
+++ b/leptonica/prog/reducetest.c
@@ -0,0 +1,75 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * reducetest.c
+ *
+ * Carries out a rank binary cascade of up to four 2x reductions.
+ * This requires all four rank levels to be input; to stop the
+ * cascade, use 0 for the final rank level(s).
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pixd;
+l_int32 level1, level2, level3, level4;
+char *filein, *fileout;
+static char mainName[] = "reducetest";
+
+ if (argc != 7)
+ return ERROR_INT(" Syntax: reducetest filein fileout l1 l2 l3 l4",
+ mainName, 1);
+ filein = argv[1];
+ fileout = argv[2];
+ level1 = atoi(argv[3]);
+ level2 = atoi(argv[4]);
+ level3 = atoi(argv[5]);
+ level4 = atoi(argv[6]);
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+#if 1
+ pixd = pixReduceRankBinaryCascade(pixs, level1, level2, level3, level4);
+#endif
+
+#if 0
+ pixd = pixReduce2(pixs, NULL);
+#endif
+
+ pixWrite(fileout, pixd, IFF_PNG);
+
+ return 0;
+}
+
diff --git a/leptonica/prog/reg_wrapper.sh b/leptonica/prog/reg_wrapper.sh
new file mode 100755
index 00000000..38502f5b
--- /dev/null
+++ b/leptonica/prog/reg_wrapper.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# This testing wrapper was written by James Le Cuirot.
+#
+# It runs all the programs in AUTO_REG_PROGS in Makefile.am
+# when the command 'make check' is invoked. This same set can
+# be run by doing:
+# alltests_reg generate
+# alltests_reg compare
+#
+# Some of the tests require gnuplot. These tests, listed below,
+# are skipped if gnuplot is not available. You can determine if a
+# test requires gnuplot, if any of these situations is true:
+# * a function starting with "gplot" is called
+# * a function starting with "boxaPlot" is called
+# * a function starting with "pixCompare" is called
+# * the function pixItalicWords() is called
+# * the function pixWordMaskByDilation() is called
+#
+# The wrapper receives several parameters in this form:
+# path/to/source/config/test-driver <TEST DRIVER ARGS> -- ./foo_reg
+#
+# Shell trickery is used to strip off the final parameter and
+# transform the invocation into this.
+# path/to/source/config/test-driver <TEST DRIVER ARGS>
+# -- /bin/sh -c "cd \"path/to/source/prog\" &&
+# \"path/to/build/prog/\"./foo_reg generate &&
+# \"path/to/build/prog/\"./foo_reg compare"
+#
+# This also allows testing when you build in a different directory
+# from the install directory, and the logs still get written to
+# the build directory.
+
+eval TEST=\${${#}}
+
+TEST_NAME="${TEST##*/}"
+TEST_NAME="${TEST_NAME%_reg*}"
+
+case "${TEST_NAME}" in
+ baseline|boxa[1234]|colormask|colorspace|crop|dna|enhance|extrema|fpix1|hash|italic|kernel|nearline|numa[123]|pixa1|projection|rank|rankbin|rankhisto|wordboxes)
+ GNUPLOT=$(which gnuplot || which wgnuplot)
+
+ if [ -z "${GNUPLOT}" ] || ! "${GNUPLOT}" -e "set terminal png" 2>/dev/null ; then
+ exec ${@%${TEST}} /bin/sh -c "exit 77"
+ fi
+esac
+
+exec ${@%${TEST}} /bin/sh -c "cd \"${srcdir}\" && \"${PWD}/\"${TEST} generate && \"${PWD}/\"${TEST} compare"
diff --git a/leptonica/prog/removecmap.c b/leptonica/prog/removecmap.c
new file mode 100644
index 00000000..7244da5d
--- /dev/null
+++ b/leptonica/prog/removecmap.c
@@ -0,0 +1,80 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * removecmap.c
+ *
+ * removecmap filein type fileout
+ *
+ * type: 1 for conversion to 8 bpp gray
+ * 2 for conversion to 24 bpp full color
+ * 3 for conversion depending on src
+ *
+ * Removes the colormap and does the conversion
+ * Works on palette images of 2, 4 and 8 bpp
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 type, numcolors;
+PIX *pixs, *pixd;
+PIXCMAP *cmap;
+static char mainName[] = "removecmap";
+
+ if (argc != 4)
+ return ERROR_INT("Syntax: removecmap filein type fileout",
+ mainName, 1);
+ filein = argv[1];
+ type = atoi(argv[2]);
+ fileout = argv[3];
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ lept_stderr(" depth = %d\n", pixGetDepth(pixs));
+ if ((cmap = pixGetColormap(pixs)) != NULL) {
+ numcolors = pixcmapGetCount(cmap);
+ pixcmapWriteStream(stderr, cmap);
+ lept_stderr(" colormap found; num colors = %d\n", numcolors);
+ } else {
+ lept_stderr(" no colormap\n");
+ }
+
+ pixd = pixRemoveColormap(pixs, type);
+ pixWrite(fileout, pixd, IFF_PNG);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
diff --git a/leptonica/prog/renderfonts.c b/leptonica/prog/renderfonts.c
new file mode 100644
index 00000000..f73f6451
--- /dev/null
+++ b/leptonica/prog/renderfonts.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * renderfonts.c
+ *
+ * This tests the font rendering functions
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define DIRECTORY "./fonts"
+
+int main(int argc,
+ char **argv)
+{
+char *textstr;
+l_int32 width, wtext, overflow;
+L_BMF *bmf;
+PIX *pixs, *pix;
+static char mainName[] = "renderfonts";
+
+ if (argc != 1)
+ return ERROR_INT("Syntax: renderfonts", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/render");
+
+ /* Render a character of text */
+ bmf = bmfCreate(DIRECTORY, 20);
+ pixs = pixRead("dreyfus8.png");
+ lept_stderr("n = %d\n", pixaGetCount(bmf->pixa));
+ pix = pixaGetPix(bmf->pixa, 6, L_CLONE);
+ pixSetMaskedGeneral(pixs, pix, 0x45, 140, 165);
+ pixWrite("/tmp/lept/render/char.png", pixs, IFF_PNG);
+ pixDisplay(pixs, 0, 0);
+ pixDestroy(&pix);
+ pixDestroy(&pixs);
+ bmfDestroy(&bmf);
+
+ /* Render a line of text */
+ bmf = bmfCreate(DIRECTORY, 8);
+ pixs = pixRead("marge.jpg");
+ bmfGetStringWidth(bmf, "This is a funny cat!", &width);
+ lept_stderr("String width: %d pixels\n", width);
+
+ pixSetTextline(pixs, bmf, "This is a funny cat!", 0x4080ff00, 50, 250,
+ &width, &overflow);
+ pixWrite("/tmp/lept/render/line.png", pixs, IFF_JFIF_JPEG);
+ pixDisplay(pixs, 450, 0);
+ lept_stderr("Text width = %d\n", width);
+ if (overflow)
+ lept_stderr("Text overflow beyond image boundary\n");
+ pixDestroy(&pixs);
+ bmfDestroy(&bmf);
+
+ /* Render a block of text */
+ bmf = bmfCreate(DIRECTORY, 10);
+ pixs = pixRead("marge.jpg");
+ textstr = stringNew("This is a cat! This is a funny cat! "
+ "This is a funny funny cat! This is a "
+ "funny funny funny cat!");
+
+ wtext = pixGetWidth(pixs) - 70;
+ pixSetTextblock(pixs, bmf, textstr, 0x90804000, 50, 50, wtext,
+ 1, &overflow);
+ pixWrite("/tmp/lept/render/block.png", pixs, IFF_JFIF_JPEG);
+ pixDisplay(pixs, 0, 500);
+ if (overflow)
+ lept_stderr("Text overflow beyond image boundary\n");
+ lept_free(textstr);
+ pixDestroy(&pixs);
+ bmfDestroy(&bmf);
+ return 0;
+}
+
diff --git a/leptonica/prog/replacebytes.c b/leptonica/prog/replacebytes.c
new file mode 100644
index 00000000..6e993e30
--- /dev/null
+++ b/leptonica/prog/replacebytes.c
@@ -0,0 +1,82 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * replacebytes.c
+ *
+ * Replaces the specified set of bytes in a file by the bytes in
+ * the input string. The general invocation is:
+ * relacebytes <filein> <start> <nbytes> <string> <fileout>
+ * where <start> is the start location in the file to begin replacing,
+ * <nbytes> is the number of bytes to be removed from the input,
+ * beginning at the start location, and
+ * <string> is the replacement string.
+ *
+ * To simply remove <nbytes> without replacing:
+ * relacebytes <filein> <start> <nbytes> <fileout>
+ *
+ * One use of the general case is for replacing the date/time in a
+ * pdf file by a string of 12 '0's. This removes the date without
+ * invalidating the byte counters:
+ * replacebytes <filein.pdf> 86 12 000000000000 <outfile.pdf>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+#include "string.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 start, nbytes;
+char *filein, *fileout, *newstr;
+static char mainName[] = "replacebytes";
+
+ if (argc != 5 && argc != 6)
+ return ERROR_INT(
+ "syntax: replacebytes filein start nbytes [string] fileout",
+ mainName, 1);
+ filein = argv[1];
+ start = atof(argv[2]);
+ nbytes = atof(argv[3]);
+ if (argc == 5) {
+ fileout = argv[4];
+ } else {
+ newstr = argv[4];
+ fileout = argv[5];
+ }
+
+ if (argc == 5) {
+ return fileReplaceBytes(filein, start, nbytes, NULL, 0, fileout);
+ } else { /* argc == 6 */
+ return fileReplaceBytes(filein, start, nbytes, (l_uint8 *)newstr,
+ strlen(newstr), fileout);
+ }
+}
+
diff --git a/leptonica/prog/rgb16.tif b/leptonica/prog/rgb16.tif
new file mode 100644
index 00000000..8b4e4e0f
--- /dev/null
+++ b/leptonica/prog/rgb16.tif
Binary files differ
diff --git a/leptonica/prog/rock.png b/leptonica/prog/rock.png
new file mode 100644
index 00000000..e7c084eb
--- /dev/null
+++ b/leptonica/prog/rock.png
Binary files differ
diff --git a/leptonica/prog/rotate1_reg.c b/leptonica/prog/rotate1_reg.c
new file mode 100644
index 00000000..536e0ee0
--- /dev/null
+++ b/leptonica/prog/rotate1_reg.c
@@ -0,0 +1,195 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * rotate1_reg.c
+ *
+ * Regression test for rotation by shear and area mapping.
+ * Displays results when images are rotated sequentially multiple times.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define BINARY_IMAGE "test1.png"
+#define TWO_BPP_IMAGE "weasel2.4c.png"
+#define FOUR_BPP_IMAGE1 "weasel4.11c.png"
+#define FOUR_BPP_IMAGE2 "weasel4.16g.png"
+#define EIGHT_BPP_IMAGE "test8.jpg"
+#define EIGHT_BPP_CMAP_IMAGE1 "dreyfus8.png"
+#define EIGHT_BPP_CMAP_IMAGE2 "test24.jpg"
+#define RGB_IMAGE "marge.jpg"
+
+static const l_int32 MODSIZE = 11; /* set to 11 for display */
+
+static const l_float32 ANGLE1 = 3.14159265 / 12.;
+static const l_float32 ANGLE2 = 3.14159265 / 120.;
+static const l_int32 NTIMES = 24;
+
+static void RotateTest(PIX *pixs, l_float32 scale, L_REGPARAMS *rp);
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+PIX *pixs, *pixd;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_stderr("Test binary image:\n");
+ pixs = pixRead(BINARY_IMAGE);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ lept_stderr("Test 2 bpp cmapped image with filled cmap:\n");
+ pixs = pixRead(TWO_BPP_IMAGE);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ lept_stderr("Test 4 bpp cmapped image with unfilled cmap:\n");
+ pixs = pixRead(FOUR_BPP_IMAGE1);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ lept_stderr("Test 4 bpp cmapped image with filled cmap:\n");
+ pixs = pixRead(FOUR_BPP_IMAGE2);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ lept_stderr("Test 8 bpp grayscale image:\n");
+ pixs = pixRead(EIGHT_BPP_IMAGE);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ lept_stderr("Test 8 bpp grayscale cmap image:\n");
+ pixs = pixRead(EIGHT_BPP_CMAP_IMAGE1);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ lept_stderr("Test 8 bpp color cmap image:\n");
+ pixs = pixRead(EIGHT_BPP_CMAP_IMAGE2);
+ pixd = pixOctreeColorQuant(pixs, 200, 0);
+ RotateTest(pixs, 0.25, rp);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+
+ lept_stderr("Test rgb image:\n");
+ pixs = pixRead(RGB_IMAGE);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ return regTestCleanup(rp);
+}
+
+
+static void
+RotateTest(PIX *pixs,
+ l_float32 scale,
+ L_REGPARAMS *rp)
+{
+l_int32 w, h, d, i, outformat;
+PIX *pixt, *pixd;
+PIXA *pixa;
+
+ pixa = pixaCreate(0);
+ pixGetDimensions(pixs, &w, &h, &d);
+ outformat = (d == 8 || d == 32) ? IFF_JFIF_JPEG : IFF_PNG;
+ pixd = pixRotate(pixs, ANGLE1, L_ROTATE_SHEAR, L_BRING_IN_WHITE, w, h);
+ for (i = 1; i < NTIMES; i++) {
+ if ((i % MODSIZE) == 0) {
+ pixaAddPix(pixa, pixd, L_COPY);
+ regTestWritePixAndCheck(rp, pixd, outformat);
+ }
+ pixt = pixRotate(pixd, ANGLE1, L_ROTATE_SHEAR,
+ L_BRING_IN_WHITE, w, h);
+ pixDestroy(&pixd);
+ pixd = pixt;
+ }
+ pixDestroy(&pixd);
+
+ pixd = pixRotate(pixs, ANGLE1, L_ROTATE_SAMPLING, L_BRING_IN_WHITE, w, h);
+ for (i = 1; i < NTIMES; i++) {
+ if ((i % MODSIZE) == 0) {
+ pixaAddPix(pixa, pixd, L_COPY);
+ regTestWritePixAndCheck(rp, pixd, outformat);
+ }
+ pixt = pixRotate(pixd, ANGLE1, L_ROTATE_SAMPLING,
+ L_BRING_IN_WHITE, w, h);
+ pixDestroy(&pixd);
+ pixd = pixt;
+ }
+ pixDestroy(&pixd);
+
+ pixd = pixRotate(pixs, ANGLE1, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, w, h);
+ for (i = 1; i < NTIMES; i++) {
+ if ((i % MODSIZE) == 0) {
+ pixaAddPix(pixa, pixd, L_COPY);
+ regTestWritePixAndCheck(rp, pixd, outformat);
+ }
+ pixt = pixRotate(pixd, ANGLE1, L_ROTATE_AREA_MAP,
+ L_BRING_IN_WHITE, w, h);
+ pixDestroy(&pixd);
+ pixd = pixt;
+ }
+ pixDestroy(&pixd);
+
+ pixd = pixRotateAMCorner(pixs, ANGLE2, L_BRING_IN_WHITE);
+ for (i = 1; i < NTIMES; i++) {
+ if ((i % MODSIZE) == 0) {
+ pixaAddPix(pixa, pixd, L_COPY);
+ regTestWritePixAndCheck(rp, pixd, outformat);
+ }
+ pixt = pixRotateAMCorner(pixd, ANGLE2, L_BRING_IN_WHITE);
+ pixDestroy(&pixd);
+ pixd = pixt;
+ }
+ pixDestroy(&pixd);
+
+ if (d == 32) {
+ pixd = pixRotateAMColorFast(pixs, ANGLE1, 0xb0ffb000);
+ for (i = 1; i < NTIMES; i++) {
+ if ((i % MODSIZE) == 0) {
+ pixaAddPix(pixa, pixd, L_COPY);
+ regTestWritePixAndCheck(rp, pixd, outformat);
+ }
+ pixt = pixRotateAMColorFast(pixd, ANGLE1, 0xb0ffb000);
+ pixDestroy(&pixd);
+ pixd = pixt;
+ }
+ }
+ pixDestroy(&pixd);
+
+ pixd = pixaDisplayTiledInColumns(pixa, 2, scale, 20, 0);
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ return;
+}
diff --git a/leptonica/prog/rotate2_reg.c b/leptonica/prog/rotate2_reg.c
new file mode 100644
index 00000000..7e6b4865
--- /dev/null
+++ b/leptonica/prog/rotate2_reg.c
@@ -0,0 +1,175 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * rotate2_reg.c
+ *
+ * Regression test for rotation by shear, sampling and area mapping.
+ * Displays results from all the various types of rotations.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define BINARY_IMAGE "test1.png"
+#define TWO_BPP_IMAGE "weasel2.4c.png"
+#define FOUR_BPP_IMAGE1 "weasel4.11c.png"
+#define FOUR_BPP_IMAGE2 "weasel4.16g.png"
+#define EIGHT_BPP_IMAGE "test8.jpg"
+#define EIGHT_BPP_CMAP_IMAGE1 "dreyfus8.png"
+#define EIGHT_BPP_CMAP_IMAGE2 "test24.jpg"
+#define RGB_IMAGE "marge.jpg"
+
+static const l_float32 ANGLE1 = 3.14159265 / 30.;
+static const l_float32 ANGLE2 = 3.14159265 / 7.;
+
+void RotateTest(PIX *pixs, l_float32 scale, L_REGPARAMS *rp);
+
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pixd;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_stderr("Test binary image:\n");
+ pixs = pixRead(BINARY_IMAGE);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ lept_stderr("Test 2 bpp cmapped image with filled cmap:\n");
+ pixs = pixRead(TWO_BPP_IMAGE);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ lept_stderr("Test 4 bpp cmapped image with unfilled cmap:\n");
+ pixs = pixRead(FOUR_BPP_IMAGE1);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ lept_stderr("Test 4 bpp cmapped image with filled cmap:\n");
+ pixs = pixRead(FOUR_BPP_IMAGE2);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ lept_stderr("Test 8 bpp grayscale image:\n");
+ pixs = pixRead(EIGHT_BPP_IMAGE);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ lept_stderr("Test 8 bpp grayscale cmap image:\n");
+ pixs = pixRead(EIGHT_BPP_CMAP_IMAGE1);
+ RotateTest(pixs, 1.0, rp);
+ pixDestroy(&pixs);
+
+ lept_stderr("Test 8 bpp color cmap image:\n");
+ pixs = pixRead(EIGHT_BPP_CMAP_IMAGE2);
+ pixd = pixOctreeColorQuant(pixs, 200, 0);
+ RotateTest(pixd, 0.5, rp);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+
+ lept_stderr("Test rgb image:\n");
+ pixs = pixRead(RGB_IMAGE);
+ RotateTest(pixs, 0.25, rp);
+ pixDestroy(&pixs);
+
+ return regTestCleanup(rp);
+}
+
+
+void
+RotateTest(PIX *pixs,
+ l_float32 scale,
+ L_REGPARAMS *rp)
+{
+l_int32 w, h, d, outformat;
+PIX *pix1, *pix2, *pix3, *pixd;
+PIXA *pixa;
+
+ pixGetDimensions(pixs, &w, &h, &d);
+ outformat = (d == 8 || d == 32) ? IFF_JFIF_JPEG : IFF_PNG;
+
+ pixa = pixaCreate(0);
+ pix1 = pixRotate(pixs, ANGLE1, L_ROTATE_SHEAR, L_BRING_IN_WHITE, w, h);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixRotate(pixs, ANGLE1, L_ROTATE_SHEAR, L_BRING_IN_BLACK, w, h);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix1 = pixRotate(pixs, ANGLE1, L_ROTATE_SHEAR, L_BRING_IN_WHITE, 0, 0);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixRotate(pixs, ANGLE1, L_ROTATE_SHEAR, L_BRING_IN_BLACK, 0, 0);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix1 = pixRotate(pixs, ANGLE2, L_ROTATE_SHEAR, L_BRING_IN_WHITE, w, h);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixRotate(pixs, ANGLE2, L_ROTATE_SHEAR, L_BRING_IN_BLACK, w, h);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix1 = pixRotate(pixs, ANGLE2, L_ROTATE_SHEAR, L_BRING_IN_WHITE, 0, 0);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixRotate(pixs, ANGLE2, L_ROTATE_SHEAR, L_BRING_IN_BLACK, 0, 0);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixd = pixaDisplayTiledInColumns(pixa, 2, scale, 20, 0);
+ regTestWritePixAndCheck(rp, pixd, outformat);
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ pixa = pixaCreate(0);
+ pix1 = pixRotate(pixs, ANGLE2, L_ROTATE_SAMPLING, L_BRING_IN_WHITE, w, h);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixRotate(pixs, ANGLE2, L_ROTATE_SAMPLING, L_BRING_IN_BLACK, w, h);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix1 = pixRotate(pixs, ANGLE2, L_ROTATE_SAMPLING, L_BRING_IN_WHITE, 0, 0);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixRotate(pixs, ANGLE2, L_ROTATE_SAMPLING, L_BRING_IN_BLACK, 0, 0);
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ if (pixGetDepth(pixs) == 1)
+ pix1 = pixScaleToGray2(pixs);
+ else
+ pix1 = pixClone(pixs);
+ pix2 = pixRotate(pix1, ANGLE2, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, w, h);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix3 = pixRotate(pix1, ANGLE2, L_ROTATE_AREA_MAP, L_BRING_IN_BLACK, w, h);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix2 = pixRotate(pix1, ANGLE2, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, 0, 0);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix3 = pixRotate(pix1, ANGLE2, L_ROTATE_AREA_MAP, L_BRING_IN_BLACK, 0, 0);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ pixd = pixaDisplayTiledInColumns(pixa, 2, scale, 20, 0);
+ regTestWritePixAndCheck(rp, pixd, outformat);
+ pixDisplayWithTitle(pixd, 600, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ return;
+}
diff --git a/leptonica/prog/rotate_it.c b/leptonica/prog/rotate_it.c
new file mode 100644
index 00000000..37ff3bc6
--- /dev/null
+++ b/leptonica/prog/rotate_it.c
@@ -0,0 +1,113 @@
+/*====================================================================*
+ - 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_it.c
+ *
+ * rotate_it filein angle fileout [type incolor]
+ *
+ * where:
+ * angle: in degrees; use 90, 180, 270 for orthogonal rotation
+ * type: "areamap", "shear", "sampling"
+ * incolor: "black", "white"
+ *
+ * If 'type' and 'incolor' are omitted, by default we use:
+ * type: sampling for 1 bpp; areamap for bpp > 1
+ * incolor: white
+ *
+ * If angle is in {90.0, 180.0, 270.0}, this does an orthogonal
+ * rotation. Args 'type' and 'incolor' can be omitted.
+ *
+ * This writes the output file in the same encoded format as
+ * the input file. If the input file is jpeg, the output file
+ * is written with default quality factor 75.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 icolor, itype, format, quads;
+l_float32 angle, deg2rad, anglerad;
+char *filein, *fileout, *type, *incolor;
+PIX *pixs, *pixd;
+static char mainName[] = "rotate_it";
+
+ if (argc != 4 && argc != 6)
+ return ERROR_INT(
+ "\n Syntax: rotate_it filein angle fileout [type incolor]",
+ mainName, 1);
+ filein = argv[1];
+ angle = atof(argv[2]);
+ fileout = argv[3];
+ if (argc == 6) {
+ type = argv[4];
+ incolor = argv[5];
+ }
+
+ setLeptDebugOK(1);
+ deg2rad = 3.1415926535 / 180.;
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+ format = pixGetInputFormat(pixs);
+ if (format == IFF_UNKNOWN) format = IFF_PNG;
+
+ if (angle == 90.0 || angle == 180.0 || angle == 270.0) {
+ quads = (l_int32)((angle + 0.5) / 90.0);
+ pixd = pixRotateOrth(pixs, quads);
+ pixWrite(fileout, pixd, format);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+ }
+
+ anglerad = deg2rad * angle;
+ icolor = L_BRING_IN_WHITE;
+ itype = L_ROTATE_AREA_MAP;
+ if (argc == 6) {
+ icolor = (!strcmp(incolor, "white")) ? L_BRING_IN_WHITE
+ : L_BRING_IN_BLACK;
+ if (!strcmp(type, "areamap"))
+ itype = L_ROTATE_AREA_MAP;
+ else if (!strcmp(type, "shear"))
+ itype = L_ROTATE_SHEAR;
+ else
+ itype = L_ROTATE_SAMPLING;
+ }
+
+ pixd = pixRotate(pixs, anglerad, itype, icolor, 0, 0);
+ pixWrite(fileout, pixd, format);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
diff --git a/leptonica/prog/rotatefastalt.c b/leptonica/prog/rotatefastalt.c
new file mode 100644
index 00000000..6dbda05d
--- /dev/null
+++ b/leptonica/prog/rotatefastalt.c
@@ -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.
+ *====================================================================*/
+
+
+/*
+ * rotatefastalt.c
+ *
+ * Alternative (slightly slower) method for rotating color images,
+ * with antialiasing. This is here just for comparison with
+ * the better methods in the library.
+ *
+ * Includes these functions:
+ * pixRotateAMColorFast2()
+ * pixShiftRGB258()
+ * rotateAMColorFastLow2()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h> /* required for sin and tan */
+#include "allheaders.h"
+
+static const l_float32 VERY_SMALL_ANGLE = 0.001; /* radians; ~0.06 degrees */
+
+static PIX *pixRotateAMColorFast2(PIX *pixs, l_float32 angle, l_uint8 grayval);
+static PIX *pixShiftRGB258(PIX *pixs);
+static void rotateAMColorFastLow2(l_uint32 *datad, l_int32 w, l_int32 h,
+ l_int32 wpld, l_uint32 *datas,
+ l_int32 wpls, l_float32 angle,
+ l_uint8 grayval);
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_float32 angle, deg2rad;
+PIX *pixs, *pixd;
+static char mainName[] = "rotatefastalt";
+
+ if (argc != 4)
+ return ERROR_INT("Syntax: rotatefastalt filein angle fileout",
+ mainName, 1);
+ filein = argv[1];
+ angle = atof(argv[2]);
+ fileout = argv[3];
+
+ setLeptDebugOK(1);
+ deg2rad = 3.1415926535 / 180.;
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not read", mainName, 1);
+
+ startTimer();
+ pixd = pixRotateAMColorFast2(pixs, deg2rad * angle, 255);
+ lept_stderr("Time for rotation: %7.3f sec\n", stopTimer());
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
+
+
+/*!
+ * pixRotateAMColorFast2()
+ *
+ * Input: pixs
+ * angle (radians; clockwise is positive)
+ * grayval (0 to bring in BLACK, 255 for WHITE)
+ * Return: pixd, or null on error
+ *
+ * Notes:
+ * - This rotates a color image about the image center.
+ * A positive angle gives a clockwise rotation.
+ * - It uses area mapping, dividing each pixel into
+ * 16 subpixels.
+ * - It creates a temporary 32-bit color image.
+ * - It is slightly slower than pixRotateAMColorFast(),
+ * which uses less memory because it does not create
+ * a temporary image.
+ *
+ * *** Warning: implicit assumption about RGB component ordering ***
+ */
+PIX *
+pixRotateAMColorFast2(PIX *pixs,
+ l_float32 angle,
+ l_uint8 grayval)
+{
+l_int32 w, h, wpls, wpld;
+l_uint32 *datas, *datad;
+PIX *pixshft, *pixd;
+
+ PROCNAME("pixRotateAMColorFast2");
+
+ 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) < VERY_SMALL_ANGLE)
+ return pixClone(pixs);
+
+ if ((pixshft = pixShiftRGB258(pixs)) == NULL)
+ return (PIX *)ERROR_PTR("pixshft not defined", procName, NULL);
+
+ w = pixGetWidth(pixshft);
+ h = pixGetHeight(pixshft);
+ datas = pixGetData(pixshft);
+ wpls = pixGetWpl(pixshft);
+ pixd = pixCreateTemplate(pixshft);
+ datad = pixGetData(pixd);
+ wpld = pixGetWpl(pixd);
+ rotateAMColorFastLow2(datad, w, h, wpld, datas, wpls, angle, grayval);
+
+ pixDestroy(&pixshft);
+ return pixd;
+}
+
+
+/*!
+ * pixShiftRGB258()
+ *
+ * Makes a new 32 bpp image with the R, G and B components
+ * right-shifted by 2, 5 and 8 bits, respectively.
+ */
+PIX *
+pixShiftRGB258(PIX *pixs)
+{
+l_int32 w, h, wpls, wpld, i, j;
+l_uint32 word;
+l_uint32 *datas, *datad, *lines, *lined;
+PIX *pixd;
+
+ PROCNAME("pixShift258");
+
+ 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);
+ w = pixGetWidth(pixs);
+ h = pixGetHeight(pixs);
+ wpls = pixGetWpl(pixs);
+ datas = pixGetData(pixs);
+
+ if ((pixd = pixCreate(w, h, 32)) == NULL)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ wpld = pixGetWpl(pixd);
+ datad = pixGetData(pixd);
+
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ lined = datad + i * wpld;
+ for (j = 0; j < w; j++) {
+ word = *(lines + j);
+ *(lined + j) = ((word & 0xff000000) >> 2) |
+ ((word & 0x00ff0000) >> 5) |
+ ((word & 0x0000ff00) >> 8);
+ }
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * rotateAMColorFastLow2()
+ *
+ * Alternative version for fast color rotation
+ *
+ * *** Warning: explicit assumption about RGB component ordering ***
+ */
+void
+rotateAMColorFastLow2(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_uint32 edgeval, word;
+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);
+
+ edgeval = (grayval << 24) | (grayval << 16) | (grayval << 8);
+ 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 + 0.5);
+ ypm = (l_int32)(-ydif * cosa + xdif * sina + 0.5);
+ xp = xcen + (xpm >> 2);
+ yp = ycen + (ypm >> 2);
+ xf = xpm & 0x03;
+ yf = ypm & 0x03;
+
+ /* if off the edge, write the input grayval */
+ if (xp < 0 || yp < 0 || xp > wm2 || yp > hm2) {
+ *(lined + j) = edgeval;
+ continue;
+ }
+
+ lines = datas + yp * wpls;
+ pword = lines + xp;
+
+ switch (xf + 4 * yf)
+ {
+ case 0:
+ word = *pword;
+ *(lined + j) = ((word & 0x3fc00000) << 2) |
+ ((word & 0x0007f800) << 5) |
+ ((word & 0x000000ff) << 8);
+ break;
+ case 1:
+ word = 3 * (*pword) + *(pword + 1);
+ *(lined + j) = (word & 0xff000000) |
+ ((word & 0x001fe000) << 3) |
+ ((word & 0x000003fc) << 6);
+ break;
+ case 2:
+ word = *pword + *(pword + 1);
+ *(lined + j) = ((word & 0x7f800000) << 1) |
+ ((word & 0x000ff000) << 4) |
+ ((word & 0x000001fe) << 7);
+ break;
+ case 3:
+ word = *pword + 3 * (*(pword + 1));
+ *(lined + j) = (word & 0xff000000) |
+ ((word & 0x001fe000) << 3) |
+ ((word & 0x000003fc) << 6);
+ break;
+ case 4:
+ word = 3 * (*pword) + *(pword + wpls);
+ *(lined + j) = (word & 0xff000000) |
+ ((word & 0x001fe000) << 3) |
+ ((word & 0x000003fc) << 6);
+ break;
+ case 5:
+ word = 2 * (*pword) + *(pword + 1) + *(pword + wpls);
+ *(lined + j) = (word & 0xff000000) |
+ ((word & 0x001fe000) << 3) |
+ ((word & 0x000003fc) << 6);
+ break;
+ case 6:
+ word = *pword + *(pword + 1);
+ *(lined + j) = ((word & 0x7f800000) << 1) |
+ ((word & 0x000ff000) << 4) |
+ ((word & 0x000001fe) << 7);
+ break;
+ case 7:
+ word = *pword + 2 * (*(pword + 1)) + *(pword + wpls + 1);
+ *(lined + j) = (word & 0xff000000) |
+ ((word & 0x001fe000) << 3) |
+ ((word & 0x000003fc) << 6);
+ break;
+ case 8:
+ word = *pword + *(pword + wpls);
+ *(lined + j) = ((word & 0x7f800000) << 1) |
+ ((word & 0x000ff000) << 4) |
+ ((word & 0x000001fe) << 7);
+ break;
+ case 9:
+ word = *pword + *(pword + wpls);
+ *(lined + j) = ((word & 0x7f800000) << 1) |
+ ((word & 0x000ff000) << 4) |
+ ((word & 0x000001fe) << 7);
+ break;
+ case 10:
+ word = *pword + *(pword + 1) + *(pword + wpls) +
+ *(pword + wpls + 1);
+ *(lined + j) = (word & 0xff000000) |
+ ((word & 0x001fe000) << 3) |
+ ((word & 0x000003fc) << 6);
+ break;
+ case 11:
+ word = *(pword + 1) + *(pword + wpls + 1);
+ *(lined + j) = ((word & 0x7f800000) << 1) |
+ ((word & 0x000ff000) << 4) |
+ ((word & 0x000001fe) << 7);
+ break;
+ case 12:
+ word = *pword + 3 * (*(pword + wpls));
+ *(lined + j) = (word & 0xff000000) |
+ ((word & 0x001fe000) << 3) |
+ ((word & 0x000003fc) << 6);
+ break;
+ case 13:
+ word = *pword + 2 * (*(pword + wpls)) + *(pword + wpls + 1);
+ *(lined + j) = (word & 0xff000000) |
+ ((word & 0x001fe000) << 3) |
+ ((word & 0x000003fc) << 6);
+ break;
+ case 14:
+ word = *(pword + wpls) + *(pword + wpls + 1);
+ *(lined + j) = ((word & 0x7f800000) << 1) |
+ ((word & 0x000ff000) << 4) |
+ ((word & 0x000001fe) << 7);
+ break;
+ case 15:
+ word = *(pword + 1) + *(pword + wpls) +
+ 2 * (*(pword + wpls + 1));
+ *(lined + j) = (word & 0xff000000) |
+ ((word & 0x001fe000) << 3) |
+ ((word & 0x000003fc) << 6);
+ break;
+ default: /* for testing only; no interpolation, no shift */
+ lept_stderr("shouldn't get here\n");
+ *(lined + j) = *pword;
+ break;
+ }
+ }
+ }
+
+ return;
+}
diff --git a/leptonica/prog/rotateorth_reg.c b/leptonica/prog/rotateorth_reg.c
new file mode 100644
index 00000000..01bc5eb2
--- /dev/null
+++ b/leptonica/prog/rotateorth_reg.c
@@ -0,0 +1,146 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Regression test for all rotateorth functions
+ */
+
+#include "allheaders.h"
+
+#define BINARY_IMAGE "test1.png"
+#define FOUR_BPP_IMAGE "weasel4.8g.png"
+#define GRAYSCALE_IMAGE "test8.jpg"
+#define COLORMAP_IMAGE "dreyfus8.png"
+#define RGB_IMAGE "marge.jpg"
+
+void RotateOrthTest(PIX *pix, L_REGPARAMS *rp);
+
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_stderr("\nTest binary image:\n");
+ pixs = pixRead(BINARY_IMAGE);
+ RotateOrthTest(pixs, rp);
+ pixDestroy(&pixs);
+ lept_stderr("\nTest 4 bpp colormapped image:\n");
+ pixs = pixRead(FOUR_BPP_IMAGE);
+ RotateOrthTest(pixs, rp);
+ pixDestroy(&pixs);
+ lept_stderr("\nTest grayscale image:\n");
+ pixs = pixRead(GRAYSCALE_IMAGE);
+ RotateOrthTest(pixs, rp);
+ pixDestroy(&pixs);
+ lept_stderr("\nTest colormap image:\n");
+ pixs = pixRead(COLORMAP_IMAGE);
+ RotateOrthTest(pixs, rp);
+ pixDestroy(&pixs);
+ lept_stderr("\nTest rgb image:\n");
+ pixs = pixRead(RGB_IMAGE);
+ RotateOrthTest(pixs, rp);
+ pixDestroy(&pixs);
+
+ return regTestCleanup(rp);
+}
+
+
+void
+RotateOrthTest(PIX *pixs,
+ L_REGPARAMS *rp)
+{
+l_int32 zero, count;
+PIX *pixt, *pixd;
+
+ /* Test 4 successive 90 degree rotations */
+ pixt = pixRotate90(pixs, 1);
+ pixd = pixRotate90(pixt, 1);
+ pixDestroy(&pixt);
+ pixt = pixRotate90(pixd, 1);
+ pixDestroy(&pixd);
+ pixd = pixRotate90(pixt, 1);
+ pixDestroy(&pixt);
+ regTestComparePix(rp, pixs, pixd);
+ pixXor(pixd, pixd, pixs);
+ pixZero(pixd, &zero);
+ if (zero) {
+ lept_stderr("OK. Four 90-degree rotations gives I\n");
+ } else {
+ pixCountPixels(pixd, &count, NULL);
+ lept_stderr("Failure for four 90-degree rots; count = %d\n", count);
+ }
+ pixDestroy(&pixd);
+
+ /* Test 2 successive 180 degree rotations */
+ pixt = pixRotate180(NULL, pixs);
+ pixRotate180(pixt, pixt);
+ regTestComparePix(rp, pixs, pixt);
+ pixXor(pixt, pixt, pixs);
+ pixZero(pixt, &zero);
+ if (zero) {
+ lept_stderr("OK. Two 180-degree rotations gives I\n");
+ } else {
+ pixCountPixels(pixt, &count, NULL);
+ lept_stderr("Failure for two 180-degree rots; count = %d\n", count);
+ }
+ pixDestroy(&pixt);
+
+ /* Test 2 successive LR flips */
+ pixt = pixFlipLR(NULL, pixs);
+ pixFlipLR(pixt, pixt);
+ regTestComparePix(rp, pixs, pixt);
+ pixXor(pixt, pixt, pixs);
+ pixZero(pixt, &zero);
+ if (zero) {
+ lept_stderr("OK. Two LR flips gives I\n");
+ } else {
+ pixCountPixels(pixt, &count, NULL);
+ lept_stderr("Failure for two LR flips; count = %d\n", count);
+ }
+ pixDestroy(&pixt);
+
+ /* Test 2 successive TB flips */
+ pixt = pixFlipTB(NULL, pixs);
+ pixFlipTB(pixt, pixt);
+ regTestComparePix(rp, pixs, pixt);
+ pixXor(pixt, pixt, pixs);
+ pixZero(pixt, &zero);
+ if (zero) {
+ lept_stderr("OK. Two TB flips gives I\n");
+ } else {
+ pixCountPixels(pixt, &count, NULL);
+ lept_stderr("Failure for two TB flips; count = %d\n", count);
+ }
+ pixDestroy(&pixt);
+ return;
+}
diff --git a/leptonica/prog/rotateorthtest1.c b/leptonica/prog/rotateorthtest1.c
new file mode 100644
index 00000000..ac79d31b
--- /dev/null
+++ b/leptonica/prog/rotateorthtest1.c
@@ -0,0 +1,145 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * rotateorthtest1.c
+ *
+ * Tests and timings for 90 and 180 degree rotations
+ * rotateorthtest1 filein fileout [direction]
+ * where
+ * direction = 1 for cw; -1 for ccw
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define NTIMES 10
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 dir;
+PIX *pixs, *pixd, *pixt;
+l_float32 pops;
+char *filein, *fileout;
+static char mainName[] = "rotateorthtest1";
+
+ if (argc != 3 && argc != 4)
+ return ERROR_INT(" Syntax: rotateorthtest1 filein fileout [direction]",
+ mainName, 1);
+ filein = argv[1];
+ fileout = argv[2];
+ if (argc == 4)
+ dir = atoi(argv[3]);
+ else
+ dir = 1;
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pix not made", mainName, 1);
+
+ /* Do a single operation */
+#if 1
+ pixd = pixRotate90(pixs, dir);
+#elif 0
+ pixd = pixRotate180(NULL, pixs);
+#elif 0
+ pixd = pixRotateLR(NULL, pixs);
+#elif 0
+ pixd = pixRotateTB(NULL, pixs);
+#endif
+
+ /* Time rotate 90, allocating & destroying each time */
+#if 0
+ startTimer();
+ w = pixGetWidth(pixs);
+ h = pixGetHeight(pixs);
+ for (i = 0; i < NTIMES; i++) {
+ pixd = pixRotate90(pixs, dir);
+ pixDestroy(&pixd);
+ }
+ pops = (l_float32)(w * h * NTIMES) / stopTimer();
+ lept_stderr("MPops for 90 rotation: %7.3f\n", pops / 1000000.);
+ pixd = pixRotate90(pixs, dir);
+#endif
+
+ /* Time rotate 180, with no alloc/destroy */
+#if 0
+ startTimer();
+ w = pixGetWidth(pixs);
+ h = pixGetHeight(pixs);
+ pixd = pixCreateTemplate(pixs);
+ for (i = 0; i < NTIMES; i++)
+ pixRotate180(pixd, pixs);
+ pops = (l_float32)(w * h * NTIMES) / stopTimer();
+ lept_stderr("MPops for 180 rotation: %7.3f\n", pops / 1000000.);
+#endif
+
+
+ /* Test rotate 180 not in-place */
+#if 0
+ pixt = pixRotate180(NULL, pixs);
+ pixd = pixRotate180(NULL, pixt);
+ pixEqual(pixs, pixd, &eq);
+ if (eq) lept_stderr("2 rots gives I\n");
+ else lept_stderr("2 rots fail to give I\n");
+ pixDestroy(&pixt);
+#endif
+
+ /* Test rotate 180 in-place */
+#if 0
+ pixd = pixCopy(NULL, pixs);
+ pixRotate180(pixd, pixd);
+ pixRotate180(pixd, pixd);
+ pixEqual(pixs, pixd, &eq);
+ if (eq) lept_stderr("2 rots gives I\n");
+ else lept_stderr("2 rots fail to give I\n");
+#endif
+
+ /* Mix rotate 180 with LR/TB */
+#if 0
+ pixd = pixRotate180(NULL, pixs);
+ pixRotateLR(pixd, pixd);
+ pixRotateTB(pixd, pixd);
+ pixEqual(pixs, pixd, &eq);
+ if (eq) lept_stderr("180 rot OK\n");
+ else lept_stderr("180 rot error\n");
+#endif
+
+ if (pixGetDepth(pixd) < 8)
+ pixWrite(fileout, pixd, IFF_PNG);
+ else
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
+
diff --git a/leptonica/prog/rotatetest1.c b/leptonica/prog/rotatetest1.c
new file mode 100644
index 00000000..18c6c109
--- /dev/null
+++ b/leptonica/prog/rotatetest1.c
@@ -0,0 +1,224 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * rotatetest1.c
+ *
+ * rotatetest1 filein angle(in degrees) fileout
+ */
+
+#include "allheaders.h"
+
+#define NTIMES 180
+#define NITERS 3
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, w, h, d, rotflag;
+PIX *pixs, *pixt, *pixd;
+l_float32 angle, deg2rad, ang;
+char *filein, *fileout;
+static char mainName[] = "rotatetest1";
+
+ if (argc != 4)
+ return ERROR_INT(" Syntax: rotatetest1 filein angle fileout",
+ mainName, 1);
+ filein = argv[1];
+ angle = atof(argv[2]);
+ fileout = argv[3];
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/rotate");
+
+ deg2rad = 3.1415926535 / 180.;
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pix not made", mainName, 1);
+ if (pixGetDepth(pixs) == 1) {
+ pixt = pixScaleToGray3(pixs);
+ pixDestroy(&pixs);
+ pixs = pixAddBorderGeneral(pixt, 1, 0, 1, 0, 255);
+ pixDestroy(&pixt);
+ }
+
+ pixGetDimensions(pixs, &w, &h, &d);
+ lept_stderr("w = %d, h = %d\n", w, h);
+
+#if 0
+ /* repertory of rotation operations to choose from */
+ pixd = pixRotateAM(pixs, deg2rad * angle, L_BRING_IN_WHITE);
+ pixd = pixRotateAMColor(pixs, deg2rad * angle, 0xffffff00);
+ pixd = pixRotateAMColorFast(pixs, deg2rad * angle, 255);
+ pixd = pixRotateAMCorner(pixs, deg2rad * angle, L_BRING_IN_WHITE);
+ pixd = pixRotateShear(pixs, w /2, h / 2, deg2rad * angle,
+ L_BRING_IN_WHITE);
+ pixd = pixRotate3Shear(pixs, w /2, h / 2, deg2rad * angle,
+ L_BRING_IN_WHITE);
+ pixRotateShearIP(pixs, w / 2, h / 2, deg2rad * angle); pixd = pixs;
+#endif
+
+#if 0
+ /* timing of shear rotation */
+ for (i = 0; i < NITERS; i++) {
+ pixd = pixRotateShear(pixs, (i * w) / NITERS,
+ (i * h) / NITERS, deg2rad * angle,
+ L_BRING_IN_WHITE);
+ pixDisplay(pixd, 100 + 20 * i, 100 + 20 * i);
+ pixDestroy(&pixd);
+ }
+#endif
+
+#if 0
+ /* timing of in-place shear rotation */
+ for (i = 0; i < NITERS; i++) {
+ pixRotateShearIP(pixs, w/2, h/2, deg2rad * angle, L_BRING_IN_WHITE);
+/* pixRotateShearCenterIP(pixs, deg2rad * angle, L_BRING_IN_WHITE); */
+ pixDisplay(pixs, 100 + 20 * i, 100 + 20 * i);
+ }
+ pixd = pixs;
+ if (pixGetDepth(pixd) == 1)
+ pixWrite(fileout, pixd, IFF_PNG);
+ else
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixs);
+#endif
+
+#if 0
+{
+ l_float32 pops;
+
+ /* timing of various rotation operations (choose) */
+ startTimer();
+ w = pixGetWidth(pixs);
+ h = pixGetHeight(pixs);
+ for (i = 0; i < NTIMES; i++) {
+ pixd = pixRotateShearCenter(pixs, deg2rad * angle, L_BRING_IN_WHITE);
+ pixDestroy(&pixd);
+ }
+ pops = (l_float32)(w * h * NTIMES / 1000000.) / stopTimer();
+ lept_stderr("vers. 1, mpops: %f\n", pops);
+ startTimer();
+ w = pixGetWidth(pixs);
+ h = pixGetHeight(pixs);
+ for (i = 0; i < NTIMES; i++) {
+ pixRotateShearIP(pixs, w/2, h/2, deg2rad * angle, L_BRING_IN_WHITE);
+ }
+ pops = (l_float32)(w * h * NTIMES / 1000000.) / stopTimer();
+ lept_stderr("shear, mpops: %f\n", pops);
+ pixWrite(fileout, pixs, IFF_PNG);
+ for (i = 0; i < NTIMES; i++) {
+ pixRotateShearIP(pixs, w/2, h/2, -deg2rad * angle, L_BRING_IN_WHITE);
+ }
+ pixWrite("/usr/tmp/junkout", pixs, IFF_PNG);
+}
+#endif
+
+#if 0
+ /* area-mapping rotation operations */
+ pixd = pixRotateAM(pixs, deg2rad * angle, L_BRING_IN_WHITE);
+/* pixd = pixRotateAMColorFast(pixs, deg2rad * angle, 255); */
+ if (pixGetDepth(pixd) == 1)
+ pixWrite(fileout, pixd, IFF_PNG);
+ else
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+#endif
+
+#if 0
+ /* compare the standard area-map color rotation with
+ * the fast area-map color rotation, on a pixel basis */
+ {
+ PIX *pix1, *pix2;
+ NUMA *nar, *nag, *nab, *naseq;
+ GPLOT *gplot;
+
+ startTimer();
+ pix1 = pixRotateAMColor(pixs, 0.12, 0xffffff00);
+ lept_stderr(" standard color rotate: %7.2f sec\n", stopTimer());
+ pixWrite("/tmp/lept/rotate/color1.jpg", pix1, IFF_JFIF_JPEG);
+ startTimer();
+ pix2 = pixRotateAMColorFast(pixs, 0.12, 0xffffff00);
+ lept_stderr(" fast color rotate: %7.2f sec\n", stopTimer());
+ pixWrite("/tmp/lept/rotate/color2.jpg", pix2, IFF_JFIF_JPEG);
+ pixd = pixAbsDifference(pix1, pix2);
+ pixGetColorHistogram(pixd, 1, &nar, &nag, &nab);
+ naseq = numaMakeSequence(0., 1., 256);
+ gplot = gplotCreate("/tmp/lept/rotate/absdiff", GPLOT_PNG,
+ "Number vs diff", "diff", "number");
+ gplotAddPlot(gplot, naseq, nar, GPLOT_POINTS, "red");
+ gplotAddPlot(gplot, naseq, nag, GPLOT_POINTS, "green");
+ gplotAddPlot(gplot, naseq, nab, GPLOT_POINTS, "blue");
+ gplotMakeOutput(gplot);
+ l_fileDisplay("/tmp/lept/rotate/absdiff.png", 100, 100, 1.0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixd);
+ numaDestroy(&nar);
+ numaDestroy(&nag);
+ numaDestroy(&nab);
+ numaDestroy(&naseq);
+ gplotDestroy(&gplot);
+ }
+#endif
+
+ /* Do a succession of 180 7-degree rotations in a cw
+ * direction, and unwind the result with another set in
+ * a ccw direction. Although there is a considerable amount
+ * of distortion after successive rotations, after all
+ * 360 rotations, the resulting image is restored to
+ * its original pristine condition! */
+#if 1
+ rotflag = L_ROTATE_AREA_MAP;
+/* rotflag = L_ROTATE_SHEAR; */
+/* rotflag = L_ROTATE_SAMPLING; */
+ ang = 7.0 * deg2rad;
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixd = pixRotate(pixs, ang, rotflag, L_BRING_IN_WHITE, w, h);
+ pixWrite("/tmp/lept/rotate/rot7.png", pixd, IFF_PNG);
+ for (i = 1; i < 180; i++) {
+ pixs = pixd;
+ pixd = pixRotate(pixs, ang, rotflag, L_BRING_IN_WHITE, w, h);
+ if ((i % 30) == 0) pixDisplay(pixd, 600, 0);
+ pixDestroy(&pixs);
+ }
+
+ pixWrite("/tmp/lept/rotate/spin.png", pixd, IFF_PNG);
+ pixDisplay(pixd, 0, 0);
+
+ for (i = 0; i < 180; i++) {
+ pixs = pixd;
+ pixd = pixRotate(pixs, -ang, rotflag, L_BRING_IN_WHITE, w, h);
+ if (i && (i % 30) == 0) pixDisplay(pixd, 600, 500);
+ pixDestroy(&pixs);
+ }
+
+ pixWrite("/tmp/lept/rotate/unspin.png", pixd, IFF_PNG);
+ pixDisplay(pixd, 0, 500);
+ pixDestroy(&pixd);
+#endif
+
+ return 0;
+}
+
diff --git a/leptonica/prog/runlengthtest.c b/leptonica/prog/runlengthtest.c
new file mode 100644
index 00000000..427dfcb7
--- /dev/null
+++ b/leptonica/prog/runlengthtest.c
@@ -0,0 +1,106 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * runlengthtest.c
+ *
+ * Set 1 tests the runlength and 1-component dynamic range transform.
+ * Set 2 tests the 3-component (rgb) dynamic range transform.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_float32 avediff, rmsdiff;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
+static char mainName[] = "runlengthtest";
+
+ if (argc != 1)
+ return ERROR_INT(" Syntax: runlengthtest", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/run");
+
+ /* Set 1 */
+ startTimer();
+ pix1 = pixRead("rabi.png");
+ pix2 = pixRunlengthTransform(pix1, 0, L_HORIZONTAL_RUNS, 8);
+ pix3 = pixRunlengthTransform(pix1, 0, L_VERTICAL_RUNS, 8);
+ pix4 = pixMinOrMax(NULL, pix2, pix3, L_CHOOSE_MIN);
+ pix5 = pixMaxDynamicRange(pix4, L_LOG_SCALE);
+ pix6 = pixMinOrMax(NULL, pix2, pix3, L_CHOOSE_MAX);
+ pix7 = pixMaxDynamicRange(pix6, L_LOG_SCALE);
+ lept_stderr("Time for set 1: %7.3f sec\n", stopTimer());
+ pixDisplay(pix2, 0, 0);
+ pixDisplay(pix3, 600, 0);
+ pixDisplay(pix4, 1200, 0);
+ pixDisplay(pix5, 1800, 0);
+ pixDisplay(pix6, 1200, 0);
+ pixDisplay(pix7, 1800, 0);
+ pixWrite("/tmp/lept/run/pixh.png", pix2, IFF_PNG);
+ pixWrite("/tmp/lept/run/pixv.png", pix3, IFF_PNG);
+ pixWrite("/tmp/lept/run/pixmin.png", pix4, IFF_PNG);
+ pixWrite("/tmp/lept/run/pixminlog.png", pix5, IFF_PNG);
+ pixWrite("/tmp/lept/run/pixmax.png", pix6, IFF_PNG);
+ pixWrite("/tmp/lept/run/pixmaxlog.png", pix7, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+
+ /* Set 2 */
+ startTimer();
+ pix1 = pixRead("test24.jpg");
+ pixWriteJpeg("/tmp/lept/run/junk24.jpg", pix1, 5, 0);
+ pix2 = pixRead("/tmp/lept/run/junk24.jpg");
+ pixCompareGrayOrRGB(pix1, pix2, L_COMPARE_ABS_DIFF, GPLOT_PNG,
+ NULL, &avediff, &rmsdiff, &pix3);
+ lept_stderr("Ave diff = %6.3f, RMS diff = %6.3f\n", avediff, rmsdiff);
+ pix4 = pixMaxDynamicRangeRGB(pix3, L_LINEAR_SCALE);
+ pix5 = pixMaxDynamicRangeRGB(pix3, L_LOG_SCALE);
+ lept_stderr("Time for set 2: %7.3f sec\n", stopTimer());
+ pixDisplay(pix4, 0, 800);
+ pixDisplay(pix5, 1000, 800);
+ pixWrite("/tmp/lept/run/linear.png", pix4, IFF_PNG);
+ pixWrite("/tmp/lept/run/log.png", pix5, IFF_PNG);
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+
+ return 0;
+}
diff --git a/leptonica/prog/scale_it.c b/leptonica/prog/scale_it.c
new file mode 100644
index 00000000..f9db6ec7
--- /dev/null
+++ b/leptonica/prog/scale_it.c
@@ -0,0 +1,143 @@
+/*====================================================================*
+ - 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_it.c
+ *
+ * scale_it filein scalex scaley fileout lossless [sharpen antialias togray]
+ *
+ * where
+ * scalex: floating pt input
+ * scaley: ditto
+ * lossless: (for bpp >= 8) 1 to output jpeg; 0 to output png
+ * sharpen : (for bpp > 1; scale factor in [0.2 ... 1.4]):
+ * 1 to sharpen; 0 not to sharpen
+ * antialias: (for bpp > 1): 1 to use area-mapping or linear
+ * interpolation; 0 for sampling.
+ * togray: (for bpp == 1, reduction): 1 for scale-to-gray;
+ * 0 for sampling
+ *
+ * The choice of writing lossless (png) or lossy (jpeg) only applies
+ * for bpp >= 8. Otherwise:
+ * bpp == 1 --> tiffg4
+ * bpp == 2 --> png
+ * bpp == 4 --> png
+ *
+ * Sharpening: no sharpening is done for scale factors < 0.2 or > 1.4.
+ * Sharpening increases the saliency of edges, making the scaled image
+ * look less fuzzy. It is much slower than scaling without sharpening.
+ * The default is to sharpen.
+ *
+ * Antialias: area-mapping and linear interpolation give higher
+ * quality results with bpp > 1. Sampling is faster, but shows
+ * artifacts, such as pixel-sized steps in lines. The default is
+ * to use antialiasing.
+ *
+ * ScaleToGray: for bpp == 1, downscaling to gray gives a better appearance
+ * than subsampling. The default is to scale-to-gray.
+ *
+ * The defaults are all intended to improve the quality of the result.
+ * The quality can be degraded, with faster processing, by setting
+ * some of the three optional inputs to 0.
+ *
+ * Note that the short form:
+ * scale_it filein scalex scaley fileout lossless
+ * is equivalent to
+ * scale_it filein scalex scaley fileout lossless 1 1 1
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 sharpen, antialias, togray, format, lossless, d;
+l_float32 scalex, scaley;
+PIX *pixs, *pixd;
+static char mainName[] = "scale_it";
+
+ if (argc != 6 && argc != 9)
+ return ERROR_INT(
+ "\n Syntax: scale_it filein scalex scaley fileout lossless "
+ "[sharpen antialias togray]",
+ mainName, 1);
+
+ filein = argv[1];
+ scalex = atof(argv[2]);
+ scaley = atof(argv[3]);
+ fileout = argv[4];
+ lossless = atoi(argv[5]);
+ sharpen = antialias = togray = 1;
+ if (argc == 9) {
+ sharpen = atoi(argv[6]);
+ antialias = atoi(argv[7]);
+ togray = atoi(argv[8]);
+ }
+ if (scalex <= 0 || scaley <= 0)
+ return ERROR_INT("invalid scale factor; must be > 0.0", mainName, 1);
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+ d = pixGetDepth(pixs);
+ if (d == 1) {
+ if (togray && scalex < 1.0)
+ pixd = pixScaleToGray(pixs, scalex);
+ else /* this will just scale by sampling */
+ pixd = pixScaleBinary(pixs, scalex, scaley);
+ } else {
+ if (antialias == 0) {
+ pixd = pixScaleBySampling(pixs, scalex, scaley);
+ } else if (sharpen == 0) {
+ pixd = pixScaleGeneral(pixs, scalex, scaley, 0.0, 0);
+ } else { /* antialias == 1, sharpen == 1 */
+ pixd = pixScale(pixs, scalex, scaley);
+ }
+ }
+ if (!pixd)
+ return ERROR_INT("pixd not made", mainName, 1);
+
+ d = pixGetDepth(pixd);
+ if (d == 1)
+ pixWrite(fileout, pixd, IFF_TIFF_G4);
+ else if (d == 2 || d == 4)
+ pixWrite(fileout, pixd, IFF_PNG);
+ else { /* d >= 8 */
+ if (lossless)
+ pixWrite(fileout, pixd, IFF_PNG);
+ else
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+ }
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
diff --git a/leptonica/prog/scale_reg.c b/leptonica/prog/scale_reg.c
new file mode 100644
index 00000000..84b099ae
--- /dev/null
+++ b/leptonica/prog/scale_reg.c
@@ -0,0 +1,307 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * This tests a number of scaling operations, through the pixScale()
+ * interface.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const char *image[10] = {"feyn-fract.tif", /* 1 bpp */
+ "weasel2.png", /* 2 bpp; no cmap */
+ "weasel2.4c.png", /* 2 bpp; cmap */
+ "weasel4.png", /* 4 bpp; no cmap */
+ "weasel4.16c.png", /* 4 bpp; cmap */
+ "weasel8.png", /* 8 bpp; no cmap */
+ "weasel8.240c.png", /* 8 bpp; cmap */
+ "test16.png", /* 16 bpp rgb */
+ "marge.jpg", /* 32 bpp rgb */
+ "test24.jpg"}; /* 32 bpp rgb */
+
+
+static const l_int32 SPACE = 30;
+static const l_int32 WIDTH = 300;
+static const l_float32 FACTOR[5] = {2.3f, 1.5f, 1.1f, 0.6f, 0.3f};
+
+static void AddScaledImages(PIXA *pixa, const char *fname, l_int32 width);
+static void PixaSaveDisplay(PIXA *pixa, L_REGPARAMS *rp);
+static void TestSmoothScaling(const char *fname, L_REGPARAMS *rp);
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i;
+PIX *pixs, *pixc;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Test 1 bpp */
+ lept_stderr("\n-------------- Testing 1 bpp ----------\n");
+ pixa = pixaCreate(0);
+ pixs = pixRead(image[0]);
+ pixc = pixScale(pixs, 0.32, 0.32);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 0 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+
+ pixc = pixScaleToGray3(pixs);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 1 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+
+ pixc = pixScaleToGray4(pixs);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 2 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+
+ pixc = pixScaleToGray6(pixs);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 3 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+
+ pixc = pixScaleToGray8(pixs);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 4 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+
+ pixc = pixScaleToGray16(pixs);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 5 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ PixaSaveDisplay(pixa, rp); /* 6 */
+
+ for (i = 1; i < 10; i++) {
+ pixa = pixaCreate(0);
+ AddScaledImages(pixa, image[i], WIDTH);
+ PixaSaveDisplay(pixa, rp); /* 7 - 15 */
+ }
+ pixDestroy(&pixs);
+
+ /* Test 2 bpp without colormap */
+ lept_stderr("\n-------------- Testing 2 bpp without cmap ----------\n");
+ pixa = pixaCreate(0);
+ pixs = pixRead(image[1]);
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixc = pixScale(pixs, 2.25, 2.25);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 16 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.85, 0.85);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 17 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.65, 0.65);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 18 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ PixaSaveDisplay(pixa, rp); /* 19 */
+
+ /* Test 2 bpp with colormap */
+ lept_stderr("\n-------------- Testing 2 bpp with cmap ----------\n");
+ pixa = pixaCreate(0);
+ pixs = pixRead(image[2]);
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixc = pixScale(pixs, 2.25, 2.25);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 20 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.85, 0.85);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 21 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.65, 0.65);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 22 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ PixaSaveDisplay(pixa, rp); /* 23 */
+
+ /* Test 4 bpp without colormap */
+ lept_stderr("\n-------------- Testing 4 bpp without cmap ----------\n");
+ pixa = pixaCreate(0);
+ pixs = pixRead(image[3]);
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixc = pixScale(pixs, 1.72, 1.72);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 24 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.85, 0.85);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 25 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.65, 0.65);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 26 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ PixaSaveDisplay(pixa, rp); /* 27 */
+
+ /* Test 4 bpp with colormap */
+ lept_stderr("\n-------------- Testing 4 bpp with cmap ----------\n");
+ pixa = pixaCreate(0);
+ pixs = pixRead(image[4]);
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixc = pixScale(pixs, 1.72, 1.72);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 28 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.85, 0.85);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 29 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.65, 0.65);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 30 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ PixaSaveDisplay(pixa, rp); /* 31 */
+
+ /* Test 8 bpp without colormap */
+ lept_stderr("\n-------------- Testing 8 bpp without cmap ----------\n");
+ pixa = pixaCreate(0);
+ pixs = pixRead(image[5]);
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixc = pixScale(pixs, 1.92, 1.92);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 32 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.85, 0.85);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 33 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.65, 0.65);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 34 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixs = pixRead("graytext.png");
+ pixc = pixScaleToSize(pixs, 0, 32); /* uses fast unsharp masking */
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 35 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ PixaSaveDisplay(pixa, rp); /* 36 */
+ pixDestroy(&pixs);
+
+ /* Test 8 bpp with colormap */
+ lept_stderr("\n-------------- Testing 8 bpp with cmap ----------\n");
+ pixa = pixaCreate(0);
+ pixs = pixRead(image[6]);
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixc = pixScale(pixs, 1.92, 1.92);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 37 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.85, 0.85);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 38 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.65, 0.65);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 39 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ PixaSaveDisplay(pixa, rp); /* 40 */
+
+ /* Test 16 bpp */
+ lept_stderr("\n-------------- Testing 16 bpp ------------\n");
+ pixa = pixaCreate(0);
+ pixs = pixRead(image[7]);
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixc = pixScale(pixs, 1.92, 1.92);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 41 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.85, 0.85);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 42 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.65, 0.65);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 43 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ PixaSaveDisplay(pixa, rp); /* 44 */
+
+ /* Test 32 bpp */
+ lept_stderr("\n-------------- Testing 32 bpp ------------\n");
+ pixa = pixaCreate(0);
+ pixs = pixRead(image[8]);
+ pixaAddPix(pixa, pixs, L_INSERT);
+ pixc = pixScale(pixs, 1.42, 1.42);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 45 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.85, 0.85);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 46 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixc = pixScale(pixs, 0.65, 0.65);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 47 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ PixaSaveDisplay(pixa, rp); /* 48 */
+
+ /* Test 32 bpp low-pass filtered smooth scaling */
+ TestSmoothScaling("test24.jpg", rp); /* 49 */
+ return regTestCleanup(rp);
+}
+
+static void
+AddScaledImages(PIXA *pixa,
+ const char *fname,
+ l_int32 width)
+{
+l_int32 i, w;
+l_float32 scalefactor;
+PIX *pixs, *pix1, *pix2, *pix3;
+
+ pixs = pixRead(fname);
+ w = pixGetWidth(pixs);
+ for (i = 0; i < 5; i++) {
+ scalefactor = (l_float32)width / (FACTOR[i] * (l_float32)w);
+ pix1 = pixScale(pixs, FACTOR[i], FACTOR[i]);
+ pix2 = pixScale(pix1, scalefactor, scalefactor);
+ pix3 = pixConvertTo32(pix2);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pixs);
+}
+
+static void
+PixaSaveDisplay(PIXA *pixa, L_REGPARAMS *rp)
+{
+PIX *pixd;
+
+ pixd = pixaDisplayTiledInRows(pixa, 32, 3000, 1.0, 0, SPACE, 2);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG);
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+}
+
+static void
+TestSmoothScaling(const char *fname, L_REGPARAMS *rp)
+{
+l_int32 i;
+l_float32 scale, upscale;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa;
+
+ pix1 = pixRead(fname);
+ pixa = pixaCreate(12);
+ scale = 0.5;
+ for (i = 0; i < 12; i++) {
+ scale *= 0.7;
+ upscale = 0.25 / scale;
+ if (rp->display) lept_stderr("scale = %5.3f\n", scale);
+ pix2 = pixScaleSmooth(pix1, scale, scale);
+ pix3 = pixScale(pix2, upscale, upscale);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ pix2 = pixaDisplayTiledInColumns(pixa, 3, 1.0, 10, 2);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG);
+ pixDisplayWithTitle(pix2, 0, 300, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+}
+
diff --git a/leptonica/prog/scaleandtile.c b/leptonica/prog/scaleandtile.c
new file mode 100644
index 00000000..d4b2608a
--- /dev/null
+++ b/leptonica/prog/scaleandtile.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * scaleandtile.c
+ *
+ * Generates a single image tiling of all images in a directory
+ * whose filename contains a given substring. The filenames
+ * are filtered and sorted, and read into a pixa, which is
+ * then tiled into a pix at a specified depth, and finally
+ * written out to file.
+ *
+ * Input: dirin: directory that has image files
+ * depth (output depth: 1, 8 or 32; use 32 for RGB)
+ * width (of each tile; all pix are scaled to the same width)
+ * ncols (number of tiles in each row)
+ * background (0 for white, 1 for black)
+ * fileout: output tiled image file
+ *
+ * Note: this program is Unix only; it will not compile under cygwin.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Change these and recompile if necessary */
+static const l_int32 BACKGROUND_COLOR = 0;
+static const l_int32 SPACING = 25; /* between images and on outside */
+static const l_int32 BLACK_BORDER = 2; /* surrounding each image */
+
+
+int main(int argc,
+ char **argv)
+{
+char *dirin, *substr, *fileout;
+l_int32 depth, width, ncols;
+PIX *pixd;
+PIXA *pixa;
+static char mainName[] = "scaleandtile";
+
+ if (argc != 7)
+ return ERROR_INT(
+ "Syntax: scaleandtile dirin substr depth width ncols fileout",
+ mainName, 1);
+ dirin = argv[1];
+ substr = argv[2];
+ depth = atoi(argv[3]);
+ width = atoi(argv[4]);
+ ncols = atoi(argv[5]);
+ fileout = argv[6];
+ setLeptDebugOK(1);
+
+ /* Avoid division by zero if ncols == 0 and require a positive value. */
+ if (ncols <= 0)
+ return ERROR_INT("Expected a positive value for ncols", mainName, 1);
+
+ /* Read the specified images from file */
+ if ((pixa = pixaReadFiles(dirin, substr)) == NULL)
+ return ERROR_INT("safiles not made", mainName, 1);
+ lept_stderr("Number of pix: %d\n", pixaGetCount(pixa));
+
+ /* Tile them */
+ pixd = pixaDisplayTiledAndScaled(pixa, depth, width, ncols,
+ BACKGROUND_COLOR, SPACING, BLACK_BORDER);
+
+ if (depth < 8)
+ pixWrite(fileout, pixd, IFF_PNG);
+ else
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+ return 0;
+}
+
+
diff --git a/leptonica/prog/scaletest1.c b/leptonica/prog/scaletest1.c
new file mode 100644
index 00000000..0525832a
--- /dev/null
+++ b/leptonica/prog/scaletest1.c
@@ -0,0 +1,92 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * scaletest1.c
+ *
+ * scaletest1 filein scalex scaley fileout
+ * where
+ * scalex, scaley are floating point input
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 d;
+l_float32 scalex, scaley;
+PIX *pixs, *pixd;
+static char mainName[] = "scaletest1";
+
+ if (argc != 5)
+ return ERROR_INT(" Syntax: scaletest1 filein scalex scaley fileout",
+ mainName, 1);
+ filein = argv[1];
+ scalex = atof(argv[2]);
+ scaley = atof(argv[3]);
+ fileout = argv[4];
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ /* choose type of scaling operation */
+#if 1
+ pixd = pixScale(pixs, scalex, scaley);
+#elif 0
+ pixd = pixScaleLI(pixs, scalex, scaley);
+#elif 0
+ pixd = pixScaleSmooth(pixs, scalex, scaley);
+#elif 0
+ pixd = pixScaleAreaMap(pixs, scalex, scaley);
+#elif 0
+ pixd = pixScaleBySampling(pixs, scalex, scaley);
+#else
+ pixd = pixScaleToGray(pixs, scalex);
+#endif
+
+ d = pixGetDepth(pixd);
+
+#if 1
+ if (d <= 8)
+ pixWrite(fileout, pixd, IFF_PNG);
+ else
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+#else
+ pixWrite(fileout, pixd, IFF_PNG);
+#endif
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
+
diff --git a/leptonica/prog/scaletest2.c b/leptonica/prog/scaletest2.c
new file mode 100644
index 00000000..2aefa8c3
--- /dev/null
+++ b/leptonica/prog/scaletest2.c
@@ -0,0 +1,363 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * scaletest2.c
+ *
+ * Tests scale-to-gray, unsharp masking, smoothing, and color scaling
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define DISPLAY 0 /* set to 1 to see the results */
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs;
+l_int32 d;
+static char mainName[] = "scaletest2";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: scaletest2 filein", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/scale");
+
+ if ((pixs = pixRead(argv[1])) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+ d = pixGetDepth(pixs);
+
+#if 1
+ /* Integer scale-to-gray functions */
+ if (d == 1) {
+ PIX *pixd;
+
+ pixd = pixScaleToGray2(pixs);
+ pixWrite("/tmp/lept/scale/s2g_2x", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ pixd = pixScaleToGray3(pixs);
+ pixWrite("/tmp/lept/scale/s2g_3x", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ pixd = pixScaleToGray4(pixs);
+ pixWrite("/tmp/lept/scale/s2g_4x", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ pixd = pixScaleToGray6(pixs);
+ pixWrite("/tmp/lept/scale/s2g_6x", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ pixd = pixScaleToGray8(pixs);
+ pixWrite("/tmp/lept/scale/s2g_8x", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ pixd = pixScaleToGray16(pixs);
+ pixWrite("/tmp/lept/scale/s2g_16x", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ }
+#endif
+
+#if 1
+ /* Various non-integer scale-to-gray, compared with
+ * with different ways of getting similar results */
+ if (d == 1) {
+ PIX *pixt, *pixd;
+
+ pixd = pixScaleToGray8(pixs);
+ pixWrite("/tmp/lept/scale/s2g_8.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+
+ pixd = pixScaleToGray(pixs, 0.124);
+ pixWrite("/tmp/lept/scale/s2g_124.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+
+ pixd = pixScaleToGray(pixs, 0.284);
+ pixWrite("/tmp/lept/scale/s2g_284.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+
+ pixt = pixScaleToGray4(pixs);
+ pixd = pixScaleBySampling(pixt, 284./250., 284./250.);
+ pixWrite("/tmp/lept/scale/s2g_284.2.png", pixd, IFF_PNG);
+ pixDestroy(&pixt);
+ pixDestroy(&pixd);
+
+ pixt = pixScaleToGray4(pixs);
+ pixd = pixScaleGrayLI(pixt, 284./250., 284./250.);
+ pixWrite("/tmp/lept/scale/s2g_284.3.png", pixd, IFF_PNG);
+ pixDestroy(&pixt);
+ pixDestroy(&pixd);
+
+ pixt = pixScaleBinary(pixs, 284./250., 284./250.);
+ pixd = pixScaleToGray4(pixt);
+ pixWrite("/tmp/lept/scale/s2g_284.4.png", pixd, IFF_PNG);
+ pixDestroy(&pixt);
+ pixDestroy(&pixd);
+
+ pixt = pixScaleToGray4(pixs);
+ pixd = pixScaleGrayLI(pixt, 0.49, 0.49);
+ pixWrite("/tmp/lept/scale/s2g_42.png", pixd, IFF_PNG);
+ pixDestroy(&pixt);
+ pixDestroy(&pixd);
+
+ pixt = pixScaleToGray4(pixs);
+ pixd = pixScaleSmooth(pixt, 0.49, 0.49);
+ pixWrite("/tmp/lept/scale/s2g_4sm.png", pixd, IFF_PNG);
+ pixDestroy(&pixt);
+ pixDestroy(&pixd);
+
+ pixt = pixScaleBinary(pixs, .16/.125, .16/.125);
+ pixd = pixScaleToGray8(pixt);
+ pixWrite("/tmp/lept/scale/s2g_16.png", pixd, IFF_PNG);
+ pixDestroy(&pixt);
+ pixDestroy(&pixd);
+
+ pixd = pixScaleToGray(pixs, .16);
+ pixWrite("/tmp/lept/scale/s2g_16.2.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ }
+#endif
+
+#if 1
+ /* Antialiased (smoothed) reduction, along with sharpening */
+ if (d != 1) {
+ PIX *pixt1, *pixt2;
+
+ startTimer();
+ pixt1 = pixScaleSmooth(pixs, 0.154, 0.154);
+ lept_stderr("fast scale: %5.3f sec\n", stopTimer());
+ pixDisplayWithTitle(pixt1, 0, 0, "smooth scaling", DISPLAY);
+ pixWrite("/tmp/lept/scale/smooth1.png", pixt1, IFF_PNG);
+ pixt2 = pixUnsharpMasking(pixt1, 1, 0.3);
+ pixWrite("/tmp/lept/scale/smooth2.png", pixt2, IFF_PNG);
+ pixDisplayWithTitle(pixt2, 200, 0, "sharp scaling", DISPLAY);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ }
+#endif
+
+
+#if 1
+ /* Test a large range of scale-to-gray reductions */
+ if (d == 1) {
+ l_int32 i;
+ l_float32 scale;
+ PIX *pixd;
+
+ for (i = 2; i < 15; i++) {
+ scale = 1. / (l_float32)i;
+ startTimer();
+ pixd = pixScaleToGray(pixs, scale);
+ lept_stderr("Time for scale %7.3f: %7.3f sec\n",
+ scale, stopTimer());
+ pixDisplayWithTitle(pixd, 75 * i, 100, "scaletogray", DISPLAY);
+ pixDestroy(&pixd);
+ }
+ for (i = 8; i < 14; i++) {
+ scale = 1. / (l_float32)(2 * i);
+ startTimer();
+ pixd = pixScaleToGray(pixs, scale);
+ lept_stderr("Time for scale %7.3f: %7.3f sec\n",
+ scale, stopTimer());
+ pixDisplayWithTitle(pixd, 100 * i, 600, "scaletogray", DISPLAY);
+ pixDestroy(&pixd);
+ }
+ }
+#endif
+
+
+#if 1
+ /* Test the same range of scale-to-gray mipmap reductions */
+ if (d == 1) {
+ l_int32 i;
+ l_float32 scale;
+ PIX *pixd;
+
+ for (i = 2; i < 15; i++) {
+ scale = 1. / (l_float32)i;
+ startTimer();
+ pixd = pixScaleToGrayMipmap(pixs, scale);
+ lept_stderr("Time for scale %7.3f: %7.3f sec\n",
+ scale, stopTimer());
+ pixDisplayWithTitle(pixd, 75 * i, 100, "scale mipmap", DISPLAY);
+ pixDestroy(&pixd);
+ }
+ for (i = 8; i < 12; i++) {
+ scale = 1. / (l_float32)(2 * i);
+ startTimer();
+ pixd = pixScaleToGrayMipmap(pixs, scale);
+ lept_stderr("Time for scale %7.3f: %7.3f sec\n",
+ scale, stopTimer());
+ pixDisplayWithTitle(pixd, 100 * i, 600, "scale mipmap", DISPLAY);
+ pixDestroy(&pixd);
+ }
+ }
+#endif
+
+#if 1
+ /* Test several methods for antialiased reduction,
+ * along with sharpening */
+ if (d != 1) {
+ PIX *pixt1, *pixt2, *pixt3, *pixt4, *pixt5, *pixt6, *pixt7;
+ l_float32 SCALING = 0.27;
+ l_int32 SIZE = 7;
+ l_int32 smooth;
+ l_float32 FRACT = 1.0;
+
+ smooth = SIZE / 2;
+
+ startTimer();
+ pixt1 = pixScaleSmooth(pixs, SCALING, SCALING);
+ lept_stderr("fast scale: %5.3f sec\n", stopTimer());
+ pixDisplayWithTitle(pixt1, 0, 0, "smooth scaling", DISPLAY);
+ pixWrite("/tmp/lept/scale/sm_1.png", pixt1, IFF_PNG);
+ pixt2 = pixUnsharpMasking(pixt1, 1, 0.3);
+ pixDisplayWithTitle(pixt2, 150, 0, "sharpened scaling", DISPLAY);
+
+ startTimer();
+ pixt3 = pixBlockconv(pixs, smooth, smooth);
+ pixt4 = pixScaleBySampling(pixt3, SCALING, SCALING);
+ lept_stderr("slow scale: %5.3f sec\n", stopTimer());
+ pixDisplayWithTitle(pixt4, 200, 200, "sampled scaling", DISPLAY);
+ pixWrite("/tmp/lept/scale/sm_2.png", pixt4, IFF_PNG);
+
+ startTimer();
+ pixt5 = pixUnsharpMasking(pixs, smooth, FRACT);
+ pixt6 = pixBlockconv(pixt5, smooth, smooth);
+ pixt7 = pixScaleBySampling(pixt6, SCALING, SCALING);
+ lept_stderr("very slow scale + sharp: %5.3f sec\n", stopTimer());
+ pixDisplayWithTitle(pixt7, 500, 200, "sampled scaling", DISPLAY);
+ pixWrite("/tmp/lept/scale/sm_3.jpg", pixt7, IFF_JFIF_JPEG);
+
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ pixDestroy(&pixt3);
+ pixDestroy(&pixt4);
+ pixDestroy(&pixt5);
+ pixDestroy(&pixt6);
+ pixDestroy(&pixt7);
+ }
+#endif
+
+
+#if 1
+ /* Test the color scaling function, comparing the
+ * special case of scaling factor 2.0 with the
+ * general case. */
+ if (d == 32) {
+ PIX *pix1, *pix2, *pixd;
+ NUMA *nar, *nag, *nab, *naseq;
+ GPLOT *gplot;
+
+ startTimer();
+ pix1 = pixScaleColorLI(pixs, 2.00001, 2.0);
+ lept_stderr(" Time with regular LI: %7.3f\n", stopTimer());
+ pixWrite("/tmp/lept/scale/color1.jpg", pix1, IFF_JFIF_JPEG);
+ startTimer();
+ pix2 = pixScaleColorLI(pixs, 2.0, 2.0);
+ lept_stderr(" Time with 2x LI: %7.3f\n", stopTimer());
+ pixWrite("/tmp/lept/scale/color2.jpg", pix2, IFF_JFIF_JPEG);
+
+ pixd = pixAbsDifference(pix1, pix2);
+ pixGetColorHistogram(pixd, 1, &nar, &nag, &nab);
+ naseq = numaMakeSequence(0., 1., 256);
+ gplot = gplotCreate("/tmp/lept/scale/c_absdiff", GPLOT_PNG,
+ "Number vs diff", "diff", "number");
+ gplotSetScaling(gplot, GPLOT_LOG_SCALE_Y);
+ gplotAddPlot(gplot, naseq, nar, GPLOT_POINTS, "red");
+ gplotAddPlot(gplot, naseq, nag, GPLOT_POINTS, "green");
+ gplotAddPlot(gplot, naseq, nab, GPLOT_POINTS, "blue");
+ gplotMakeOutput(gplot);
+ l_fileDisplay("/tmp/lept/scale/c_absdiff.png", 0, 100, 1.0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixd);
+ numaDestroy(&naseq);
+ numaDestroy(&nar);
+ numaDestroy(&nag);
+ numaDestroy(&nab);
+ gplotDestroy(&gplot);
+ }
+#endif
+
+
+#if 1
+ /* Test the gray LI scaling function, comparing the
+ * special cases of scaling factor 2.0 and 4.0 with the
+ * general case */
+ if (d == 8 || d == 32) {
+ PIX *pixt, *pix0, *pix1, *pix2, *pixd;
+ NUMA *nagray, *naseq;
+ GPLOT *gplot;
+
+ if (d == 8)
+ pixt = pixClone(pixs);
+ else
+ pixt = pixConvertRGBToGray(pixs, 0.33, 0.34, 0.33);
+ pix0 = pixScaleGrayLI(pixt, 0.5, 0.5);
+
+#if 1
+ startTimer();
+ pix1 = pixScaleGrayLI(pix0, 2.00001, 2.0);
+ lept_stderr(" Time with regular LI 2x: %7.3f\n", stopTimer());
+ startTimer();
+ pix2 = pixScaleGrayLI(pix0, 2.0, 2.0);
+ lept_stderr(" Time with 2x LI: %7.3f\n", stopTimer());
+#else
+ startTimer();
+ pix1 = pixScaleGrayLI(pix0, 4.00001, 4.0);
+ lept_stderr(" Time with regular LI 4x: %7.3f\n", stopTimer());
+ startTimer();
+ pix2 = pixScaleGrayLI(pix0, 4.0, 4.0);
+ lept_stderr(" Time with 2x LI: %7.3f\n", stopTimer());
+#endif
+ pixWrite("/tmp/lept/scale/gray1", pix1, IFF_JFIF_JPEG);
+ pixWrite("/tmp/lept/scale/gray2", pix2, IFF_JFIF_JPEG);
+
+ pixd = pixAbsDifference(pix1, pix2);
+ nagray = pixGetGrayHistogram(pixd, 1);
+ naseq = numaMakeSequence(0., 1., 256);
+ gplot = gplotCreate("/tmp/lept/scale/g_absdiff", GPLOT_PNG,
+ "Number vs diff", "diff", "number");
+ gplotSetScaling(gplot, GPLOT_LOG_SCALE_Y);
+ gplotAddPlot(gplot, naseq, nagray, GPLOT_POINTS, "gray");
+ gplotMakeOutput(gplot);
+ l_fileDisplay("/tmp/lept/scale/g_absdiff.png", 750, 100, 1.0);
+ pixDestroy(&pixt);
+ pixDestroy(&pix0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixd);
+ numaDestroy(&naseq);
+ numaDestroy(&nagray);
+ gplotDestroy(&gplot);
+ }
+#endif
+
+ pixDestroy(&pixs);
+ return 0;
+}
+
diff --git a/leptonica/prog/scots-frag.tif b/leptonica/prog/scots-frag.tif
new file mode 100644
index 00000000..f030ebf9
--- /dev/null
+++ b/leptonica/prog/scots-frag.tif
Binary files differ
diff --git a/leptonica/prog/seedfilltest.c b/leptonica/prog/seedfilltest.c
new file mode 100644
index 00000000..8249bffc
--- /dev/null
+++ b/leptonica/prog/seedfilltest.c
@@ -0,0 +1,170 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+
+/*
+ * seedfilltest.c
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define NTIMES 5
+#define CONNECTIVITY 8
+#define XS 150
+#define YS 150
+#define DFLAG 1
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 i;
+l_uint32 val;
+l_float32 size;
+PIX *pixs, *pixd, *pixm, *pixmi, *pixt1, *pixt2, *pixt3;
+static char mainName[] = "seedfilltest";
+
+ if (argc != 3)
+ return ERROR_INT(" Syntax: seedfilltest filein fileout", mainName, 1);
+ filein = argv[1];
+ fileout = argv[2];
+ pixd = NULL;
+ setLeptDebugOK(1);
+
+ if ((pixm = pixRead(filein)) == NULL)
+ return ERROR_INT("pixm not made", mainName, 1);
+ pixmi = pixInvert(NULL, pixm);
+
+ size = pixGetWidth(pixm) * pixGetHeight(pixm);
+ pixs = pixCreateTemplate(pixm);
+ for (i = 0; i < 100; i++) {
+ pixGetPixel(pixm, XS + 5 * i, YS + 5 * i, &val);
+ if (val == 0) break;
+ }
+ if (i == 100)
+ return ERROR_INT("no seed pixel found", mainName, 1);
+ pixSetPixel(pixs, XS + 5 * i, YS + 5 * i, 1);
+
+#if 0
+ /* hole filling; use "hole-filler.png" */
+ pixt1 = pixHDome(pixmi, 100, 4);
+ pixt2 = pixThresholdToBinary(pixt1, 10);
+/* pixInvert(pixt1, pixt1); */
+ pixDisplay(pixt1, 100, 500);
+ pixDisplay(pixt2, 600, 500);
+ pixt3 = pixHolesByFilling(pixt2, 4);
+ pixDilateBrick(pixt3, pixt3, 7, 7);
+ pixd = pixConvertTo8(pixt3, FALSE);
+ pixDisplay(pixd, 0, 100);
+ pixSeedfillGray(pixd, pixmi, CONNECTIVITY);
+ pixInvert(pixd, pixd);
+ pixDisplay(pixmi, 500, 100);
+ pixDisplay(pixd, 1000, 100);
+ pixWrite("/tmp/junkpixm.png", pixmi, IFF_PNG);
+ pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG);
+#endif
+
+#if 0
+ /* hole filling; use "hole-filler.png" */
+ pixt1 = pixThresholdToBinary(pixm, 110);
+ pixInvert(pixt1, pixt1);
+ pixDisplay(pixt1, 100, 500);
+ pixt2 = pixHolesByFilling(pixt1, 4);
+ pixd = pixConvertTo8(pixt2, FALSE);
+ pixDisplay(pixd, 0, 100);
+ pixSeedfillGray(pixd, pixmi, CONNECTIVITY);
+ pixInvert(pixd, pixd);
+ pixDisplay(pixmi, 500, 100);
+ pixDisplay(pixd, 1000, 100);
+ pixWrite("/tmp/junkpixm.png", pixmi, IFF_PNG);
+ pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG);
+#endif
+
+#if 0
+ /* hole filling; use "hole-filler.png" */
+ pixd = pixInvert(NULL, pixm);
+ pixAddConstantGray(pixd, -50);
+ pixDisplay(pixd, 0, 100);
+/* pixt1 = pixThresholdToBinary(pixd, 20);
+ pixDisplayWithTitle(pixt1, 600, 600, "pixt1", DFLAG); */
+ pixSeedfillGray(pixd, pixmi, CONNECTIVITY);
+/* pixInvert(pixd, pixd); */
+ pixDisplay(pixmi, 500, 100);
+ pixDisplay(pixd, 1000, 100);
+ pixWrite("/tmp/junkpixm.png", pixmi, IFF_PNG);
+ pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG);
+#endif
+
+#if 0
+ /* test in-place seedfill for speed */
+ pixd = pixClone(pixs);
+ startTimer();
+ pixSeedfillBinary(pixs, pixs, pixmi, CONNECTIVITY);
+ lept_stderr("Filling rate: %7.4f Mpix/sec\n",
+ (size/1000000.) / stopTimer());
+
+ pixWrite(fileout, pixd, IFF_PNG);
+ pixOr(pixd, pixd, pixm);
+ pixWrite("/tmp/junkout1.png", pixd, IFF_PNG);
+#endif
+
+#if 0
+ /* test seedfill to dest for speed */
+ pixd = pixCreateTemplate(pixm);
+ startTimer();
+ for (i = 0; i < NTIMES; i++) {
+ pixSeedfillBinary(pixd, pixs, pixmi, CONNECTIVITY);
+ }
+ lept_stderr("Filling rate: %7.4f Mpix/sec\n",
+ (size/1000000.) * NTIMES / stopTimer());
+
+ pixWrite(fileout, pixd, IFF_PNG);
+ pixOr(pixd, pixd, pixm);
+ pixWrite("/tmp/junkout1.png", pixd, IFF_PNG);
+#endif
+
+ /* use same connectivity to compare with the result of the
+ * slow parallel operation */
+#if 1
+ pixDestroy(&pixd);
+ pixd = pixSeedfillMorph(pixs, pixmi, 100, CONNECTIVITY);
+ pixOr(pixd, pixd, pixm);
+ pixWrite("/tmp/junkout2.png", pixd, IFF_PNG);
+#endif
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixm);
+ pixDestroy(&pixmi);
+ pixDestroy(&pixd);
+ return 0;
+}
+
+
diff --git a/leptonica/prog/seedspread_reg.c b/leptonica/prog/seedspread_reg.c
new file mode 100644
index 00000000..20038ceb
--- /dev/null
+++ b/leptonica/prog/seedspread_reg.c
@@ -0,0 +1,160 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * seedspread_reg.c
+ *
+ * Tests the seedspreading (voronoi finding & filling) function
+ * for both 4 and 8 connectivity.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const l_int32 scalefact = 1.0;
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, x, y, val;
+PIX *pixsq, *pixs, *pixc, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixsq = pixCreate(3, 3, 32);
+ pixSetAllArbitrary(pixsq, 0x00ff0000);
+ pixa = pixaCreate(6);
+
+ /* Moderately dense */
+ pixs = pixCreate(300, 300, 8);
+ for (i = 0; i < 100; i++) {
+ x = (153 * i * i * i + 59) % 299;
+ y = (117 * i * i * i + 241) % 299;
+ val = (97 * i + 74) % 256;
+ pixSetPixel(pixs, x, y, val);
+ }
+
+ pixd = pixSeedspread(pixs, 4); /* 4-cc */
+ pixc = pixConvertTo32(pixd);
+ for (i = 0; i < 100; i++) {
+ x = (153 * i * i * i + 59) % 299;
+ y = (117 * i * i * i + 241) % 299;
+ pixRasterop(pixc, x - 1, y - 1, 3, 3, PIX_SRC, pixsq, 0, 0);
+ }
+ pixaAddPix(pixa, pixc, L_INSERT);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pixc, 100, 100, "4-cc", rp->display);
+ pixDestroy(&pixd);
+
+ pixd = pixSeedspread(pixs, 8); /* 8-cc */
+ pixc = pixConvertTo32(pixd);
+ for (i = 0; i < 100; i++) {
+ x = (153 * i * i * i + 59) % 299;
+ y = (117 * i * i * i + 241) % 299;
+ pixRasterop(pixc, x - 1, y - 1, 3, 3, PIX_SRC, pixsq, 0, 0);
+ }
+ pixaAddPix(pixa, pixc, L_INSERT);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pixc, 410, 100, "8-cc", rp->display);
+ pixDestroy(&pixd);
+ pixDestroy(&pixs);
+
+ /* Regular lattice */
+ pixs = pixCreate(200, 200, 8);
+ for (i = 5; i <= 195; i += 10) {
+ for (j = 5; j <= 195; j += 10) {
+ pixSetPixel(pixs, i, j, (7 * i + 17 * j) % 255);
+ }
+ }
+ pixd = pixSeedspread(pixs, 4); /* 4-cc */
+ pixc = pixConvertTo32(pixd);
+ for (i = 5; i <= 195; i += 10) {
+ for (j = 5; j <= 195; j += 10) {
+ pixRasterop(pixc, j - 1, i - 1, 3, 3, PIX_SRC, pixsq, 0, 0);
+ }
+ }
+ pixaAddPix(pixa, pixc, L_INSERT);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pixc, 100, 430, "4-cc", rp->display);
+ pixDestroy(&pixd);
+
+ pixd = pixSeedspread(pixs, 8); /* 8-cc */
+ pixc = pixConvertTo32(pixd);
+ for (i = 5; i <= 195; i += 10) {
+ for (j = 5; j <= 195; j += 10) {
+ pixRasterop(pixc, j - 1, i - 1, 3, 3, PIX_SRC, pixsq, 0, 0);
+ }
+ }
+ pixaAddPix(pixa, pixc, L_INSERT);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pixc, 310, 430, "8-cc", rp->display);
+ pixDestroy(&pixd);
+ pixDestroy(&pixs);
+
+ /* Very sparse points */
+ pixs = pixCreate(200, 200, 8);
+ pixSetPixel(pixs, 60, 20, 90);
+ pixSetPixel(pixs, 160, 40, 130);
+ pixSetPixel(pixs, 80, 80, 205);
+ pixSetPixel(pixs, 40, 160, 115);
+ pixd = pixSeedspread(pixs, 4); /* 4-cc */
+ pixc = pixConvertTo32(pixd);
+ pixRasterop(pixc, 60 - 1, 20 - 1, 3, 3, PIX_SRC, pixsq, 0, 0);
+ pixRasterop(pixc, 160 - 1, 40 - 1, 3, 3, PIX_SRC, pixsq, 0, 0);
+ pixRasterop(pixc, 80 - 1, 80 - 1, 3, 3, PIX_SRC, pixsq, 0, 0);
+ pixRasterop(pixc, 40 - 1, 160 - 1, 3, 3, PIX_SRC, pixsq, 0, 0);
+ pixaAddPix(pixa, pixc, L_INSERT);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pixc, 100, 600, "4-cc", rp->display);
+ pixDestroy(&pixd);
+
+ pixd = pixSeedspread(pixs, 8); /* 8-cc */
+ pixc = pixConvertTo32(pixd);
+ pixRasterop(pixc, 60 - 1, 20 - 1, 3, 3, PIX_SRC, pixsq, 0, 0);
+ pixRasterop(pixc, 160 - 1, 40 - 1, 3, 3, PIX_SRC, pixsq, 0, 0);
+ pixRasterop(pixc, 80 - 1, 80 - 1, 3, 3, PIX_SRC, pixsq, 0, 0);
+ pixRasterop(pixc, 40 - 1, 160 - 1, 3, 3, PIX_SRC, pixsq, 0, 0);
+ pixaAddPix(pixa, pixc, L_INSERT);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pixc, 310, 660, "8-cc", rp->display);
+ pixDestroy(&pixd);
+ pixDestroy(&pixs);
+ pixDestroy(&pixsq);
+
+ pixd = pixaDisplayTiledInColumns(pixa, 2, scalefact, 20, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pixd, 720, 100, "Final", rp->display);
+
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/selio_reg.c b/leptonica/prog/selio_reg.c
new file mode 100644
index 00000000..4b920322
--- /dev/null
+++ b/leptonica/prog/selio_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * selio_reg.c
+ *
+ * Runs a number of tests on reading and writing of Sels
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+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";
+static const char *textsel5 = "xxxxxx"
+ " x"
+ " o x"
+ " ooo x"
+ " oo x";
+static const char *textsel6 = "xxXxxx"
+ " x"
+ " o x"
+ " oOo x"
+ " oo x";
+
+
+int main(int argc,
+ char **argv)
+{
+l_float32 val;
+PIX *pix;
+SEL *sel;
+SELA *sela1, *sela2;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* selaRead() / selaWrite() */
+ sela1 = selaAddBasic(NULL);
+ selaWrite("/tmp/lept/regout/sel.0.sela", sela1);
+ regTestCheckFile(rp, "/tmp/lept/regout/sel.0.sela"); /* 0 */
+ sela2 = selaRead("/tmp/lept/regout/sel.0.sela");
+ selaWrite("/tmp/lept/regout/sel.1.sela", sela2);
+ regTestCheckFile(rp, "/tmp/lept/regout/sel.1.sela"); /* 1 */
+ regTestCompareFiles(rp, 0, 1); /* 2 */
+ selaDestroy(&sela1);
+ selaDestroy(&sela2);
+
+ /* Create from file and display result */
+ sela1 = selaCreateFromFile("flipsels.txt");
+ pix = selaDisplayInPix(sela1, 31, 3, 15, 4);
+ regTestWritePixAndCheck(rp, pix, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pix, 100, 100, NULL, rp->display);
+ selaWrite("/tmp/lept/regout/sel.3.sela", sela1);
+ regTestCheckFile(rp, "/tmp/lept/regout/sel.3.sela"); /* 4 */
+ pixDestroy(&pix);
+ selaDestroy(&sela1);
+
+ /* Create the same set of Sels from compiled strings and compare */
+ sela2 = selaCreate(4);
+ sel = selCreateFromString(textsel1, 5, 6, "textsel1");
+ selaAddSel(sela2, sel, NULL, 0);
+ sel = selCreateFromString(textsel2, 5, 6, "textsel2");
+ selaAddSel(sela2, sel, NULL, 0);
+ sel = selCreateFromString(textsel3, 5, 6, "textsel3");
+ selaAddSel(sela2, sel, NULL, 0);
+ sel = selCreateFromString(textsel4, 5, 6, "textsel4");
+ selaAddSel(sela2, sel, NULL, 0);
+ selaWrite("/tmp/lept/regout/sel.4.sela", sela2);
+ regTestCheckFile(rp, "/tmp/lept/regout/sel.4.sela"); /* 5 */
+ regTestCompareFiles(rp, 4, 5); /* 6 */
+ selaDestroy(&sela2);
+
+ /* Attempt to create sels from invalid strings (0 or 2 origins) */
+ lept_stderr("******************************************************\n");
+ lept_stderr("* The next 2 error messages are intentional *\n");
+ sel = selCreateFromString(textsel5, 5, 6, "textsel5");
+ val = (sel) ? 1.0 : 0.0;
+ regTestCompareValues(rp, val, 0.0, 0.0); /* 6 */
+ sel = selCreateFromString(textsel6, 5, 6, "textsel6");
+ val = (sel) ? 1.0 : 0.0;
+ regTestCompareValues(rp, val, 0.0, 0.0); /* 7 */
+ lept_stderr("******************************************************\n");
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/settest.c b/leptonica/prog/settest.c
new file mode 100644
index 00000000..dac1f7ac
--- /dev/null
+++ b/leptonica/prog/settest.c
@@ -0,0 +1,146 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * settest.c
+ *
+ * Tests set function for RGB (uint32) keys.
+ *
+ * We take a colormapped image and use the set to find the unique
+ * colors in the image. These are stored as 32-bit rgb keys.
+ * Also test the iterator on the set.
+ *
+ * For a more complete set of tests, see the operations tested in maptest.c.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static L_ASET *BuildSet(PIX *pix, l_int32 factor, l_int32 print);
+static void TestSetIterator(L_ASET *s, l_int32 print);
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+L_ASET *s;
+PIX *pix;
+
+ setLeptDebugOK(1);
+ pix = pixRead("weasel8.240c.png");
+
+ /* Build the set from all the pixels. */
+ s = BuildSet(pix, 1, FALSE);
+ TestSetIterator(s, FALSE);
+ l_asetDestroy(&s);
+
+ /* Ditto, but just with a few pixels */
+ s = BuildSet(pix, 10, TRUE);
+ TestSetIterator(s, TRUE);
+ l_asetDestroy(&s);
+ pixDestroy(&pix);
+
+ pix = pixRead("marge.jpg");
+ startTimer();
+ s = BuildSet(pix, 1, FALSE);
+ lept_stderr("Time (250K pixels): %7.3f sec\n", stopTimer());
+ TestSetIterator(s, FALSE);
+ l_asetDestroy(&s);
+ pixDestroy(&pix);
+ return 0;
+}
+
+static L_ASET *
+BuildSet(PIX *pix,
+ l_int32 factor,
+ l_int32 print)
+{
+l_int32 i, j, w, h, wpl, val;
+l_uint32 val32;
+l_uint32 *data, *line;
+L_ASET *s;
+PIXCMAP *cmap;
+RB_TYPE key;
+RB_TYPE *pval;
+
+ lept_stderr("\n --------------- Begin building set --------------\n");
+ s = l_asetCreate(L_UINT_TYPE);
+ data = pixGetData(pix);
+ wpl = pixGetWpl(pix);
+ cmap = pixGetColormap(pix);
+ pixGetDimensions(pix, &w, &h, NULL);
+ for (i = 0; i < h; i += factor) {
+ line = data + i * wpl;
+ for (j = 0; j < w; j += factor) {
+ if (cmap) {
+ val = GET_DATA_BYTE(line, j);
+ pixcmapGetColor32(cmap, val, &val32);
+ key.utype = val32;
+ } else {
+ key.utype = line[j];
+ }
+ pval = l_asetFind(s, key);
+ if (pval && print)
+ lept_stderr("key = %llx\n", key.utype);
+ l_asetInsert(s, key);
+ }
+ }
+ lept_stderr("Size: %d\n", l_asetSize(s));
+ if (print)
+ l_rbtreePrint(stderr, s);
+ lept_stderr(" ----------- End Building set -----------------\n");
+
+ return s;
+}
+
+static void
+TestSetIterator(L_ASET *s,
+ l_int32 print)
+{
+l_int32 count;
+L_ASET_NODE *n;
+
+ n = l_asetGetFirst(s);
+ count = 0;
+ lept_stderr("\n --------------- Begin iter listing --------------\n");
+ while (n) {
+ count++;
+ if (print)
+#if 0
+ lept_stderr("key = %x\n", n->key.utype);
+#else
+ lept_stderr("key = %llx\n", n->key.utype);
+#endif
+ n = l_asetGetNext(n);
+ }
+ lept_stderr("Count from iterator: %d\n", count);
+ lept_stderr(" --------------- End iter listing --------------\n");
+ return;
+}
+
diff --git a/leptonica/prog/sevens.tif b/leptonica/prog/sevens.tif
new file mode 100644
index 00000000..46d7e4f4
--- /dev/null
+++ b/leptonica/prog/sevens.tif
Binary files differ
diff --git a/leptonica/prog/sharptest.c b/leptonica/prog/sharptest.c
new file mode 100644
index 00000000..a4ea764c
--- /dev/null
+++ b/leptonica/prog/sharptest.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * sharptest.c
+ *
+ * sharptest filein smooth fract fileout
+ *
+ * (1) Use smooth = 1 for 3x3 smoothing filter
+ * smooth = 2 for 5x5 smoothing filter, etc.
+ * (2) Use fract in typical range (0.2 - 0.7)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pixd;
+l_int32 smooth;
+l_float32 fract;
+char *filein, *fileout;
+static char mainName[] = "sharptest";
+
+ if (argc != 5)
+ return ERROR_INT(" Syntax: sharptest filein smooth fract fileout",
+ mainName, 1);
+ filein = argv[1];
+ smooth = atoi(argv[2]);
+ fract = atof(argv[3]);
+ fileout = argv[4];
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ pixd = pixUnsharpMasking(pixs, smooth, fract);
+ pixWrite(fileout, pixd, IFF_JFIF_JPEG);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
+
diff --git a/leptonica/prog/shear1_reg.c b/leptonica/prog/shear1_reg.c
new file mode 100644
index 00000000..ec319923
--- /dev/null
+++ b/leptonica/prog/shear1_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * shear1_reg.c
+ *
+ * Regression test for shear, both IP and to new pix.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define BINARY_IMAGE "test1.png"
+#define TWO_BPP_IMAGE "weasel2.4c.png"
+#define FOUR_BPP_IMAGE1 "weasel4.11c.png"
+#define FOUR_BPP_IMAGE2 "weasel4.16g.png"
+#define EIGHT_BPP_IMAGE "test8.jpg"
+#define EIGHT_BPP_CMAP_IMAGE1 "dreyfus8.png"
+#define EIGHT_BPP_CMAP_IMAGE2 "test24.jpg"
+#define RGB_IMAGE "marge.jpg"
+
+static PIX *shearTest1(PIX *pixs, l_float32 scale);
+static PIX *shearTest2(PIX *pixs, L_REGPARAMS *rp);
+
+static const l_float32 ANGLE1 = 3.14159265 / 12.;
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 index;
+PIX *pixs, *pix1, *pixc, *pixd;
+PIXCMAP *cmap;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_stderr("Test binary image:\n");
+ pixs = pixRead(BINARY_IMAGE);
+ pixd = shearTest1(pixs, 1.0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+
+ /* We change the black to dark red so that we can see
+ * that the IP shear does brings in that color. It
+ * can't bring in black because the cmap is filled. */
+ lept_stderr("Test 2 bpp cmapped image with filled cmap:\n");
+ pixs = pixRead(TWO_BPP_IMAGE);
+ cmap = pixGetColormap(pixs);
+ pixcmapGetIndex(cmap, 40, 44, 40, &index);
+ pixcmapResetColor(cmap, index, 100, 0, 0);
+ pixd = shearTest1(pixs, 1.0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+
+ lept_stderr("Test 4 bpp cmapped image with unfilled cmap:\n");
+ pixs = pixRead(FOUR_BPP_IMAGE1);
+ pixd = shearTest1(pixs, 1.0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+
+ lept_stderr("Test 4 bpp cmapped image with filled cmap:\n");
+ pixs = pixRead(FOUR_BPP_IMAGE2);
+ pixd = shearTest1(pixs, 1.0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+
+ lept_stderr("Test 8 bpp grayscale image:\n");
+ pixs = pixRead(EIGHT_BPP_IMAGE);
+ pix1 = pixScale(pixs, 0.5, 0.5);
+ pixd = shearTest1(pixs, 1.0);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 4 */
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pixd);
+
+ lept_stderr("Test 8 bpp grayscale cmap image:\n");
+ pixs = pixRead(EIGHT_BPP_CMAP_IMAGE1);
+ pixd = shearTest1(pixs, 1.0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+
+ lept_stderr("Test 8 bpp color cmap image:\n");
+ pixs = pixRead(EIGHT_BPP_CMAP_IMAGE2);
+ pix1 = pixScale(pixs, 0.3, 0.3);
+ pixd = pixOctreeColorQuant(pix1, 200, 0);
+ pixc = shearTest1(pixd, 1.0);
+ regTestWritePixAndCheck(rp, pixc, IFF_JFIF_JPEG); /* 6 */
+ pixDisplayWithTitle(pixc, 100, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pixd);
+ pixDestroy(&pixc);
+
+ lept_stderr("Test rgb image:\n");
+ pixs = pixRead(RGB_IMAGE);
+ pix1 = pixScale(pixs, 0.3, 0.3);
+ pixd = shearTest1(pix1, 1.0);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 7 */
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pixd);
+
+#if 1
+ lept_stderr("Test in-place shear on 4 bpp cmapped image:\n");
+ pixs = pixRead(FOUR_BPP_IMAGE1);
+ pixd = shearTest2(pixs, rp);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 12 */
+ pixDisplayWithTitle(pixd, 800, 100, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+#endif
+
+ return regTestCleanup(rp);
+}
+
+/* ------------------------------------------------------------- */
+static PIX *
+shearTest1(PIX *pixs,
+ l_float32 scale)
+{
+l_int32 w, h, d;
+PIX *pix1, *pix2, *pixd;
+PIXA *pixa;
+
+ pixa = pixaCreate(0);
+ pixGetDimensions(pixs, &w, &h, &d);
+
+ pix1 = pixHShear(NULL, pixs, 0, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixHShear(NULL, pixs, h / 2, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix1 = pixHShear(NULL, pixs, 0, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixHShear(NULL, pixs, h / 2, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ if (!pixGetColormap(pixs)) {
+ pix1 = pixCopy(NULL, pixs);
+ pixHShearIP(pix1, 0, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixCopy(NULL, pixs);
+ pixHShearIP(pix2, h / 2, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix1 = pixCopy(NULL, pixs);
+ pixHShearIP(pix1, 0, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixCopy(NULL, pixs);
+ pixHShearIP(pix2, h / 2, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ }
+
+ if (d == 8 || d == 32 || pixGetColormap(pixs)) {
+ pix1 = pixHShearLI(pixs, 0, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixHShearLI(pixs, w / 2, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix1 = pixHShearLI(pixs, 0, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixHShearLI(pixs, w / 2, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ }
+
+ pix1 = pixVShear(NULL, pixs, 0, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixVShear(NULL, pixs, w / 2, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix1 = pixVShear(NULL, pixs, 0, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixVShear(NULL, pixs, w / 2, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix2, L_INSERT);
+
+ if (!pixGetColormap(pixs)) {
+ pix1 = pixCopy(NULL, pixs);
+ pixVShearIP(pix1, 0, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixCopy(NULL, pixs);
+ pixVShearIP(pix2, w / 2, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix1 = pixCopy(NULL, pixs);
+ pixVShearIP(pix1, 0, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixCopy(NULL, pixs);
+ pixVShearIP(pix2, w / 2, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ }
+
+ if (d == 8 || d == 32 || pixGetColormap(pixs)) {
+ pix1 = pixVShearLI(pixs, 0, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixVShearLI(pixs, w / 2, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix1 = pixVShearLI(pixs, 0, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixVShearLI(pixs, w / 2, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ }
+
+ pixd = pixaDisplayTiledInColumns(pixa, 4, scale, 20, 0);
+ pixaDestroy(&pixa);
+ return pixd;
+}
+
+/* ------------------------------------------------------------- */
+static PIX *
+shearTest2(PIX *pixs,
+ L_REGPARAMS *rp)
+{
+l_int32 w, h;
+PIX *pix1, *pix2, *pixd;
+PIXA *pixa;
+
+ pixa = pixaCreate(0);
+ pixGetDimensions(pixs, &w, &h, NULL);
+
+ pix1 = pixHShear(NULL, pixs, h / 2, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixCopy(NULL, pixs);
+ pixHShear(pix2, pix2, h / 2, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 8 */
+ pix1 = pixHShear(NULL, pixs, h / 2, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixCopy(NULL, pixs);
+ pixHShear(pix2, pix2, h / 2, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 9 */
+
+ pix1 = pixVShear(NULL, pixs, w / 2, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixCopy(NULL, pixs);
+ pixVShear(pix2, pix2, w / 2, ANGLE1, L_BRING_IN_WHITE);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 10 */
+ pix1 = pixVShear(NULL, pixs, w / 2, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pix2 = pixCopy(NULL, pixs);
+ pixVShear(pix2, pix2, w / 2, ANGLE1, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ regTestComparePix(rp, pix1, pix2); /* 11 */
+
+ pixd = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 0);
+ pixaDestroy(&pixa);
+ return pixd;
+}
diff --git a/leptonica/prog/shear2_reg.c b/leptonica/prog/shear2_reg.c
new file mode 100644
index 00000000..1922e59e
--- /dev/null
+++ b/leptonica/prog/shear2_reg.c
@@ -0,0 +1,185 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * shear2_reg.c
+ *
+ * Regression test for quadratic shear, both sampled and interpolated.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void PixSave(PIX **ppixs, PIXA *pixa, l_int32 newrow,
+ L_BMF *bmf, const char *textstr);
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+L_BMF *bmf;
+PIX *pixs1, *pixs2, *pixg, *pixt, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "shear2_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ bmf = bmfCreate("./fonts", 8);
+ pixs1 = pixCreate(301, 301, 32);
+ pixs2 = pixCreate(601, 601, 32);
+ pixSetAll(pixs1);
+ pixSetAll(pixs2);
+ pixRenderLineArb(pixs1, 0, 20, 300, 20, 5, 0, 0, 255);
+ pixRenderLineArb(pixs1, 0, 70, 300, 70, 5, 0, 255, 0);
+ pixRenderLineArb(pixs1, 0, 120, 300, 120, 5, 0, 255, 255);
+ pixRenderLineArb(pixs1, 0, 170, 300, 170, 5, 255, 0, 0);
+ pixRenderLineArb(pixs1, 0, 220, 300, 220, 5, 255, 0, 255);
+ pixRenderLineArb(pixs1, 0, 270, 300, 270, 5, 255, 255, 0);
+ pixRenderLineArb(pixs2, 0, 20, 300, 20, 5, 0, 0, 255);
+ pixRenderLineArb(pixs2, 0, 70, 300, 70, 5, 0, 255, 0);
+ pixRenderLineArb(pixs2, 0, 120, 300, 120, 5, 0, 255, 255);
+ pixRenderLineArb(pixs2, 0, 170, 300, 170, 5, 255, 0, 0);
+ pixRenderLineArb(pixs2, 0, 220, 300, 220, 5, 255, 0, 255);
+ pixRenderLineArb(pixs2, 0, 270, 300, 270, 5, 255, 255, 0);
+
+ /* Color, small pix */
+ pixa = pixaCreate(0);
+ pixt = pixQuadraticVShear(pixs1, L_WARP_TO_LEFT,
+ 60, -20, L_SAMPLED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 1, bmf, "sampled-left");
+ pixt = pixQuadraticVShear(pixs1, L_WARP_TO_RIGHT,
+ 60, -20, L_SAMPLED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 0, bmf, "sampled-right");
+ pixt = pixQuadraticVShear(pixs1, L_WARP_TO_LEFT,
+ 60, -20, L_INTERPOLATED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 1, bmf, "interpolated-left");
+ pixt = pixQuadraticVShear(pixs1, L_WARP_TO_RIGHT,
+ 60, -20, L_INTERPOLATED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 0, bmf, "interpolated-right");
+ pixd = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixDisplayWithTitle(pixd, 50, 50, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ /* Grayscale, small pix */
+ pixg = pixConvertTo8(pixs1, 0);
+ pixa = pixaCreate(0);
+ pixt = pixQuadraticVShear(pixg, L_WARP_TO_LEFT,
+ 60, -20, L_SAMPLED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 1, bmf, "sampled-left");
+ pixt = pixQuadraticVShear(pixg, L_WARP_TO_RIGHT,
+ 60, -20, L_SAMPLED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 0, bmf, "sampled-right");
+ pixt = pixQuadraticVShear(pixg, L_WARP_TO_LEFT,
+ 60, -20, L_INTERPOLATED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 1, bmf, "interpolated-left");
+ pixt = pixQuadraticVShear(pixg, L_WARP_TO_RIGHT,
+ 60, -20, L_INTERPOLATED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 0, bmf, "interpolated-right");
+ pixd = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixDisplayWithTitle(pixd, 250, 50, NULL, rp->display);
+ pixDestroy(&pixg);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ /* Color, larger pix */
+ pixa = pixaCreate(0);
+ pixt = pixQuadraticVShear(pixs2, L_WARP_TO_LEFT,
+ 120, -40, L_SAMPLED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 1, bmf, "sampled-left");
+ pixt = pixQuadraticVShear(pixs2, L_WARP_TO_RIGHT,
+ 120, -40, L_SAMPLED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 0, bmf, "sampled-right");
+ pixt = pixQuadraticVShear(pixs2, L_WARP_TO_LEFT,
+ 120, -40, L_INTERPOLATED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 1, bmf, "interpolated-left");
+ pixt = pixQuadraticVShear(pixs2, L_WARP_TO_RIGHT,
+ 120, -40, L_INTERPOLATED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 0, bmf, "interpolated-right");
+ pixd = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixDisplayWithTitle(pixd, 550, 50, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ /* Grayscale, larger pix */
+ pixg = pixConvertTo8(pixs2, 0);
+ pixa = pixaCreate(0);
+ pixt = pixQuadraticVShear(pixg, L_WARP_TO_LEFT,
+ 60, -20, L_SAMPLED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 1, bmf, "sampled-left");
+ pixt = pixQuadraticVShear(pixg, L_WARP_TO_RIGHT,
+ 60, -20, L_SAMPLED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 0, bmf, "sampled-right");
+ pixt = pixQuadraticVShear(pixg, L_WARP_TO_LEFT,
+ 60, -20, L_INTERPOLATED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 1, bmf, "interpolated-left");
+ pixt = pixQuadraticVShear(pixg, L_WARP_TO_RIGHT,
+ 60, -20, L_INTERPOLATED, L_BRING_IN_WHITE);
+ PixSave(&pixt, pixa, 0, bmf, "interpolated-right");
+ pixd = pixaDisplayTiledInColumns(pixa, 2, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixDisplayWithTitle(pixd, 850, 50, NULL, rp->display);
+ pixDestroy(&pixg);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ pixDestroy(&pixs1);
+ pixDestroy(&pixs2);
+ bmfDestroy(&bmf);
+ return regTestCleanup(rp);
+}
+
+
+void
+PixSave(PIX **ppixs,
+ PIXA *pixa,
+ l_int32 newrow,
+ L_BMF *bmf,
+ const char *textstr)
+{
+PIX *pix1, *pix2, *pix3;
+
+ pix1 = pixConvertTo32(*ppixs);
+ pix2 = pixAddBorder(pix1, 3, 0);
+ pix3 = pixAddSingleTextblock(pix2, bmf, textstr, 0xff000000, L_ADD_BELOW,
+ NULL);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(ppixs);
+}
diff --git a/leptonica/prog/shearer.148.tif b/leptonica/prog/shearer.148.tif
new file mode 100644
index 00000000..b619ea25
--- /dev/null
+++ b/leptonica/prog/shearer.148.tif
Binary files differ
diff --git a/leptonica/prog/sheartest.c b/leptonica/prog/sheartest.c
new file mode 100644
index 00000000..3dc691c1
--- /dev/null
+++ b/leptonica/prog/sheartest.c
@@ -0,0 +1,169 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * sheartest.c
+ *
+ * sheartest filein angle fileout
+ *
+ * where angle is expressed in degrees
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define NTIMES 10
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 i, w, h, liney, linex, same;
+l_float32 angle, deg2rad;
+PIX *pixt1, *pixt2, *pixs, *pixd;
+static char mainName[] = "sheartest";
+
+ if (argc != 4)
+ return ERROR_INT(" Syntax: sheartest filein angle fileout",
+ mainName, 1);
+
+ setLeptDebugOK(1);
+
+ /* Compare in-place H shear with H shear to a new pix */
+ pixt1 = pixRead("marge.jpg");
+ pixGetDimensions(pixt1, &w, &h, NULL);
+ pixt2 = pixHShear(NULL, pixt1, (l_int32)(0.3 * h), 0.17, L_BRING_IN_WHITE);
+ pixHShearIP(pixt1, (l_int32)(0.3 * h), 0.17, L_BRING_IN_WHITE);
+ pixEqual(pixt1, pixt2, &same);
+ if (same)
+ lept_stderr("Correct for H shear\n");
+ else
+ lept_stderr("Error for H shear\n");
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ /* Compare in-place V shear with V shear to a new pix */
+ pixt1 = pixRead("marge.jpg");
+ pixGetDimensions(pixt1, &w, &h, NULL);
+ pixt2 = pixVShear(NULL, pixt1, (l_int32)(0.3 * w), 0.17, L_BRING_IN_WHITE);
+ pixVShearIP(pixt1, (l_int32)(0.3 * w), 0.17, L_BRING_IN_WHITE);
+ pixEqual(pixt1, pixt2, &same);
+ if (same)
+ lept_stderr("Correct for V shear\n");
+ else
+ lept_stderr("Error for V shear\n");
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+
+ filein = argv[1];
+ angle = atof(argv[2]);
+ fileout = argv[3];
+ deg2rad = 3.1415926535 / 180.;
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pix not made", mainName, 1);
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+
+#if 0
+ /* Select an operation from this list ...
+ * ------------------------------------------
+ pixd = pixHShear(NULL, pixs, liney, deg2rad * angle, L_BRING_IN_WHITE);
+ pixd = pixVShear(NULL, pixs, linex, deg2rad * angle, L_BRING_IN_WHITE);
+ pixd = pixHShearCorner(NULL, pixs, deg2rad * angle, L_BRING_IN_WHITE);
+ pixd = pixVShearCorner(NULL, pixs, deg2rad * angle, L_BRING_IN_WHITE);
+ pixd = pixHShearCenter(NULL, pixs, deg2rad * angle, L_BRING_IN_WHITE);
+ pixd = pixVShearCenter(NULL, pixs, deg2rad * angle, L_BRING_IN_WHITE);
+ pixHShearIP(pixs, liney, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixs;
+ pixVShearIP(pixs, linex, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixs;
+ pixRasteropHip(pixs, 0, h/3, -50, L_BRING_IN_WHITE); pixd = pixs;
+ pixRasteropVip(pixs, 0, w/3, -50, L_BRING_IN_WHITE); pixd = pixs;
+ * ------------------------------------------
+ * ... and use it in the following: */
+ pixd = pixHShear(NULL, pixs, liney, deg2rad * angle, L_BRING_IN_WHITE);
+ pixWrite(fileout, pixd, IFF_PNG);
+ pixDisplay(pixd, 50, 50);
+ pixDestroy(&pixd);
+#endif
+
+#if 0
+ /* Do a horizontal shear about a line */
+ for (i = 0; i < NTIMES; i++) {
+ liney = i * h / (NTIMES - 1);
+ if (liney >= h)
+ liney = h - 1;
+ pixd = pixHShear(NULL, pixs, liney, deg2rad * angle, L_BRING_IN_WHITE);
+ pixDisplay(pixd, 50 + 10 * i, 50 + 10 * i);
+ pixDestroy(&pixd);
+ }
+#endif
+
+#if 0
+ /* Do a vertical shear about a line */
+ for (i = 0; i < NTIMES; i++) {
+ linex = i * w / (NTIMES - 1);
+ if (linex >= w)
+ linex = w - 1;
+ pixd = pixVShear(NULL, pixs, linex, deg2rad * angle, L_BRING_IN_WHITE);
+ pixDisplay(pixd, 50 + 10 * i, 50 + 10 * i);
+ pixDestroy(&pixd);
+ }
+#endif
+
+#if 0
+ /* Do a horizontal in-place shear about a line */
+ pixSetPadBits(pixs, 0);
+ for (i = 0; i < NTIMES; i++) {
+ pixd = pixCopy(NULL, pixs);
+ liney = i * h / (NTIMES - 1);
+ if (liney >= h)
+ liney = h - 1;
+ pixHShearIP(pixd, liney, deg2rad * angle, L_BRING_IN_WHITE);
+ pixDisplay(pixd, 50 + 10 * i, 50 + 10 * i);
+ pixDestroy(&pixd);
+ }
+#endif
+
+#if 0
+ /* Do a vertical in-place shear about a line */
+ for (i = 0; i < NTIMES; i++) {
+ pixd = pixCopy(NULL, pixs);
+ linex = i * w / (NTIMES - 1);
+ if (linex >= w)
+ linex = w - 1;
+ pixVShearIP(pixd, linex, deg2rad * angle, L_BRING_IN_WHITE);
+ pixDisplay(pixd, 50 + 10 * i, 50 + 10 * i);
+ pixDestroy(&pixd);
+ }
+#endif
+
+ pixDestroy(&pixs);
+ return 0;
+}
+
diff --git a/leptonica/prog/showboxes.pac b/leptonica/prog/showboxes.pac
new file mode 100644
index 00000000..39b18ea7
--- /dev/null
+++ b/leptonica/prog/showboxes.pac
Binary files differ
diff --git a/leptonica/prog/showboxes1.baa b/leptonica/prog/showboxes1.baa
new file mode 100644
index 00000000..efe2ceb0
--- /dev/null
+++ b/leptonica/prog/showboxes1.baa
@@ -0,0 +1,27 @@
+
+Boxaa Version 3
+Number of boxa = 2
+
+Boxa[0] extent: x = 16, y = 6, w = 318, h = 228
+Boxa Version 2
+Number of boxes = 8
+ Box[0]: x = 44, y = 8, w = 289, h = 226
+ Box[1]: x = 44, y = 6, w = 288, h = 228
+ Box[2]: x = 42, y = 11, w = 292, h = 223
+ Box[3]: x = 16, y = 18, w = 297, h = 216
+ Box[4]: x = 42, y = 10, w = 292, h = 224
+ Box[5]: x = 42, y = 11, w = 291, h = 223
+ Box[6]: x = 42, y = 10, w = 291, h = 224
+ Box[7]: x = 43, y = 9, w = 291, h = 225
+
+Boxa[1] extent: x = 22, y = 9, w = 309, h = 212
+Boxa Version 2
+Number of boxes = 8
+ Box[0]: x = 53, y = 12, w = 277, h = 209
+ Box[1]: x = 51, y = 22, w = 278, h = 162
+ Box[2]: x = 51, y = 11, w = 280, h = 187
+ Box[3]: x = 22, y = 21, w = 287, h = 198
+ Box[4]: x = 50, y = 10, w = 281, h = 196
+ Box[5]: x = 50, y = 11, w = 280, h = 184
+ Box[6]: x = 50, y = 10, w = 280, h = 186
+ Box[7]: x = 50, y = 9, w = 280, h = 192
diff --git a/leptonica/prog/showboxes2.baa b/leptonica/prog/showboxes2.baa
new file mode 100644
index 00000000..c8a1b93f
--- /dev/null
+++ b/leptonica/prog/showboxes2.baa
@@ -0,0 +1,51 @@
+
+Boxaa Version 3
+Number of boxa = 8
+
+Boxa[0] extent: x = 44, y = 8, w = 289, h = 226
+Boxa Version 2
+Number of boxes = 2
+ Box[0]: x = 44, y = 8, w = 289, h = 226
+ Box[1]: x = 53, y = 12, w = 277, h = 209
+
+Boxa[1] extent: x = 44, y = 6, w = 288, h = 228
+Boxa Version 2
+Number of boxes = 2
+ Box[0]: x = 44, y = 6, w = 288, h = 228
+ Box[1]: x = 51, y = 22, w = 278, h = 162
+
+Boxa[2] extent: x = 42, y = 11, w = 292, h = 223
+Boxa Version 2
+Number of boxes = 2
+ Box[0]: x = 42, y = 11, w = 292, h = 223
+ Box[1]: x = 51, y = 11, w = 280, h = 187
+
+Boxa[3] extent: x = 16, y = 18, w = 297, h = 216
+Boxa Version 2
+Number of boxes = 2
+ Box[0]: x = 16, y = 18, w = 297, h = 216
+ Box[1]: x = 22, y = 21, w = 287, h = 198
+
+Boxa[4] extent: x = 42, y = 10, w = 292, h = 224
+Boxa Version 2
+Number of boxes = 2
+ Box[0]: x = 42, y = 10, w = 292, h = 224
+ Box[1]: x = 50, y = 10, w = 281, h = 196
+
+Boxa[5] extent: x = 42, y = 11, w = 291, h = 223
+Boxa Version 2
+Number of boxes = 2
+ Box[0]: x = 42, y = 11, w = 291, h = 223
+ Box[1]: x = 50, y = 11, w = 280, h = 184
+
+Boxa[6] extent: x = 42, y = 10, w = 291, h = 224
+Boxa Version 2
+Number of boxes = 2
+ Box[0]: x = 42, y = 10, w = 291, h = 224
+ Box[1]: x = 50, y = 10, w = 280, h = 186
+
+Boxa[7] extent: x = 43, y = 9, w = 291, h = 225
+Boxa Version 2
+Number of boxes = 2
+ Box[0]: x = 43, y = 9, w = 291, h = 225
+ Box[1]: x = 50, y = 9, w = 280, h = 192
diff --git a/leptonica/prog/showedges.c b/leptonica/prog/showedges.c
new file mode 100644
index 00000000..9ee7ee84
--- /dev/null
+++ b/leptonica/prog/showedges.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * showedges.c
+ *
+ * Uses computation of half edge function, along with thresholding.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define SMOOTH_WIDTH_1 2 /* must be smaller */
+#define SMOOTH_WIDTH_2 4 /* must be larger */
+#define THRESHOLD 5 /* low works best */
+
+
+int main(int argc,
+ char **argv)
+{
+char *infile, *outfile;
+l_int32 d;
+PIX *pixs, *pixgr, *pixb;
+static char mainName[] = "showedges";
+
+ if (argc != 3)
+ return ERROR_INT(" Syntax: showedges infile outfile", mainName, 1);
+ infile = argv[1];
+ outfile = argv[2];
+ setLeptDebugOK(1);
+
+ pixs = pixRead(infile);
+ d = pixGetDepth(pixs);
+ if (d != 8 && d != 32)
+ return ERROR_INT("d not 8 or 32 bpp", mainName, 1);
+
+ pixgr = pixHalfEdgeByBandpass(pixs, SMOOTH_WIDTH_1, SMOOTH_WIDTH_1,
+ SMOOTH_WIDTH_2, SMOOTH_WIDTH_2);
+ pixb = pixThresholdToBinary(pixgr, THRESHOLD);
+ pixInvert(pixb, pixb);
+/* pixWrite("junkpixgr", pixgr, IFF_JFIF_JPEG); */
+ pixWrite(outfile, pixb, IFF_PNG);
+ return 0;
+}
+
diff --git a/leptonica/prog/singlecc.tif b/leptonica/prog/singlecc.tif
new file mode 100644
index 00000000..cee282b4
--- /dev/null
+++ b/leptonica/prog/singlecc.tif
Binary files differ
diff --git a/leptonica/prog/skew_reg.c b/leptonica/prog/skew_reg.c
new file mode 100644
index 00000000..ede0cb6e
--- /dev/null
+++ b/leptonica/prog/skew_reg.c
@@ -0,0 +1,132 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * Regression test for skew detection.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* deskew */
+#define DESKEW_REDUCTION 4 /* 1, 2 or 4 */
+
+ /* sweep only */
+#define SWEEP_RANGE 5. /* degrees */
+#define SWEEP_DELTA 0.2 /* degrees */
+#define SWEEP_REDUCTION 2 /* 1, 2, 4 or 8 */
+
+ /* sweep and search */
+#define SWEEP_RANGE2 5. /* degrees */
+#define SWEEP_DELTA2 1. /* degrees */
+#define SWEEP_REDUCTION2 2 /* 1, 2, 4 or 8 */
+#define SEARCH_REDUCTION 2 /* 1, 2, 4 or 8 */
+#define SEARCH_MIN_DELTA 0.01 /* degrees */
+
+static const l_int32 BORDER = 150;
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w, h, wd, hd;
+l_float32 deg2rad, angle, conf;
+PIX *pixs, *pixb1, *pixb2, *pixr, *pixf, *pixd, *pixc;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ deg2rad = 3.1415926535 / 180.;
+
+ pixa = pixaCreate(0);
+ pixs = pixRead("feyn.tif");
+ pixSetOrClearBorder(pixs, 100, 250, 100, 0, PIX_CLR);
+ pixb1 = pixReduceRankBinaryCascade(pixs, 2, 2, 0, 0);
+ regTestWritePixAndCheck(rp, pixb1, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pixb1, 0, 100, NULL, rp->display);
+
+ /* Add a border and locate and deskew a 40 degree rotation */
+ pixb2 = pixAddBorder(pixb1, BORDER, 0);
+ pixGetDimensions(pixb2, &w, &h, NULL);
+ pixaAddPix(pixa, pixb2, L_COPY);
+ pixr = pixRotateBySampling(pixb2, w / 2, h / 2,
+ deg2rad * 40., L_BRING_IN_WHITE);
+ regTestWritePixAndCheck(rp, pixr, IFF_PNG); /* 1 */
+ pixaAddPix(pixa, pixr, L_INSERT);
+ pixFindSkewSweepAndSearchScorePivot(pixr, &angle, &conf, NULL, 1, 1,
+ 0.0, 45.0, 2.0, 0.03,
+ L_SHEAR_ABOUT_CENTER);
+ lept_stderr("Should be 40 degrees: angle = %7.3f, conf = %7.3f\n",
+ angle, conf);
+ pixf = pixRotateBySampling(pixr, w / 2, h / 2,
+ deg2rad * angle, L_BRING_IN_WHITE);
+ pixd = pixRemoveBorder(pixf, BORDER);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 2 */
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pixf);
+
+ /* Do a rotation larger than 90 degrees using embedding;
+ * Use 2 sets of measurements at 90 degrees to scan the
+ * full range of possible rotation angles. */
+ pixGetDimensions(pixb1, &w, &h, NULL);
+ pixr = pixRotate(pixb1, deg2rad * 37., L_ROTATE_SAMPLING,
+ L_BRING_IN_WHITE, w, h);
+ regTestWritePixAndCheck(rp, pixr, IFF_PNG); /* 3 */
+ pixaAddPix(pixa, pixr, L_INSERT);
+ startTimer();
+ pixFindSkewOrthogonalRange(pixr, &angle, &conf, 2, 1,
+ 47.0, 1.0, 0.03, 0.0);
+ lept_stderr("Orth search time: %7.3f sec\n", stopTimer());
+ lept_stderr("Should be about -128 degrees: angle = %7.3f\n", angle);
+ pixd = pixRotate(pixr, deg2rad * angle, L_ROTATE_SAMPLING,
+ L_BRING_IN_WHITE, w, h);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 4 */
+ pixGetDimensions(pixd, &wd, &hd, NULL);
+ pixc = pixCreate(w, h, 1);
+ pixRasterop(pixc, 0, 0, w, h, PIX_SRC, pixd, (wd - w) / 2, (hd - h) / 2);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 5 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixDestroy(&pixf);
+ pixDestroy(&pixd);
+
+ pixd = pixaDisplayTiledInColumns(pixa, 3, 0.5, 20, 3);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pixb1);
+ pixDestroy(&pixb2);
+ pixaDestroy(&pixa);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/skewtest.c b/leptonica/prog/skewtest.c
new file mode 100644
index 00000000..65866f92
--- /dev/null
+++ b/leptonica/prog/skewtest.c
@@ -0,0 +1,192 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+
+/*
+ * skewtest.c
+ *
+ * Tests various skew finding methods, optionally deskewing
+ * the input (binary) image. The best version does a linear
+ * sweep followed by a binary (angle-splitting) search.
+ * The basic method is to find the vertical shear angle such
+ * that the differential variance of ON pixels between each
+ * line and it's neighbor, when summed over all lines, is
+ * maximized.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* binarization threshold */
+#define BIN_THRESHOLD 130
+
+ /* deskew */
+#define DESKEW_REDUCTION 2 /* 1, 2 or 4 */
+
+ /* sweep only */
+#define SWEEP_RANGE 10. /* degrees */
+#define SWEEP_DELTA 0.2 /* degrees */
+#define SWEEP_REDUCTION 2 /* 1, 2, 4 or 8 */
+
+ /* sweep and search */
+#define SWEEP_RANGE2 10. /* degrees */
+#define SWEEP_DELTA2 1. /* degrees */
+#define SWEEP_REDUCTION2 2 /* 1, 2, 4 or 8 */
+#define SEARCH_REDUCTION 2 /* 1, 2, 4 or 8 */
+#define SEARCH_MIN_DELTA 0.01 /* degrees */
+
+
+int main(int argc,
+ char **argv)
+{
+char *filein;
+l_int32 ret;
+l_float32 deg2rad;
+l_float32 angle, conf, score, endscore;
+PIX *pix, *pixs, *pixd;
+static char mainName[] = "skewtest";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: skewtest filein", mainName, 1);
+ filein = argv[1];
+
+ setLeptDebugOK(1);
+ lept_rmdir("lept/deskew");
+ lept_mkdir("lept/deskew");
+ pixd = NULL;
+ deg2rad = 3.1415926535 / 180.;
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ /* Find the skew angle various ways */
+ pix = pixConvertTo1(pixs, BIN_THRESHOLD);
+ pixWrite("/tmp/lept/deskew/binarized.tif", pix, IFF_TIFF_G4);
+ pixFindSkew(pix, &angle, &conf);
+ lept_stderr("pixFindSkew():\n"
+ " conf = %5.3f, angle = %7.3f degrees\n", conf, angle);
+
+ pixFindSkewSweepAndSearchScorePivot(pix, &angle, &conf, &score,
+ SWEEP_REDUCTION2, SEARCH_REDUCTION,
+ 0.0, SWEEP_RANGE2, SWEEP_DELTA2,
+ SEARCH_MIN_DELTA,
+ L_SHEAR_ABOUT_CORNER);
+ lept_stderr("pixFind...Pivot(about corner):\n"
+ " conf = %5.3f, angle = %7.3f degrees, score = %.0f\n",
+ conf, angle, score);
+
+ pixFindSkewSweepAndSearchScorePivot(pix, &angle, &conf, &score,
+ SWEEP_REDUCTION2, SEARCH_REDUCTION,
+ 0.0, SWEEP_RANGE2, SWEEP_DELTA2,
+ SEARCH_MIN_DELTA,
+ L_SHEAR_ABOUT_CENTER);
+ lept_stderr("pixFind...Pivot(about center):\n"
+ " conf = %5.3f, angle = %7.3f degrees, score = %.0f\n",
+ conf, angle, score);
+
+ /* Use top-level */
+ pixd = pixDeskew(pixs, 0);
+ pixWriteImpliedFormat("/tmp/lept/deskew/result1", pixd, 0, 0);
+ pixDestroy(&pix);
+ pixDestroy(&pixd);
+
+#if 1
+ /* Do skew finding and rotation separately. This fails if
+ * the skew angle is outside the range. */
+ pix = pixConvertTo1(pixs, BIN_THRESHOLD);
+ if (pixGetDepth(pixs) == 1) {
+ pixd = pixDeskew(pix, DESKEW_REDUCTION);
+ pixWrite("/tmp/lept/deskew/result2", pixd, IFF_PNG);
+ }
+ else {
+ ret = pixFindSkewSweepAndSearch(pix, &angle, &conf, SWEEP_REDUCTION2,
+ SEARCH_REDUCTION, SWEEP_RANGE2,
+ SWEEP_DELTA2, SEARCH_MIN_DELTA);
+ if (ret)
+ L_WARNING("skew angle not valid\n", mainName);
+ else {
+ lept_stderr("conf = %5.3f, angle = %7.3f degrees\n", conf, angle);
+ if (conf > 2.5)
+ pixd = pixRotate(pixs, angle * deg2rad, L_ROTATE_AREA_MAP,
+ L_BRING_IN_WHITE, 0, 0);
+ else
+ pixd = pixClone(pixs);
+ pixWrite("/tmp/lept/deskew/result2", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ }
+ }
+ pixDestroy(&pix);
+#endif
+
+#if 1
+ pixFindSkewSweepAndSearchScore(pixs, &angle, &conf, &endscore,
+ 4, 2, 0.0, 5.0, 1.0, 0.01);
+ lept_stderr("angle = %8.4f, conf = %8.4f, endscore = %.0f\n",
+ angle, conf, endscore);
+ startTimer();
+ pixd = pixDeskew(pixs, DESKEW_REDUCTION);
+ lept_stderr("Time to deskew = %7.4f sec\n", stopTimer());
+ pixWrite("/tmp/lept/deskew/result3", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+#endif
+
+#if 1
+ ret = pixFindSkew(pixs, &angle, &conf);
+ lept_stderr("angle = %8.4f, conf = %8.4f\n", angle, conf);
+ if (ret) {
+ L_WARNING("skew angle not valid\n", mainName);
+ return 1;
+ }
+#endif
+
+#if 1
+ ret = pixFindSkewSweep(pixs, &angle, SWEEP_REDUCTION,
+ SWEEP_RANGE, SWEEP_DELTA);
+ lept_stderr("angle = %8.4f, conf = %8.4f\n", angle, conf);
+ if (ret) {
+ L_WARNING("skew angle not valid\n", mainName);
+ return 1;
+ }
+#endif
+
+#if 1
+ ret = pixFindSkewSweepAndSearch(pixs, &angle, &conf,
+ SWEEP_REDUCTION2, SEARCH_REDUCTION,
+ SWEEP_RANGE2, SWEEP_DELTA2,
+ SEARCH_MIN_DELTA);
+ lept_stderr("angle = %8.4f, conf = %8.4f\n", angle, conf);
+ if (ret) {
+ L_WARNING("skew angle not valid\n", mainName);
+ return 1;
+ }
+#endif
+
+ pixDestroy(&pixs);
+ return 0;
+}
diff --git a/leptonica/prog/smallpix_reg.c b/leptonica/prog/smallpix_reg.c
new file mode 100644
index 00000000..4c2f2d90
--- /dev/null
+++ b/leptonica/prog/smallpix_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * smallpix_reg.c
+ *
+ * This is a regression test for scaling and rotation.
+ *
+ * The question to be answered is: in the quantization, where, if
+ * anywhere, do we add 0.5?
+ *
+ * The answer is that it should usually, but not always, be omitted.
+ * To see this, we operate on a very small pix and for visualization,
+ * scale up with replication to avoid aliasing and shifting.
+ *
+ * To determine that the current implementations in scalelow.c,
+ * rotate.c and rotateamlow.c are better, change the specific
+ * implementations and re-run.
+ *
+ * In all cases here, the pix to be operated on is of odd size
+ * so that the center pixel is symmetrically located, and there
+ * are a couple of black pixels outside the pattern so that edge
+ * effects (e.g., in pixScaleSmooth()) do not affect the results.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void SaveAndDisplayPix(L_REGPARAMS *rp, PIXA **ppixa, l_int32 x, l_int32 y);
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i;
+l_float32 pi, scale, angle;
+PIX *pixc, *pixm, *pix1, *pix2, *pix3;
+PIXA *pixa;
+PTA *pta1, *pta2, *pta3, *pta4;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Make a small test image, the hard way! */
+ pi = 3.1415926535;
+ pixc = pixCreate(9, 9, 32);
+ pixm = pixCreate(9, 9, 1);
+ pta1 = generatePtaLineFromPt(4, 4, 3.1, 0.0);
+ pta2 = generatePtaLineFromPt(4, 4, 3.1, 0.5 * pi);
+ pta3 = generatePtaLineFromPt(4, 4, 3.1, pi);
+ pta4 = generatePtaLineFromPt(4, 4, 3.1, 1.5 * pi);
+ 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);
+ pixDestroy(&pixm);
+
+ /* Results differ for scaleSmoothLow() w/ and w/out + 0.5.
+ * Neither is properly symmetric (with symm pattern on odd-sized
+ * pix, because the smoothing is destroying the symmetry. */
+ pixa = pixaCreate(11);
+ pix1 = pixExpandReplicate(pixc, 2);
+ for (i = 0; i < 11; i++) {
+ scale = 0.30 + 0.035 * (l_float32)i;
+ pix2 = pixScaleSmooth(pix1, scale, scale);
+ pix3 = pixExpandReplicate(pix2, 6);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+ SaveAndDisplayPix(rp, &pixa, 100, 100); /* 0 */
+
+ /* Results same for pixScaleAreaMap w/ and w/out + 0.5 */
+ pixa = pixaCreate(11);
+ pix1 = pixExpandReplicate(pixc, 2);
+ for (i = 0; i < 11; i++) {
+ scale = 0.30 + 0.035 * (l_float32)i;
+ pix2 = pixScaleAreaMap(pix1, scale, scale);
+ pix3 = pixExpandReplicate(pix2, 6);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+ SaveAndDisplayPix(rp, &pixa, 100, 200); /* 1 */
+
+ /* Results better for pixScaleBySampling with + 0.5, for small,
+ * odd-dimension pix. */
+ pixa = pixaCreate(11);
+ pix1 = pixExpandReplicate(pixc, 2);
+ for (i = 0; i < 11; i++) {
+ scale = 0.30 + 0.035 * (l_float32)i;
+ pix2 = pixScaleBySampling(pix1, scale, scale);
+ pix3 = pixExpandReplicate(pix2, 6);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+ SaveAndDisplayPix(rp, &pixa, 100, 300); /* 2 */
+
+ /* Results same for pixRotateAM w/ and w/out + 0.5 */
+ pixa = pixaCreate(11);
+ pix1 = pixExpandReplicate(pixc, 1);
+ for (i = 0; i < 11; i++) {
+ angle = 0.10 + 0.05 * (l_float32)i;
+ pix2 = pixRotateAM(pix1, angle, L_BRING_IN_BLACK);
+ pix3 = pixExpandReplicate(pix2, 8);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+ SaveAndDisplayPix(rp, &pixa, 100, 400); /* 3 */
+
+ /* If the size is odd, we express the center exactly, and the
+ * results are better for pixRotateBySampling() w/out 0.5
+ * However, if the size is even, the center value is not
+ * exact, and if we choose it 0.5 smaller than the actual
+ * center, we get symmetrical results with +0.5.
+ * So we choose not to include + 0.5. */
+ pixa = pixaCreate(11);
+ pix1 = pixExpandReplicate(pixc, 1);
+ for (i = 0; i < 11; i++) {
+ angle = 0.10 + 0.05 * (l_float32)i;
+ pix2 = pixRotateBySampling(pix1, 4, 4, angle, L_BRING_IN_BLACK);
+ pix3 = pixExpandReplicate(pix2, 8);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+ SaveAndDisplayPix(rp, &pixa, 100, 500); /* 4 */
+
+ /* Results same for pixRotateAMCorner w/ and w/out + 0.5 */
+ pixa = pixaCreate(11);
+ pix1 = pixExpandReplicate(pixc, 1);
+ for (i = 0; i < 11; i++) {
+ angle = 0.10 + 0.05 * (l_float32)i;
+ pix2 = pixRotateAMCorner(pix1, angle, L_BRING_IN_BLACK);
+ pix3 = pixExpandReplicate(pix2, 8);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+ SaveAndDisplayPix(rp, &pixa, 100, 600); /* 5 */
+
+ /* Results better for pixRotateAMColorFast without + 0.5 */
+ pixa = pixaCreate(11);
+ pix1 = pixExpandReplicate(pixc, 1);
+ for (i = 0; i < 11; i++) {
+ angle = 0.10 + 0.05 * (l_float32)i;
+ pix2 = pixRotateAMColorFast(pix1, angle, 0);
+ pix3 = pixExpandReplicate(pix2, 8);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+ SaveAndDisplayPix(rp, &pixa, 100, 700); /* 6 */
+
+ /* Results slightly better for pixScaleColorLI() w/out + 0.5 */
+ pixa = pixaCreate(11);
+ pix1 = pixExpandReplicate(pixc, 1);
+ for (i = 0; i < 11; i++) {
+ scale = 1.0 + 0.2 * (l_float32)i;
+ pix2 = pixScaleColorLI(pix1, scale, scale);
+ pix3 = pixExpandReplicate(pix2, 4);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+ SaveAndDisplayPix(rp, &pixa, 100, 800); /* 7 */
+
+ /* Results slightly better for pixScaleColorLI() w/out + 0.5 */
+ pixa = pixaCreate(11);
+ pix1 = pixExpandReplicate(pixc, 1);
+ for (i = 0; i < 11; i++) {
+ scale = 1.0 + 0.2 * (l_float32)i;
+ pix2 = pixScaleLI(pix1, scale, scale);
+ pix3 = pixExpandReplicate(pix2, 4);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+ SaveAndDisplayPix(rp, &pixa, 100, 940); /* 8 */
+
+ pixDestroy(&pixc);
+ return regTestCleanup(rp);
+}
+
+void
+SaveAndDisplayPix(L_REGPARAMS *rp,
+ PIXA **ppixa,
+ l_int32 x,
+ l_int32 y)
+{
+PIX *pix1;
+
+ pix1 = pixaDisplayTiledInColumns(*ppixa, 12, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG);
+ pixDisplayWithTitle(pix1, x, y, NULL, rp->display);
+ pixaDestroy(ppixa);
+ pixDestroy(&pix1);
+}
diff --git a/leptonica/prog/smoothedge_reg.c b/leptonica/prog/smoothedge_reg.c
new file mode 100644
index 00000000..61b875ee
--- /dev/null
+++ b/leptonica/prog/smoothedge_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * smoothedge_reg.c
+ *
+ * Analyzes edges of a 1 bpp (connected component) image for smoothness.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static l_int32 MIN_JUMP = 2;
+static l_int32 MIN_REVERSAL = 3;
+
+void PixAddEdgeData(PIXA *pixa, PIX *pixs, l_int32 side, l_int32 minjump,
+ l_int32 minreversal);
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 w;
+PIX *pixs, *pixt, *pixd;
+PIXA *pixa;
+
+ pixs = pixRead("raggededge.png");
+ w = pixGetWidth(pixs);
+ pixa = pixaCreate(0);
+ PixAddEdgeData(pixa, pixs, L_FROM_RIGHT, MIN_JUMP, MIN_REVERSAL);
+ PixAddEdgeData(pixa, pixs, L_FROM_LEFT, MIN_JUMP, MIN_REVERSAL);
+ pixt = pixRotateOrth(pixs, 1);
+ PixAddEdgeData(pixa, pixt, L_FROM_BOT, MIN_JUMP, MIN_REVERSAL);
+ PixAddEdgeData(pixa, pixt, L_FROM_TOP, MIN_JUMP, MIN_REVERSAL);
+ pixDestroy(&pixt);
+ pixt = pixRotateOrth(pixs, 2);
+ PixAddEdgeData(pixa, pixt, L_FROM_LEFT, MIN_JUMP, MIN_REVERSAL);
+ PixAddEdgeData(pixa, pixt, L_FROM_RIGHT, MIN_JUMP, MIN_REVERSAL);
+ pixDestroy(&pixt);
+ pixt = pixRotateOrth(pixs, 3);
+ PixAddEdgeData(pixa, pixt, L_FROM_TOP, MIN_JUMP, MIN_REVERSAL);
+ PixAddEdgeData(pixa, pixt, L_FROM_BOT, MIN_JUMP, MIN_REVERSAL);
+ pixDestroy(&pixt);
+ pixDestroy(&pixs);
+
+ /* Display at 2x scaling */
+ pixd = pixaDisplayTiledAndScaled(pixa, 32, 2 * (w + 10), 2, 0, 25, 2);
+ pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ return 0;
+}
+
+
+void PixAddEdgeData(PIXA *pixa,
+ PIX *pixs,
+ l_int32 side,
+ l_int32 minjump,
+ l_int32 minreversal)
+{
+l_float32 jpl, jspl, rpl;
+PIX *pixt1, *pixt2;
+
+ pixMeasureEdgeSmoothness(pixs, side, minjump, minreversal, &jpl,
+ &jspl, &rpl, "/tmp/junkedge.png");
+ lept_stderr("side = %d: jpl = %6.3f, jspl = %6.3f, rpl = %6.3f\n",
+ side, jpl, jspl, rpl);
+ pixt1 = pixRead("/tmp/junkedge.png");
+ pixt2 = pixAddBorder(pixt1, 10, 0); /* 10 pixel white border */
+ pixaAddPix(pixa, pixt2, L_INSERT);
+ pixDestroy(&pixt1);
+ return;
+}
+
diff --git a/leptonica/prog/sorttest.c b/leptonica/prog/sorttest.c
new file mode 100644
index 00000000..64e67a9c
--- /dev/null
+++ b/leptonica/prog/sorttest.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * sorttest.c
+ *
+ * Tests sorting of connected components by various attributes,
+ * in increasing or decreasing order.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein;
+l_int32 i, n, ns;
+BOXA *boxa;
+PIX *pixs, *pixt;
+PIXA *pixa, *pixas, *pixas2;
+static char mainName[] = "sorttest";
+
+ if (argc != 2)
+ return ERROR_INT(" Syntax: sorttest filein", mainName, 1);
+
+ filein = argv[1];
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+#if 0
+ boxa = pixConnComp(pixs, NULL, 8);
+ n = boxaGetCount(boxa);
+
+ boxas = boxaSort(boxa, L_SORT_BY_PERIMETER, L_SORT_DECREASING, NULL);
+ ns = boxaGetCount(boxas);
+ lept_stderr("Number of cc: n = %d, ns = %d\n", n, ns);
+ boxaWrite("/tmp/junkboxa.ba", boxas);
+
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxas, i, L_CLONE);
+ pixRenderBox(pixs, box, 2, L_FLIP_PIXELS);
+ boxDestroy(&box);
+ }
+ pixWrite("/tmp/junkout.png", pixs, IFF_PNG);
+ boxaDestroy(&boxa);
+ boxaDestroy(&boxas);
+#endif
+
+
+#if 1
+ boxa = pixConnComp(pixs, &pixa, 8);
+ n = pixaGetCount(pixa);
+
+ pixas = pixaSort(pixa, L_SORT_BY_Y, L_SORT_INCREASING, NULL, L_CLONE);
+ ns = pixaGetCount(pixas);
+ lept_stderr("Number of cc: n = %d, ns = %d\n", n, ns);
+ pixaWrite("/tmp/pixa.pa", pixas);
+ pixas2 = pixaRead("/tmp/pixa.pa");
+ pixaWrite("/tmp/pixa2.pa", pixas2);
+
+ pixt = pixaDisplayOnLattice(pixas, 100, 100, NULL, NULL);
+ pixWrite("/tmp/sorted.png", pixt, IFF_PNG);
+ boxaWrite("/tmp/boxa.ba", pixas->boxa);
+ pixDestroy(&pixt);
+ pixaDestroy(&pixa);
+ pixaDestroy(&pixas);
+ pixaDestroy(&pixas2);
+ boxaDestroy(&boxa);
+#endif
+
+ pixDestroy(&pixs);
+ return 0;
+}
diff --git a/leptonica/prog/speckle.png b/leptonica/prog/speckle.png
new file mode 100644
index 00000000..fe390fac
--- /dev/null
+++ b/leptonica/prog/speckle.png
Binary files differ
diff --git a/leptonica/prog/speckle2.png b/leptonica/prog/speckle2.png
new file mode 100644
index 00000000..f019cebf
--- /dev/null
+++ b/leptonica/prog/speckle2.png
Binary files differ
diff --git a/leptonica/prog/speckle4.png b/leptonica/prog/speckle4.png
new file mode 100644
index 00000000..5f12c42b
--- /dev/null
+++ b/leptonica/prog/speckle4.png
Binary files differ
diff --git a/leptonica/prog/speckle_reg.c b/leptonica/prog/speckle_reg.c
new file mode 100644
index 00000000..00e58667
--- /dev/null
+++ b/leptonica/prog/speckle_reg.c
@@ -0,0 +1,119 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * speckle_reg.c
+ *
+ * Image normalization to get an image with speckle background
+ * noise, followed by attempts to remove some of the speckle.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* HMT (with just misses) for speckle up to 2x2 */
+static const char *selstr2 = "oooo"
+ "oC o"
+ "o o"
+ "oooo";
+ /* HMT (with just misses) for speckle up to 3x3 */
+static const char *selstr3 = "ooooo"
+ "oC o"
+ "o o"
+ "o o"
+ "ooooo";
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5;
+PIX *pix6, *pix7, *pix8, *pix9, *pix10;
+PIXA *pixa1;
+SEL *sel1, *sel2, *sel3, *sel4;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Normalize for rapidly varying background */
+ pixa1 = pixaCreate(0);
+ pixs = pixRead("w91frag.jpg");
+ pixaAddPix(pixa1, pixs, L_INSERT);
+ regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 0 */
+ pix1 = pixBackgroundNormFlex(pixs, 7, 7, 1, 1, 10);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 1 */
+
+ /* Remove the background */
+ pix2 = pixGammaTRCMasked(NULL, pix1, NULL, 1.0, 100, 175);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 2 */
+
+ /* Binarize */
+ pix3 = pixThresholdToBinary(pix2, 180);
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3 */
+
+ /* Remove the speckle noise up to 2x2 */
+ sel1 = selCreateFromString(selstr2, 4, 4, "speckle2");
+ pix4 = pixHMT(NULL, pix3, sel1);
+ pixaAddPix(pixa1, pix4, L_INSERT);
+ sel2 = selCreateBrick(2, 2, 0, 0, SEL_HIT);
+ pix5 = pixDilate(NULL, pix4, sel2);
+ pixaAddPix(pixa1, pix5, L_INSERT);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 4 */
+ pix6 = pixSubtract(NULL, pix3, pix5);
+ pixaAddPix(pixa1, pix6, L_INSERT);
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 5 */
+
+ /* Remove the speckle noise up to 3x3 */
+ sel3 = selCreateFromString(selstr3, 5, 5, "speckle3");
+ pix7 = pixHMT(NULL, pix3, sel3);
+ pixaAddPix(pixa1, pix7, L_INSERT);
+ sel4 = selCreateBrick(3, 3, 0, 0, SEL_HIT);
+ pix8 = pixDilate(NULL, pix7, sel4);
+ pixaAddPix(pixa1, pix8, L_INSERT);
+ regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 6 */
+ pix9 = pixSubtract(NULL, pix3, pix8);
+ pixaAddPix(pixa1, pix9, L_INSERT);
+ regTestWritePixAndCheck(rp, pix9, IFF_PNG); /* 7 */
+
+ pix10 = pixaDisplayTiledInColumns(pixa1, 3, 1.0, 30, 2);
+ pixDisplayWithTitle(pix10, 0, 0, NULL, rp->display);
+ regTestWritePixAndCheck(rp, pix10, IFF_JFIF_JPEG); /* 8 */
+ selDestroy(&sel1);
+ selDestroy(&sel2);
+ selDestroy(&sel3);
+ selDestroy(&sel4);
+ pixDestroy(&pix2);
+ pixDestroy(&pix10);
+ pixaDestroy(&pixa1);
+ return regTestCleanup(rp);
+}
+
+
diff --git a/leptonica/prog/splitcomp_reg.c b/leptonica/prog/splitcomp_reg.c
new file mode 100644
index 00000000..faa49415
--- /dev/null
+++ b/leptonica/prog/splitcomp_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * splitcomp_reg.c
+ *
+ * Regression test for splittings of a single component and for an image
+ * composed of several components, using different components and
+ * parameters. Note that:
+ * (1) All coverings must cover the fg of the mask.
+ * (2) The first set of parameters is small and generates
+ * a proper tiling, covering ONLY the mask fg.
+ * (3) The tilings generated on 90 degree rotated components
+ * are identical (rotated) to those on un-rotated components.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j, w, h;
+l_int32 minsum[5] = { 2, 40, 50, 50, 70};
+l_int32 skipdist[5] = { 5, 5, 10, 10, 30};
+l_int32 delta[5] = { 2, 10, 10, 25, 40};
+l_int32 maxbg[5] = {10, 15, 10, 20, 40};
+BOX *box1, *box2, *box3, *box4;
+BOXA *boxa;
+PIX *pixs, *pixc, *pixt, *pixd, *pix32;
+PIXA *pixas, *pixad;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Generate and save 1 bpp masks */
+ pixas = pixaCreate(0);
+ pixs = pixCreate(300, 250, 1);
+ pixSetAll(pixs);
+ box1 = boxCreate(50, 0, 140, 25);
+ box2 = boxCreate(120, 100, 100, 25);
+ box3 = boxCreate(75, 170, 80, 20);
+ box4 = boxCreate(150, 80, 25, 70);
+
+ pixClearInRect(pixs, box1);
+ pixaAddPix(pixas, pixs, L_COPY);
+ pixt = pixRotateOrth(pixs, 1);
+ pixaAddPix(pixas, pixt, L_INSERT);
+
+ pixClearInRect(pixs, box2);
+ pixaAddPix(pixas, pixs, L_COPY);
+ pixt = pixRotateOrth(pixs, 1);
+ pixaAddPix(pixas, pixt, L_INSERT);
+
+ pixClearInRect(pixs, box3);
+ pixaAddPix(pixas, pixs, L_COPY);
+ pixt = pixRotateOrth(pixs, 1);
+ pixaAddPix(pixas, pixt, L_INSERT);
+
+ pixClearInRect(pixs, box4);
+ pixaAddPix(pixas, pixs, L_COPY);
+ pixt = pixRotateOrth(pixs, 1);
+ pixaAddPix(pixas, pixt, L_INSERT);
+
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ boxDestroy(&box3);
+ boxDestroy(&box4);
+ pixDestroy(&pixs);
+
+ /* Do 5 splittings on each of the 8 masks */
+ pixad = pixaCreate(0);
+ for (j = 0; j < 8; j++) {
+ pixt = pixaGetPix(pixas, j, L_CLONE);
+ pixGetDimensions(pixt, &w, &h, NULL);
+ pix32 = pixCreate(w, h, 32);
+ pixSetAll(pix32);
+ pixPaintThroughMask(pix32, pixt, 0, 0, 0xc0c0c000);
+ pixaAddPix(pixad, pix32, L_INSERT);
+ for (i = 0; i < 5; i++) {
+ pixc = pixCopy(NULL, pix32);
+ boxa = pixSplitComponentIntoBoxa(pixt, NULL, minsum[i], skipdist[i],
+ delta[i], maxbg[i], 0, 1);
+/* boxaWriteStream(stderr, boxa); */
+ pixd = pixBlendBoxaRandom(pixc, boxa, 0.4);
+ pixRenderBoxaArb(pixd, boxa, 2, 255, 0, 0);
+ pixaAddPix(pixad, pixd, L_INSERT);
+ pixDestroy(&pixc);
+ boxaDestroy(&boxa);
+ }
+ pixDestroy(&pixt);
+ }
+
+ /* Display results */
+ pixd = pixaDisplayTiledInColumns(pixad, 6, 1.0, 30, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixad);
+
+ /* Put the 8 masks all together, and split 5 ways */
+ pixad = pixaCreate(0);
+ pixs = pixaDisplayOnLattice(pixas, 325, 325, NULL, NULL);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pix32 = pixCreate(w, h, 32);
+ pixSetAll(pix32);
+ pixPaintThroughMask(pix32, pixs, 0, 0, 0xc0c0c000);
+ pixaAddPix(pixad, pix32, L_INSERT);
+ for (i = 0; i < 5; i++) {
+ pixc = pixCopy(NULL, pix32);
+ boxa = pixSplitIntoBoxa(pixs, minsum[i], skipdist[i],
+ delta[i], maxbg[i], 0, 1);
+/* boxaWriteStream(stderr, boxa); */
+ pixd = pixBlendBoxaRandom(pixc, boxa, 0.4);
+ pixRenderBoxaArb(pixd, boxa, 2, 255, 0, 0);
+ pixaAddPix(pixad, pixd, L_INSERT);
+ pixDestroy(&pixc);
+ boxaDestroy(&boxa);
+ }
+ pixDestroy(&pixs);
+
+ /* Display results */
+ pixd = pixaDisplayTiledInColumns(pixad, 6, 1.0, 30, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pixd, 600, 100, NULL, rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixad);
+
+ pixaDestroy(&pixas);
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/splitimage2pdf.c b/leptonica/prog/splitimage2pdf.c
new file mode 100644
index 00000000..7e4dc367
--- /dev/null
+++ b/leptonica/prog/splitimage2pdf.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * splitimage2pdf.c
+ *
+ * Syntax: splitimage2pdf filein nx ny fileout
+ *
+ * nx = number of horizontal tiles
+ * ny = number of vertical tiles
+ *
+ * Simple program to generate a pdf of image tiles.
+ * To print the tiles, one page per tile, use printsplitimage.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *fileout;
+l_int32 nx, ny;
+PIX *pixs;
+PIXA *pixa;
+static char mainName[] = "splitimage2pdf";
+
+ if (argc != 5)
+ return ERROR_INT(" Syntax: splitimage2pdf filein nx ny fileout",
+ mainName, 1);
+ filein = argv[1];
+ nx = atoi(argv[2]);
+ ny = atoi(argv[3]);
+ fileout = argv[4];
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+
+ pixa = pixaSplitPix(pixs, nx, ny, 0, 0);
+ pixaConvertToPdf(pixa, 300, 1.0, 0, 0, NULL, fileout);
+
+ pixDestroy(&pixs);
+ pixaDestroy(&pixa);
+ return 0;
+}
+
diff --git a/leptonica/prog/stampede2.jpg b/leptonica/prog/stampede2.jpg
new file mode 100644
index 00000000..62539612
--- /dev/null
+++ b/leptonica/prog/stampede2.jpg
Binary files differ
diff --git a/leptonica/prog/string_reg.c b/leptonica/prog/string_reg.c
new file mode 100644
index 00000000..acfbd03e
--- /dev/null
+++ b/leptonica/prog/string_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * string_reg.c
+ *
+ * This tests:
+ * * search/replace for strings and arrays
+ * * sarray generation and flattening
+ * * sarray serialization
+ * * file splitting
+ * * sarray splitting
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+char strs[32] = "This is a gooood test!";
+char substr1[2] = "o";
+char substr2[4] = "00";
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, loc, count;
+size_t size1, size2;
+char *str0, *str1, *str2, *str3, *str4, *str5, *str6;
+char fname[128];
+l_uint8 *data1, *data2;
+L_DNA *da;
+SARRAY *sa1, *sa2, *sa3, *sa4, *sa5;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ lept_mkdir("lept/string");
+
+ /* Finding all substrings */
+ da = stringFindEachSubstr(strs, substr1);
+ regTestCompareValues(rp, 4, l_dnaGetCount(da), 0.0); /* 0 */
+ l_dnaDestroy(&da);
+
+ /* Replacing a substring */
+ loc = 0;
+ str1 = stringReplaceSubstr(strs, "his", "hers", &loc, NULL);
+ regTestCompareValues(rp, 5, loc, 0.0); /* 1 */
+ regTestCompareStrings(rp, (l_uint8 *)"Thers is a gooood test!", 23,
+ (l_uint8 *)str1, strlen(str1)); /* 2 */
+ lept_free(str1);
+
+ /* Replacing all substrings */
+ str1 = stringReplaceEachSubstr(strs, substr1, substr2, &count);
+ regTestCompareValues(rp, 4, count, 0.0); /* 3 */
+ regTestCompareStrings(rp, (l_uint8 *)"This is a g00000000d test!", 26,
+ (l_uint8 *)str1, strlen(str1)); /* 4 */
+ lept_free(str1);
+
+ str1 = stringReplaceEachSubstr(strs, substr1, "", &count);
+ regTestCompareValues(rp, 4, count, 0.0); /* 5 */
+ regTestCompareStrings(rp, (l_uint8 *)"This is a gd test!", 18,
+ (l_uint8 *)str1, strlen(str1)); /* 6 */
+ lept_free(str1);
+
+ /* Finding all sequences */
+ str1 = (char *)l_binaryRead("kernel_reg.c", &size1);
+ da = arrayFindEachSequence((l_uint8 *)str1, size1,
+ (l_uint8 *)"Destroy", 7);
+ regTestCompareValues(rp, 35, l_dnaGetCount(da), 0.0); /* 7 */
+ l_dnaDestroy(&da);
+ lept_free(str1);
+
+ /* Replacing all sequences */
+ str1 = (char *)l_binaryRead("kernel_reg.c", &size1);
+ data1 = arrayReplaceEachSequence((l_uint8 *)str1, size1,
+ (l_uint8 *)"Destroy", 7,
+ (l_uint8 *)"####", 4, &size2, &count);
+ l_binaryWrite("/tmp/lept/string/string1.txt", "w", data1, size2);
+ regTestCheckFile(rp, "/tmp/lept/string/string1.txt"); /* 8 */
+ regTestCompareValues(rp, 35, count, 0.0); /* 9 */
+ data2 = arrayReplaceEachSequence((l_uint8 *)str1, size1,
+ (l_uint8 *)"Destroy", 7,
+ NULL, 0, &size2, &count);
+ l_binaryWrite("/tmp/lept/string/string2.txt", "w", data2, size2);
+ regTestCheckFile(rp, "/tmp/lept/string/string2.txt"); /* 10 */
+ regTestCompareValues(rp, 35, count, 0.0); /* 11 */
+ lept_free(data1);
+ lept_free(data2);
+ lept_free(str1);
+
+ /* Generating sarray from strings, and v.v */
+ str0 = (char *)l_binaryRead("kernel_reg.c", &size1);
+ str0[2500] = '\0';
+ sa1 = sarrayCreateWordsFromString(str0 + 2000);
+ sa2 = sarrayCreateLinesFromString(str0 + 2000, 0);
+ sa3 = sarrayCreateLinesFromString(str0 + 2000, 1);
+ str1 = sarrayToString(sa1, 0);
+ str2 = sarrayToString(sa1, 1);
+ str3 = sarrayToString(sa2, 0);
+ str4 = sarrayToString(sa2, 1);
+ str5 = sarrayToString(sa3, 0);
+ str6 = sarrayToString(sa3, 1);
+ l_binaryWrite("/tmp/lept/string/test1.txt", "w", str1, strlen(str1));
+ l_binaryWrite("/tmp/lept/string/test2.txt", "w", str2, strlen(str2));
+ l_binaryWrite("/tmp/lept/string/test3.txt", "w", str3, strlen(str3));
+ l_binaryWrite("/tmp/lept/string/test4.txt", "w", str4, strlen(str4));
+ l_binaryWrite("/tmp/lept/string/test5.txt", "w", str5, strlen(str5));
+ l_binaryWrite("/tmp/lept/string/test6.txt", "w", str6, strlen(str6));
+ regTestCheckFile(rp, "/tmp/lept/string/test1.txt"); /* 12 */
+ regTestCheckFile(rp, "/tmp/lept/string/test2.txt"); /* 13 */
+ regTestCheckFile(rp, "/tmp/lept/string/test3.txt"); /* 14 */
+ regTestCheckFile(rp, "/tmp/lept/string/test4.txt"); /* 15 */
+ regTestCheckFile(rp, "/tmp/lept/string/test5.txt"); /* 16 */
+ regTestCheckFile(rp, "/tmp/lept/string/test6.txt"); /* 17 */
+ regTestCompareFiles(rp, 14, 16); /* 18 */
+ lept_free(str0);
+ lept_free(str1);
+ lept_free(str2);
+ lept_free(str3);
+ lept_free(str4);
+ lept_free(str5);
+ lept_free(str6);
+ sarrayDestroy(&sa1);
+ sarrayDestroy(&sa2);
+ sarrayDestroy(&sa3);
+
+ /* Test sarray serialization */
+ str1 = (char *)l_binaryRead("kernel_reg.c", &size1);
+ sa1 = sarrayCreateLinesFromString(str1, 0);
+ sarrayWrite("/tmp/lept/string/test7.txt", sa1);
+ sa2 = sarrayRead("/tmp/lept/string/test7.txt");
+ sarrayWrite("/tmp/lept/string/test8.txt", sa2);
+ regTestCheckFile(rp, "/tmp/lept/string/test7.txt"); /* 19 */
+ regTestCheckFile(rp, "/tmp/lept/string/test8.txt"); /* 20 */
+ regTestCompareFiles(rp, 19, 20); /* 21 */
+ lept_free(str1);
+ sarrayDestroy(&sa1);
+ sarrayDestroy(&sa2);
+
+ /* Test byte replacement in a file:
+ * - replace 200 bytes by 10 bytes
+ * - remove the 10 bytes
+ * - recover the 200 bytes and insert back */
+ fileReplaceBytes("kernel_reg.c", 100, 200, (l_uint8 *)"abcdefghij",
+ sizeof("abcdefghij"), "/tmp/lept/string/junk1.txt");
+ str1 = (char *)l_binaryRead("kernel_reg.c", &size1);
+ fileReplaceBytes("/tmp/lept/string/junk1.txt", 100, sizeof("abcdefghij"),
+ NULL, 0, "/tmp/lept/string/junk2.txt");
+ str2 = stringCopySegment(str1, 100, 200);
+ fileReplaceBytes("/tmp/lept/string/junk2.txt", 100, 0, (l_uint8 *)str2,
+ strlen(str2), "/tmp/lept/string/junk3.txt");
+ str3 = (char *)l_binaryRead("/tmp/lept/string/junk3.txt", &size2);
+ regTestCompareStrings(rp, (l_uint8 *)str1, size1, (l_uint8 *)str3, size2);
+ /* 22 */
+ lept_free(str1);
+ lept_free(str2);
+ lept_free(str3);
+
+ /* File splitting by lines */
+ str1 = (char *)l_binaryRead("kernel_reg.c", &size1);
+ fileSplitLinesUniform("kernel_reg.c", 3, 1, "/tmp/lept/string/split",
+ ".txt");
+ str2 = NULL;
+ for (i = 0; i < 3; i++) { /* put the pieces back together */
+ snprintf(fname, sizeof(fname), "/tmp/lept/string/split_%d.txt", i);
+ str3 = (char *)l_binaryRead(fname, &size2);
+ stringJoinIP(&str2, str3);
+ lept_free(str3);
+ }
+ regTestCompareStrings(rp, (l_uint8 *)str1, size1,
+ (l_uint8 *)str2, strlen(str2)); /* 23 */
+ lept_free(str1);
+ lept_free(str2);
+
+ /* Sarray splitting by lines */
+ str1 = (char *)l_binaryRead("kernel_reg.c", &size1);
+ sa1 = sarrayCreateLinesFromString(str1, 0);
+ sa2 = sarrayConcatUniformly(sa1, 6, 0); /* into 6 strings */
+ sa3 = sarrayCreate(0);
+ for (i = 0; i < 6; i++) {
+ str2 = sarrayGetString(sa2, i, L_NOCOPY);
+ sa4 = sarrayCreateLinesFromString(str2, 0);
+ sarrayJoin(sa3, sa4);
+ sarrayDestroy(&sa4);
+ }
+ sa5 = sarrayConcatUniformly(sa3, 6, 0); /* same as sa2 ? */
+ sarrayWriteMem((l_uint8 **)&str3, &size1, sa2);
+ sarrayWriteMem((l_uint8 **)&str4, &size2, sa5);
+ regTestWriteDataAndCheck(rp, str3, size1, ".sa"); /* 24 */
+ regTestWriteDataAndCheck(rp, str4, size2, ".sa"); /* 25 */
+ regTestCompareFiles(rp, 24, 25); /* 26 */
+ sarrayDestroy(&sa1);
+ sarrayDestroy(&sa2);
+ sarrayDestroy(&sa3);
+ sarrayDestroy(&sa4);
+ sarrayDestroy(&sa5);
+ lept_free(str1);
+ lept_free(str3);
+ lept_free(str4);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/stringtemplate1.txt b/leptonica/prog/stringtemplate1.txt
new file mode 100644
index 00000000..aea771f9
--- /dev/null
+++ b/leptonica/prog/stringtemplate1.txt
@@ -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 */
+/*---------------------------------------------------------------------*/
+/*!
+--- * \brief l_autodecode_*()
+ *
+ * \param[in] 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 the selected string, uncompress it, 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/leptonica/prog/stringtemplate2.txt b/leptonica/prog/stringtemplate2.txt
new file mode 100644
index 00000000..20c853ad
--- /dev/null
+++ b/leptonica/prog/stringtemplate2.txt
@@ -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/leptonica/prog/subpixel_reg.c b/leptonica/prog/subpixel_reg.c
new file mode 100644
index 00000000..49acf80a
--- /dev/null
+++ b/leptonica/prog/subpixel_reg.c
@@ -0,0 +1,209 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * subpixel_reg.c
+ *
+ * Regression test for subpixel scaling.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void AddTextAndSave(PIXA *pixa, PIX *pixs,
+ L_BMF *bmf, const char *textstr,
+ l_int32 location, l_uint32 val);
+
+const char *textstr[] =
+ {"Downscaled with sharpening",
+ "Subpixel scaling; horiz R-G-B",
+ "Subpixel scaling; horiz B-G-R",
+ "Subpixel scaling; vert R-G-B",
+ "Subpixel scaling; vert B-G-R"};
+
+int main(int argc,
+ char **argv)
+{
+l_float32 scalefact;
+L_BMF *bmf, *bmftop;
+L_KERNEL *kel, *kelx, *kely;
+PIX *pixs, *pixg, *pixt, *pixd;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "subpixel_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* ----------------- Test on 8 bpp grayscale ---------------------*/
+ pixa = pixaCreate(5);
+ bmf = bmfCreate("./fonts", 6);
+ bmftop = bmfCreate("./fonts", 10);
+ pixs = pixRead("lucasta.047.jpg");
+ pixg = pixScale(pixs, 0.4, 0.4); /* 8 bpp grayscale */
+ pix1 = pixConvertTo32(pixg); /* 32 bpp rgb */
+ AddTextAndSave(pixa, pix1, bmf, textstr[0], L_ADD_BELOW, 0xff000000);
+ pix2 = pixConvertGrayToSubpixelRGB(pixs, 0.4, 0.4, L_SUBPIXEL_ORDER_RGB);
+ AddTextAndSave(pixa, pix2, bmf, textstr[1], L_ADD_BELOW, 0x00ff0000);
+ pix3 = pixConvertGrayToSubpixelRGB(pixs, 0.4, 0.4, L_SUBPIXEL_ORDER_BGR);
+ AddTextAndSave(pixa, pix3, bmf, textstr[2], L_ADD_BELOW, 0x0000ff00);
+ pix4 = pixConvertGrayToSubpixelRGB(pixs, 0.4, 0.4, L_SUBPIXEL_ORDER_VRGB);
+ AddTextAndSave(pixa, pix4, bmf, textstr[3], L_ADD_BELOW, 0x00ff0000);
+ pix5 = pixConvertGrayToSubpixelRGB(pixs, 0.4, 0.4, L_SUBPIXEL_ORDER_VBGR);
+ AddTextAndSave(pixa, pix5, bmf, textstr[4], L_ADD_BELOW, 0x0000ff00);
+
+ pixt = pixaDisplayTiledInColumns(pixa, 5, 1.0, 30, 2);
+ pixd = pixAddSingleTextblock(pixt, bmftop,
+ "Regression test for subpixel scaling: gray",
+ 0xff00ff00, L_ADD_ABOVE, NULL);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 0 */
+ pixDisplayWithTitle(pixd, 50, 50, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs);
+ pixDestroy(&pixg);
+ pixDestroy(&pixt);
+ pixDestroy(&pixd);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+
+
+ /* ----------------- Test on 32 bpp rgb ---------------------*/
+ pixa = pixaCreate(5);
+ pixs = pixRead("fish24.jpg");
+ pix1 = pixScale(pixs, 0.4, 0.4);
+ AddTextAndSave(pixa, pix1, bmf, textstr[0], L_ADD_BELOW, 0xff000000);
+ pix2 = pixConvertToSubpixelRGB(pixs, 0.4, 0.4, L_SUBPIXEL_ORDER_RGB);
+ AddTextAndSave(pixa, pix2, bmf, textstr[1], L_ADD_BELOW, 0x00ff0000);
+ pix3 = pixConvertToSubpixelRGB(pixs, 0.4, 0.35, L_SUBPIXEL_ORDER_BGR);
+ AddTextAndSave(pixa, pix3, bmf, textstr[2], L_ADD_BELOW, 0x0000ff00);
+ pix4 = pixConvertToSubpixelRGB(pixs, 0.4, 0.45, L_SUBPIXEL_ORDER_VRGB);
+ AddTextAndSave(pixa, pix4, bmf, textstr[3], L_ADD_BELOW, 0x00ff0000);
+ pix5 = pixConvertToSubpixelRGB(pixs, 0.4, 0.4, L_SUBPIXEL_ORDER_VBGR);
+ AddTextAndSave(pixa, pix5, bmf, textstr[4], L_ADD_BELOW, 0x0000ff00);
+
+ pixt = pixaDisplayTiledInColumns(pixa, 5, 1.0, 30, 2);
+ pixd = pixAddSingleTextblock(pixt, bmftop,
+ "Regression test for subpixel scaling: color",
+ 0xff00ff00, L_ADD_ABOVE, NULL);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 1 */
+ pixDisplayWithTitle(pixd, 50, 350, NULL, rp->display);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs);
+ pixDestroy(&pixt);
+ pixDestroy(&pixd);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ bmfDestroy(&bmf);
+ bmfDestroy(&bmftop);
+
+
+ /* --------------- Test on images that are initially 1 bpp ------------*/
+ /* For these, it is better to apply a lowpass filter before scaling */
+ /* Normal scaling of 8 bpp grayscale */
+ scalefact = 800. / 2320.;
+ pixs = pixRead("patent.png"); /* sharp, 300 ppi, 1 bpp image */
+ pix1 = pixConvertTo8(pixs, FALSE); /* use 8 bpp input */
+ pix2 = pixScale(pix1, scalefact, scalefact);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 2 */
+
+ /* Subpixel scaling; bad because there is very little aliasing. */
+ pix3 = pixConvertToSubpixelRGB(pix1, scalefact, scalefact,
+ L_SUBPIXEL_ORDER_RGB);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3 */
+
+ /* Get same (bad) result doing subpixel rendering on RGB input */
+ pix4 = pixConvertTo32(pixs);
+ pix5 = pixConvertToSubpixelRGB(pix4, scalefact, scalefact,
+ L_SUBPIXEL_ORDER_RGB);
+ regTestComparePix(rp, pix3, pix5); /* 4 */
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 5 */
+
+ /* Now apply a small lowpass filter before scaling. */
+ makeGaussianKernelSep(2, 2, 1.0, 1.0, &kelx, &kely);
+ startTimer();
+ pix6 = pixConvolveSep(pix1, kelx, kely, 8, 1); /* normalized */
+ lept_stderr("Time sep: %7.3f\n", stopTimer());
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 6 */
+
+ /* Get same lowpass result with non-separated convolution */
+ kel = makeGaussianKernel(2, 2, 1.0, 1.0);
+ startTimer();
+ pix7 = pixConvolve(pix1, kel, 8, 1); /* normalized */
+ lept_stderr("Time non-sep: %7.3f\n", stopTimer());
+ regTestComparePix(rp, pix6, pix7); /* 7 */
+
+ /* Now do the subpixel scaling on this slightly blurred image */
+ pix8 = pixConvertToSubpixelRGB(pix6, scalefact, scalefact,
+ L_SUBPIXEL_ORDER_RGB);
+ regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 8 */
+
+ kernelDestroy(&kelx);
+ kernelDestroy(&kely);
+ kernelDestroy(&kel);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ return regTestCleanup(rp);
+}
+
+
+void
+AddTextAndSave(PIXA *pixa,
+ PIX *pixs,
+ L_BMF *bmf,
+ const char *textstr,
+ l_int32 location,
+ l_uint32 val)
+{
+l_int32 n, ovf;
+PIX *pixt;
+
+ pixt = pixAddSingleTextblock(pixs, bmf, textstr, val, location, &ovf);
+ n = pixaGetCount(pixa);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ if (ovf) lept_stderr("Overflow writing text in image %d\n", n + 1);
+ return;
+}
diff --git a/leptonica/prog/sudoku1.dat b/leptonica/prog/sudoku1.dat
new file mode 100644
index 00000000..b9e0a62a
--- /dev/null
+++ b/leptonica/prog/sudoku1.dat
@@ -0,0 +1,12 @@
+# sudoku1.dat
+# 5 star puzzle
+# Requires: 1.0M guesses, 0.1 sec
+3 8 0 2 0 0 0 9 0
+2 0 0 0 9 1 0 0 7
+0 4 0 0 3 0 0 0 0
+0 2 0 0 0 0 0 0 0
+0 1 6 0 0 0 2 5 0
+0 0 0 0 0 0 0 1 0
+0 0 0 0 8 0 0 6 0
+4 0 0 6 7 0 0 0 8
+0 5 0 0 0 3 0 7 1
diff --git a/leptonica/prog/sudoku2.dat b/leptonica/prog/sudoku2.dat
new file mode 100644
index 00000000..9c9c6a9d
--- /dev/null
+++ b/leptonica/prog/sudoku2.dat
@@ -0,0 +1,11 @@
+# sudoku2.dat
+# 3 stars
+0 1 0 0 6 0 0 2 4
+7 0 0 0 0 3 0 0 0
+5 6 0 0 9 0 3 0 0
+0 5 4 1 0 0 8 7 0
+0 0 0 0 0 0 0 0 0
+0 8 7 0 0 9 2 4 0
+0 0 5 0 3 0 0 9 8
+0 0 0 8 0 0 0 0 3
+8 3 0 0 7 0 0 6 0
diff --git a/leptonica/prog/sudoku3.dat b/leptonica/prog/sudoku3.dat
new file mode 100644
index 00000000..c4210e96
--- /dev/null
+++ b/leptonica/prog/sudoku3.dat
@@ -0,0 +1,14 @@
+# sudoku3.dat
+# "Near worst case" sudoku for brute force,
+# Req: 623M guesses, 70 sec
+# (Rotating by 180, it only requires: 4.7M guesses, 0.6 sec)
+# from http://en.wikipedia.org/wiki/Algorithmics_of_sudoku
+0 0 0 0 0 0 0 0 0
+0 0 0 0 0 3 0 8 5
+0 0 1 0 2 0 0 0 0
+0 0 0 5 0 7 0 0 0
+0 0 4 0 0 0 1 0 0
+0 9 0 0 0 0 0 0 0
+5 0 0 0 0 0 0 7 3
+0 0 2 0 1 0 0 0 0
+0 0 0 0 4 0 0 0 9
diff --git a/leptonica/prog/sudoku4.dat b/leptonica/prog/sudoku4.dat
new file mode 100644
index 00000000..5893814e
--- /dev/null
+++ b/leptonica/prog/sudoku4.dat
@@ -0,0 +1,13 @@
+# sudoku4.dat
+# Easter Monster (rating 99408 -- whatever that is)
+# Req: 2.4M guesses, about 0.7 sec
+# from http://en.wikipedia.org/wiki/Algorithmics_of_sudoku
+1 0 0 0 0 0 0 0 2
+0 9 0 4 0 0 0 5 0
+0 0 6 0 0 0 7 0 0
+0 5 0 9 0 3 0 0 0
+0 0 0 0 7 0 0 0 0
+0 0 0 8 5 0 0 4 0
+7 0 0 0 0 0 6 0 0
+0 3 0 0 0 9 0 8 0
+0 0 2 0 0 0 0 0 1
diff --git a/leptonica/prog/sudoku5.dat b/leptonica/prog/sudoku5.dat
new file mode 100644
index 00000000..ca5c8ddc
--- /dev/null
+++ b/leptonica/prog/sudoku5.dat
@@ -0,0 +1,14 @@
+# sudoku5.dat
+# tarek071223170000052Easter Monster (rating 99408 -- whatever that is)
+# rating: 4m19s@2 GHz (by tarek)
+# Actual solution requires: Req: 18.1M guesses, about 2.1 sec
+# from http://en.wikipedia.org/wiki/Algorithmics_of_sudoku
+0 0 1 0 0 4 0 0 0
+0 0 0 0 6 0 3 0 5
+0 0 0 9 0 0 0 0 0
+8 0 0 0 0 0 7 0 3
+0 0 0 0 0 0 0 2 8
+5 0 0 0 7 0 6 0 0
+3 0 0 0 8 0 0 0 6
+0 0 9 2 0 0 0 0 0
+0 4 0 0 0 1 0 0 0
diff --git a/leptonica/prog/sudoku6.dat b/leptonica/prog/sudoku6.dat
new file mode 100644
index 00000000..20f11576
--- /dev/null
+++ b/leptonica/prog/sudoku6.dat
@@ -0,0 +1,11 @@
+# sudoku6.dat
+# 6 star sudoku (Jackson Hole daily)
+7 8 0 0 0 6 3 0 0
+0 0 0 9 8 0 5 0 4
+0 0 0 0 3 0 0 8 0
+3 0 8 4 0 0 0 0 0
+0 0 0 5 7 1 0 0 0
+0 0 0 0 0 8 9 0 1
+0 6 0 0 2 0 0 0 0
+9 0 7 0 1 3 0 0 0
+0 0 3 6 0 0 0 2 7
diff --git a/leptonica/prog/sudoku7.dat b/leptonica/prog/sudoku7.dat
new file mode 100644
index 00000000..7a55b454
--- /dev/null
+++ b/leptonica/prog/sudoku7.dat
@@ -0,0 +1,11 @@
+# sudoku7.dat
+# 4 star sudoku (Jackson Hole daily)
+7 8 6 0 0 0 5 0 3
+0 0 0 0 0 8 0 6 2
+0 0 0 0 5 0 0 4 0
+3 4 0 0 8 0 0 0 0
+0 0 5 0 0 0 4 0 0
+0 0 0 0 6 0 0 3 5
+0 3 0 0 1 0 0 0 0
+6 9 0 8 0 0 0 0 0
+8 0 4 0 0 0 1 7 9
diff --git a/leptonica/prog/sudokutest.c b/leptonica/prog/sudokutest.c
new file mode 100644
index 00000000..9949ff41
--- /dev/null
+++ b/leptonica/prog/sudokutest.c
@@ -0,0 +1,93 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * sudokutest.c
+ *
+ * Tests sudoku solver and generator.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const char *startsol = "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";
+
+int main(int argc,
+ char **argv)
+{
+l_int32 unique;
+l_int32 *array;
+L_SUDOKU *sud;
+static char mainName[] = "sudokutest";
+
+ if (argc != 1 && argc != 2)
+ return ERROR_INT(" Syntax: sudokutest [filein]", mainName, 1);
+
+ setLeptDebugOK(1);
+ if (argc == 1) {
+ /* Generate a new sudoku by element elimination */
+ array = sudokuReadString(startsol);
+ sud = sudokuGenerate(array, 3693, 28, 7);
+ sudokuDestroy(&sud);
+ lept_free(array);
+ return 0;
+ }
+
+ /* Solve the input sudoku */
+ if ((array = sudokuReadFile(argv[1])) == NULL)
+ return ERROR_INT("invalid input", mainName, 1);
+ if ((sud = sudokuCreate(array)) == NULL)
+ return ERROR_INT("sud not made", mainName, 1);
+ sudokuOutput(sud, L_SUDOKU_INIT);
+ startTimer();
+ sudokuSolve(sud);
+ lept_stderr("Time: %7.3f sec\n", stopTimer());
+ sudokuOutput(sud, L_SUDOKU_STATE);
+ sudokuDestroy(&sud);
+
+ /* Test for uniqueness */
+ sudokuTestUniqueness(array, &unique);
+ if (unique)
+ lept_stderr("Sudoku is unique\n");
+ else
+ lept_stderr("Sudoku is NOT unique\n");
+ lept_free(array);
+
+ return 0;
+}
+
+
diff --git a/leptonica/prog/table.15.tif b/leptonica/prog/table.15.tif
new file mode 100644
index 00000000..2e1e673c
--- /dev/null
+++ b/leptonica/prog/table.15.tif
Binary files differ
diff --git a/leptonica/prog/table.150.png b/leptonica/prog/table.150.png
new file mode 100644
index 00000000..85b62f67
--- /dev/null
+++ b/leptonica/prog/table.150.png
Binary files differ
diff --git a/leptonica/prog/table.27.tif b/leptonica/prog/table.27.tif
new file mode 100644
index 00000000..f8e0ba70
--- /dev/null
+++ b/leptonica/prog/table.27.tif
Binary files differ
diff --git a/leptonica/prog/test-1bit-alpha.png b/leptonica/prog/test-1bit-alpha.png
new file mode 100644
index 00000000..03daf0b1
--- /dev/null
+++ b/leptonica/prog/test-1bit-alpha.png
Binary files differ
diff --git a/leptonica/prog/test-87220.59.png b/leptonica/prog/test-87220.59.png
new file mode 100644
index 00000000..8c7ceb14
--- /dev/null
+++ b/leptonica/prog/test-87220.59.png
Binary files differ
diff --git a/leptonica/prog/test-cmap-alpha.png b/leptonica/prog/test-cmap-alpha.png
new file mode 100644
index 00000000..26bf281f
--- /dev/null
+++ b/leptonica/prog/test-cmap-alpha.png
Binary files differ
diff --git a/leptonica/prog/test-cmap-alpha2.png b/leptonica/prog/test-cmap-alpha2.png
new file mode 100644
index 00000000..26cfb819
--- /dev/null
+++ b/leptonica/prog/test-cmap-alpha2.png
Binary files differ
diff --git a/leptonica/prog/test-fulltrans-alpha.png b/leptonica/prog/test-fulltrans-alpha.png
new file mode 100644
index 00000000..9b69daa8
--- /dev/null
+++ b/leptonica/prog/test-fulltrans-alpha.png
Binary files differ
diff --git a/leptonica/prog/test-gray-alpha.png b/leptonica/prog/test-gray-alpha.png
new file mode 100644
index 00000000..3d49f3a1
--- /dev/null
+++ b/leptonica/prog/test-gray-alpha.png
Binary files differ
diff --git a/leptonica/prog/test1.bmp b/leptonica/prog/test1.bmp
new file mode 100644
index 00000000..d13002de
--- /dev/null
+++ b/leptonica/prog/test1.bmp
Binary files differ
diff --git a/leptonica/prog/test1.png b/leptonica/prog/test1.png
new file mode 100644
index 00000000..402fb2dc
--- /dev/null
+++ b/leptonica/prog/test1.png
Binary files differ
diff --git a/leptonica/prog/test16.png b/leptonica/prog/test16.png
new file mode 100644
index 00000000..54e9c370
--- /dev/null
+++ b/leptonica/prog/test16.png
Binary files differ
diff --git a/leptonica/prog/test16.tif b/leptonica/prog/test16.tif
new file mode 100644
index 00000000..36c0aa32
--- /dev/null
+++ b/leptonica/prog/test16.tif
Binary files differ
diff --git a/leptonica/prog/test24.jpg b/leptonica/prog/test24.jpg
new file mode 100644
index 00000000..6c5aff53
--- /dev/null
+++ b/leptonica/prog/test24.jpg
Binary files differ
diff --git a/leptonica/prog/test32-alpha.png b/leptonica/prog/test32-alpha.png
new file mode 100644
index 00000000..252171af
--- /dev/null
+++ b/leptonica/prog/test32-alpha.png
Binary files differ
diff --git a/leptonica/prog/test8.jpg b/leptonica/prog/test8.jpg
new file mode 100644
index 00000000..11863a6b
--- /dev/null
+++ b/leptonica/prog/test8.jpg
Binary files differ
diff --git a/leptonica/prog/testangle.na b/leptonica/prog/testangle.na
new file mode 100644
index 00000000..dcf6934b
--- /dev/null
+++ b/leptonica/prog/testangle.na
@@ -0,0 +1,19 @@
+
+Numa Version 1
+Number of numbers = 15
+ [0] = -1.000000
+ [1] = -2.000000
+ [2] = 0.000000
+ [3] = -1.500000
+ [4] = -0.500000
+ [5] = -1.250000
+ [6] = -0.750000
+ [7] = -1.125000
+ [8] = -0.875000
+ [9] = -1.062500
+ [10] = -0.937500
+ [11] = -0.968750
+ [12] = -0.906250
+ [13] = -0.953125
+ [14] = -0.921875
+
diff --git a/leptonica/prog/testbuffer.tif b/leptonica/prog/testbuffer.tif
new file mode 100644
index 00000000..213ac806
--- /dev/null
+++ b/leptonica/prog/testbuffer.tif
Binary files differ
diff --git a/leptonica/prog/testfile1.pdf b/leptonica/prog/testfile1.pdf
new file mode 100644
index 00000000..ade4373e
--- /dev/null
+++ b/leptonica/prog/testfile1.pdf
Binary files differ
diff --git a/leptonica/prog/testfile2.pdf b/leptonica/prog/testfile2.pdf
new file mode 100644
index 00000000..8389a3bb
--- /dev/null
+++ b/leptonica/prog/testfile2.pdf
Binary files differ
diff --git a/leptonica/prog/testscore.na b/leptonica/prog/testscore.na
new file mode 100644
index 00000000..e4b20f37
--- /dev/null
+++ b/leptonica/prog/testscore.na
@@ -0,0 +1,19 @@
+
+Numa Version 1
+Number of numbers = 15
+ [0] = 9090650.000000
+ [1] = 1837561.000000
+ [2] = 1648841.000000
+ [3] = 3320445.000000
+ [4] = 2573805.000000
+ [5] = 4926207.000000
+ [6] = 6150297.000000
+ [7] = 7065168.000000
+ [8] = 8679735.000000
+ [9] = 8093272.000000
+ [10] = 9742700.000000
+ [11] = 9711060.000000
+ [12] = 9275647.000000
+ [13] = 9825966.000000
+ [14] = 9559469.000000
+
diff --git a/leptonica/prog/tetons.jpg b/leptonica/prog/tetons.jpg
new file mode 100644
index 00000000..9c60b88b
--- /dev/null
+++ b/leptonica/prog/tetons.jpg
Binary files differ
diff --git a/leptonica/prog/textorient.c b/leptonica/prog/textorient.c
new file mode 100644
index 00000000..bde2abaf
--- /dev/null
+++ b/leptonica/prog/textorient.c
@@ -0,0 +1,95 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * textorient.c
+ *
+ * This attempts to identify the orientation of text in the image.
+ * If text is found, it is rotated by a multiple of 90 degrees
+ * to make it right-side up. It is not further deskewed.
+ * This works for roman mixed-case text. It will not work if the
+ * image has all caps or all numbers. It has not been tested on
+ * other scripts.
+
+ * Usage:
+ * textorient filein minupconf minratio fileout
+ *
+ * You can use minupconf = 0.0, minratio = 0.0 for default values,
+ * which are:
+ * minupconf = 8.0, minratio = 2.5
+ * fileout is the output file name, without the extension, which is
+ * added here depending on the encoding chosen for the output pix.
+ *
+ * Example on 1 bpp image:
+ * textorient feyn.tif 0.0 0.0 feyn.oriented
+ * which generates the file
+ * feyn.oriented.tif
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define BUF_SIZE 512
+
+LEPT_DLL extern const char *ImageFileFormatExtensions[];
+
+int main(int argc,
+ char **argv)
+{
+char buf[BUF_SIZE];
+const char *filein, *fileout;
+l_int32 pixformat;
+l_float32 minupconf, minratio;
+PIX *pixs, *pixd;
+static char mainName[] = "textorient";
+
+ if (argc != 5) {
+ return ERROR_INT(
+ "Syntax: textorient filein minupconf minratio, fileout",
+ mainName, 1);
+ }
+ filein = argv[1];
+ minupconf = atof(argv[2]);
+ minratio = atof(argv[3]);
+ fileout = argv[4];
+ setLeptDebugOK(1);
+
+ pixs = pixRead(filein);
+ pixd = pixOrientCorrect(pixs, minupconf, minratio, NULL, NULL, NULL, 1);
+
+ pixformat = pixChooseOutputFormat(pixd);
+ snprintf(buf, BUF_SIZE, "%s.%s", fileout,
+ ImageFileFormatExtensions[pixformat]);
+ pixWrite(buf, pixd, pixformat);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
+
diff --git a/leptonica/prog/texturefill_reg.c b/leptonica/prog/texturefill_reg.c
new file mode 100644
index 00000000..a4d59b4b
--- /dev/null
+++ b/leptonica/prog/texturefill_reg.c
@@ -0,0 +1,194 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * texturefill_reg.c
+ *
+ * This demonstrates a method for filling in a region using nearby
+ * patches and mirrored tiling to generate the texture.
+ *
+ * For each of a set of possible tiles to use, convert to gray
+ * and compute the mean and standard deviation of the intensity.
+ * Then determine the specific tile to use for filling by selecting
+ * the one that (1) has a mean value within 1.0 stdev of the median
+ * of average intensities, and (2) of that set has the smallest
+ * standard deviation of intensity.
+ *
+ * We can choose tiles looking either horizontally or vertically
+ * away from the region to be textured, or both. If both, the
+ * selected tiles are blended before painting the resulting
+ * texture through a mask.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Designed to work with amoris.2.150.jpg */
+static PIX *MakeReplacementMask(PIX *pixs);
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 bx, by, bw, bh;
+l_uint32 pixval;
+BOX *box1, *box2;
+BOXA *boxa;
+PIX *pixs, *pixm, *pixd;
+PIX *pix0, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Find a mask for repainting pixels */
+ pixs = pixRead("amoris.2.150.jpg");
+ pix1 = MakeReplacementMask(pixs);
+ boxa = pixConnCompBB(pix1, 8);
+ box1 = boxaGetBox(boxa, 0, L_COPY);
+ boxaDestroy(&boxa);
+
+ /*--------------------------------------------------------*
+ * Show the individual steps *
+ *--------------------------------------------------------*/
+ /* Locate a good tile to use */
+ pixFindRepCloseTile(pixs, box1, L_VERT, 20, 30, 7, &box2, 1);
+ pix0 = pixCopy(NULL, pix1);
+ pixRenderBox(pix0, box2, 2, L_SET_PIXELS);
+
+ /* Make a patch using this tile */
+ boxGetGeometry(box1, &bx, &by, &bw, &bh);
+ pix2 = pixClipRectangle(pixs, box2, NULL);
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pix2, 400, 100, NULL, rp->display);
+ pix3 = pixMirroredTiling(pix2, bw, bh);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pix3, 1000, 0, NULL, rp->display);
+
+ /* Paint the patch through the mask */
+ pixd = pixCopy(NULL, pixs);
+ pixm = pixClipRectangle(pix1, box1, NULL);
+ pixCombineMaskedGeneral(pixd, pix3, pixm, bx, by);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 2 */
+ pixDisplayWithTitle(pixd, 0, 0, NULL, rp->display);
+ boxDestroy(&box2);
+ pixDestroy(&pixm);
+ pixDestroy(&pixd);
+ pixDestroy(&pix2);
+
+ /* Blend two patches and then overlay. Use the previous
+ * tile found vertically and a new one found horizontally. */
+ pixFindRepCloseTile(pixs, box1, L_HORIZ, 20, 30, 7, &box2, 1);
+ pixRenderBox(pix0, box2, 2, L_SET_PIXELS);
+ regTestWritePixAndCheck(rp, pix0, IFF_TIFF_G4); /* 3 */
+ pixDisplayWithTitle(pix0, 100, 100, NULL, rp->display);
+ pix2 = pixClipRectangle(pixs, box2, NULL);
+ pix4 = pixMirroredTiling(pix2, bw, bh);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pix4, 1100, 0, NULL, rp->display);
+ pix5 = pixBlend(pix3, pix4, 0, 0, 0.5);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix5, 1200, 0, NULL, rp->display);
+ pix6 = pixClipRectangle(pix1, box1, NULL);
+ pixd = pixCopy(NULL, pixs);
+ pixCombineMaskedGeneral(pixd, pix5, pix6, bx, by);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 6 */
+ pixDisplayWithTitle(pixd, 700, 200, NULL, rp->display);
+ boxDestroy(&box2);
+ pixDestroy(&pixd);
+ pixDestroy(&pix0);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+
+ /*--------------------------------------------------------*
+ * Show painting from a color near region *
+ *--------------------------------------------------------*/
+ pix2 = pixCopy(NULL, pixs);
+ pixGetColorNearMaskBoundary(pix2, pix1, box1, 20, &pixval, 0);
+ pix3 = pixClipRectangle(pix1, box1, NULL);
+ boxGetGeometry(box1, &bx, &by, NULL, NULL);
+ pixSetMaskedGeneral(pix2, pix3, pixval, bx, by);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 7 */
+ pixDisplayWithTitle(pix2, 0, 0, NULL, rp->display);
+ boxDestroy(&box1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /*--------------------------------------------------------*
+ * Use the higher-level function *
+ *--------------------------------------------------------*/
+ /* Use various tile selections and tile blending with one component */
+ pix2 = pixCopy(NULL, pixs);
+ pix3 = pixCopy(NULL, pixs);
+ pix4 = pixCopy(NULL, pixs);
+ pixPaintSelfThroughMask(pix2, pix1, 0, 0, L_HORIZ, 30, 50, 5, 10);
+ pixPaintSelfThroughMask(pix3, pix1, 0, 0, L_VERT, 30, 50, 5, 0);
+ pixPaintSelfThroughMask(pixs, pix1, 0, 0, L_BOTH_DIRECTIONS, 30, 50, 5, 20);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 8 */
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 9 */
+ regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 10 */
+ pixDisplayWithTitle(pix2, 300, 0, NULL, rp->display);
+ pixDisplayWithTitle(pix3, 500, 0, NULL, rp->display);
+ pixDisplayWithTitle(pixs, 700, 0, NULL, rp->display);
+
+ /* Test with two components; */
+ pix5 = pixFlipLR(NULL, pix1);
+ pixOr(pix5, pix5, pix1);
+ pixPaintSelfThroughMask(pix4, pix5, 0, 0, L_BOTH_DIRECTIONS, 50, 100, 5, 9);
+ regTestWritePixAndCheck(rp, pix4, IFF_JFIF_JPEG); /* 11 */
+ pixDisplayWithTitle(pix4, 900, 0, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+
+ return regTestCleanup(rp);
+}
+
+
+PIX *
+MakeReplacementMask(PIX *pixs)
+{
+PIX *pix1, *pix2, *pix3, *pix4;
+
+ pix1 = pixMaskOverColorPixels(pixs, 95, 3);
+ pix2 = pixMorphSequence(pix1, "o15.15", 0);
+ pixSeedfillBinary(pix2, pix2, pix1, 8);
+ pix3 = pixMorphSequence(pix2, "c15.15 + d61.31", 0);
+ pix4 = pixRemoveBorderConnComps(pix3, 8);
+ pixXor(pix4, pix4, pix3);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ return pix4;
+}
diff --git a/leptonica/prog/threshnorm_reg.c b/leptonica/prog/threshnorm_reg.c
new file mode 100644
index 00000000..09f04635
--- /dev/null
+++ b/leptonica/prog/threshnorm_reg.c
@@ -0,0 +1,105 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * threshnorm__reg.c
+ *
+ * Regression test for adaptive threshold normalization.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static void AddTestSet(PIXA *pixa, 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);
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pixd;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("stampede2.jpg");
+ pixa = pixaCreate(0);
+
+ AddTestSet(pixa, pixs, L_SOBEL_EDGE, 18, 40, 40, 0.7, -25, 280, 128);
+ AddTestSet(pixa, pixs, L_TWO_SIDED_EDGE, 18, 40, 40, 0.7, -25, 280, 128);
+ AddTestSet(pixa, pixs, L_SOBEL_EDGE, 10, 40, 40, 0.7, -15, 305, 128);
+ AddTestSet(pixa, pixs, L_TWO_SIDED_EDGE, 10, 40, 40, 0.7, -15, 305, 128);
+ AddTestSet(pixa, pixs, L_SOBEL_EDGE, 15, 40, 40, 0.6, -45, 285, 158);
+ AddTestSet(pixa, pixs, L_TWO_SIDED_EDGE, 15, 40, 40, 0.6, -45, 285, 158);
+
+ pixDestroy(&pixs);
+ pixd = pixaDisplayTiledInColumns(pixa, 6, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 0 */
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ return regTestCleanup(rp);
+}
+
+
+void
+AddTestSet(PIXA *pixa,
+ 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 *pix1, *pix2, *pix3;
+
+ pixThresholdSpreadNorm(pixs, filtertype, edgethresh,
+ smoothx, smoothy, gamma, minval,
+ maxval, targetthresh, &pix1, NULL, &pix2);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix3 = pixThresholdToBinary(pix2, targetthresh - 20);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix3 = pixThresholdToBinary(pix2, targetthresh);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix3 = pixThresholdToBinary(pix2, targetthresh + 20);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix3 = pixThresholdToBinary(pix2, targetthresh + 40);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ return;
+}
diff --git a/leptonica/prog/tickets.tif b/leptonica/prog/tickets.tif
new file mode 100644
index 00000000..8609af28
--- /dev/null
+++ b/leptonica/prog/tickets.tif
Binary files differ
diff --git a/leptonica/prog/tiffpdftest.c b/leptonica/prog/tiffpdftest.c
new file mode 100644
index 00000000..bb710be4
--- /dev/null
+++ b/leptonica/prog/tiffpdftest.c
@@ -0,0 +1,142 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * tiffpdftest.c
+ *
+ * Generates pdf wrappers for tiff images, with both min_is_black
+ * and min_is_white. Demonstrates that multiple cycles using
+ * pdftoppm preserve photometry.
+ *
+ * Note: this test requires poppler pdf utilities, so it cannot
+ * be part of the alltests_reg regression test suite.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+char buf[256];
+l_int32 ret;
+l_float32 fract;
+PIX *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa1, *pixa2, *pixa3;
+
+#if !defined(HAVE_LIBTIFF)
+ L_ERROR("This test requires libtiff to run.\n", "tiffpdf_reg");
+ exit(77);
+#endif
+
+ setLeptDebugOK(1);
+ l_pdfSetDateAndVersion(0);
+ lept_mkdir("lept/tiffpdf");
+
+ /* Wrap min-is-white and min-is-black */
+ pix1 = pixRead("miniswhite.tif");
+ pix2 = pixRead("minisblack.tif");
+ pixCompareBinary(pix1, pix2, L_COMPARE_XOR, &fract, NULL);
+ lept_stderr("Compare input: %5.3f percent different\n", fract);
+ pixa1 = pixaCreate(2);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ lept_stderr("Writing /tmp/lept/tiffpdf/set1.pdf\n");
+ pixaConvertToPdf(pixa1, 100, 1.0, L_G4_ENCODE, 0, NULL,
+ "/tmp/lept/tiffpdf/set1.pdf");
+
+ /* Extract the images */
+ lept_rmdir("lept/tmp");
+ lept_mkdir("lept/tmp");
+ snprintf(buf, sizeof(buf),
+ "pdftoppm -r 300 /tmp/lept/tiffpdf/set1.pdf /tmp/lept/tmp/sevens");
+ ret = system(buf);
+
+ /* Re-wrap them */
+ pix1 = pixRead("/tmp/lept/tmp/sevens-1.ppm");
+ pix2 = pixRead("/tmp/lept/tmp/sevens-2.ppm");
+ pix3 = pixConvertTo1(pix1, 160);
+ pix4 = pixConvertTo1(pix2, 160);
+ pixCompareBinary(pix3, pix4, L_COMPARE_XOR, &fract, NULL);
+ lept_stderr("Compare after first extraction: "
+ "%5.3f percent different\n", fract);
+ pixa2 = pixaCreate(2);
+ pixaAddPix(pixa2, pix3, L_INSERT);
+ pixaAddPix(pixa2, pix4, L_INSERT);
+ lept_stderr("Writing /tmp/lept/tiffpdf/set2.pdf\n");
+ pixaConvertToPdf(pixa2, 300, 1.0, L_G4_ENCODE, 0, NULL,
+ "/tmp/lept/tiffpdf/set2.pdf");
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Extract the images again */
+ lept_rmdir("lept/tmp");
+ lept_mkdir("lept/tmp");
+ snprintf(buf, sizeof(buf),
+ "pdftoppm -r 300 /tmp/lept/tiffpdf/set2.pdf /tmp/lept/tmp/sevens");
+ ret = system(buf);
+
+ /* And wrap them up again */
+ pix1 = pixRead("/tmp/lept/tmp/sevens-1.ppm");
+ pix2 = pixRead("/tmp/lept/tmp/sevens-2.ppm");
+ pix3 = pixConvertTo1(pix1, 160);
+ pix4 = pixConvertTo1(pix2, 160);
+ pixCompareBinary(pix3, pix4, L_COMPARE_XOR, &fract, NULL);
+ lept_stderr("Compare after second extraction: "
+ "%5.3f percent different\n", fract);
+ pixa3 = pixaCreate(2);
+ pixaAddPix(pixa3, pix3, L_INSERT);
+ pixaAddPix(pixa3, pix4, L_INSERT);
+ lept_stderr("Writing /tmp/lept/tiffpdf/set3.pdf\n");
+ pixaConvertToPdf(pixa3, 300, 1.0, L_G4_ENCODE, 0, NULL,
+ "/tmp/lept/tiffpdf/set3.pdf");
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ pix1 = pixaGetPix(pixa2, 0, L_COPY);
+ pix2 = pixaGetPix(pixa3, 0, L_COPY);
+ pixCompareBinary(pix1, pix2, L_COMPARE_XOR, &fract, NULL);
+ lept_stderr("Compare between first and second extraction: "
+ "%5.3f percent different\n", fract);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Show the six images */
+ pixaJoin(pixa1, pixa2, 0, -1);
+ pixaJoin(pixa1, pixa3, 0, -1);
+ pix1 = pixaDisplayTiledInColumns(pixa1, 6, 1.0, 30, 2);
+ pixDisplay(pix1, 100, 100);
+ pixDestroy(&pix1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ return 0;
+}
+
+
diff --git a/leptonica/prog/toc.99.tif b/leptonica/prog/toc.99.tif
new file mode 100644
index 00000000..98d07cf6
--- /dev/null
+++ b/leptonica/prog/toc.99.tif
Binary files differ
diff --git a/leptonica/prog/topotest.png b/leptonica/prog/topotest.png
new file mode 100644
index 00000000..3bc5509e
--- /dev/null
+++ b/leptonica/prog/topotest.png
Binary files differ
diff --git a/leptonica/prog/translate_reg.c b/leptonica/prog/translate_reg.c
new file mode 100644
index 00000000..bf1c0262
--- /dev/null
+++ b/leptonica/prog/translate_reg.c
@@ -0,0 +1,163 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * translate_reg.c
+ *
+ * Regression test for in-place translation
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define BINARY_IMAGE "test1.png"
+#define GRAYSCALE_IMAGE "test8.jpg"
+#define FOUR_BPP_IMAGE "weasel4.8g.png"
+#define COLORMAP_IMAGE "dreyfus8.png"
+#define RGB_IMAGE "marge.jpg"
+
+void TranslateAndSave1(PIXA *pixa, l_int32 depth, PIX *pix,
+ l_int32 xshift, l_int32 yshift);
+
+void TranslateAndSave2(PIXA *pixa, PIX *pix, l_int32 xshift, l_int32 yshift);
+
+
+int main(int argc,
+ char **argv)
+{
+BOX *box;
+PIX *pixs, *pixd;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* Set up images */
+ pix1 = pixRead("weasel2.4c.png");
+ pix2 = pixScaleBySampling(pix1, 3.0, 3.0);
+ box = boxCreate(0, 0, 209, 214);
+ pixs = pixClipRectangle(pix2, box, NULL);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxDestroy(&box);
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+ pix2 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+ pix3 = pixConvertTo1(pixs, 128);
+ pix4 = pixRotateAM(pix1, 0.25, L_BRING_IN_BLACK);
+ pix5 = pixRotateAM(pix1, -0.25, L_BRING_IN_WHITE);
+ pix6 = pixRotateAM(pix2, -0.15, L_BRING_IN_BLACK);
+ pix7 = pixRotateAM(pix2, +0.15, L_BRING_IN_WHITE);
+
+ pixa = pixaCreate(0);
+ TranslateAndSave1(pixa, 32, pixs, 30, 30);
+ TranslateAndSave1(pixa, 32, pix1, 35, 20);
+ TranslateAndSave1(pixa, 32, pix2, 20, 35);
+ TranslateAndSave1(pixa, 32, pix3, 20, 35);
+ pixd = pixaDisplayTiledInColumns(pixa, 4, 1.0, 30, 3);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 0 */
+ pixDisplayWithTitle(pixd, 0, 0, "trans0", rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ pixa = pixaCreate(0);
+ TranslateAndSave1(pixa, 8, pix1, 35, 20);
+ TranslateAndSave1(pixa, 8, pix4, 35, 20);
+ pixd = pixaDisplayTiledInColumns(pixa, 4, 1.0, 30, 3);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 1 */
+ pixDisplayWithTitle(pixd, 250, 0, "trans1", rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ pixa = pixaCreate(0);
+ TranslateAndSave2(pixa, pixs, 30, 30);
+ TranslateAndSave2(pixa, pix1, 30, 30);
+ TranslateAndSave2(pixa, pix2, 35, 20);
+ TranslateAndSave2(pixa, pix3, 20, 35);
+ TranslateAndSave2(pixa, pix4, 25, 25);
+ TranslateAndSave2(pixa, pix5, 25, 25);
+ TranslateAndSave2(pixa, pix6, 25, 25);
+ TranslateAndSave2(pixa, pix7, 25, 25);
+ pixd = pixaDisplayTiledInColumns(pixa, 4, 1.0, 30, 3);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pixd, 500, 0, "trans2", rp->display);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ return regTestCleanup(rp);
+}
+
+
+void
+TranslateAndSave1(PIXA *pixa,
+ l_int32 depth,
+ PIX *pix,
+ l_int32 xshift,
+ l_int32 yshift)
+{
+PIX *pix1, *pix2, *pix3, *pix4;
+
+ pix1 = pixTranslate(NULL, pix, xshift, yshift, L_BRING_IN_WHITE);
+ pix2 = pixTranslate(NULL, pix, xshift, yshift, L_BRING_IN_BLACK);
+ pix3 = pixTranslate(NULL, pix, -xshift, -yshift, L_BRING_IN_WHITE);
+ pix4 = pixTranslate(NULL, pix, -xshift, -yshift, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ return;
+}
+
+void
+TranslateAndSave2(PIXA *pixa,
+ PIX *pix,
+ l_int32 xshift,
+ l_int32 yshift)
+{
+PIX *pix1, *pix2, *pix3, *pix4;
+
+ pix1 = pixTranslate(NULL, pix, xshift, yshift, L_BRING_IN_WHITE);
+ pix2 = pixTranslate(NULL, pix, xshift, yshift, L_BRING_IN_BLACK);
+ pix3 = pixTranslate(NULL, pix, -xshift, -yshift, L_BRING_IN_WHITE);
+ pix4 = pixTranslate(NULL, pix, -xshift, -yshift, L_BRING_IN_BLACK);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ return;
+}
diff --git a/leptonica/prog/trctest.c b/leptonica/prog/trctest.c
new file mode 100644
index 00000000..bd12d481
--- /dev/null
+++ b/leptonica/prog/trctest.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * trctest.c
+ *
+ * Example: trctest wet-day.jpg 3.1 50 160 /tmp/junk.png
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+PIX *pixs, *pixd;
+l_int32 minval, maxval;
+l_float32 gamma;
+char *filein, *fileout;
+static char mainName[] = "trctest";
+
+ if (argc != 6)
+ return ERROR_INT(" Syntax: trctest filein gamma minval maxval fileout",
+ mainName, 1);
+ filein = argv[1];
+ gamma = atof(argv[2]);
+ minval = atoi(argv[3]);
+ maxval = atoi(argv[4]);
+ fileout = argv[5];
+ setLeptDebugOK(1);
+
+ if ((pixs = pixRead(filein)) == NULL)
+ return ERROR_INT("pixs not made", mainName, 1);
+ pixd = pixGammaTRC(NULL, pixs, gamma, minval, maxval);
+ pixWrite(fileout, pixd, IFF_PNG);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
+
diff --git a/leptonica/prog/tribune-page-4x.png b/leptonica/prog/tribune-page-4x.png
new file mode 100644
index 00000000..fd551727
--- /dev/null
+++ b/leptonica/prog/tribune-page-4x.png
Binary files differ
diff --git a/leptonica/prog/tribune-t.png b/leptonica/prog/tribune-t.png
new file mode 100644
index 00000000..fb939dbf
--- /dev/null
+++ b/leptonica/prog/tribune-t.png
Binary files differ
diff --git a/leptonica/prog/tribune-word.png b/leptonica/prog/tribune-word.png
new file mode 100644
index 00000000..02a6b53d
--- /dev/null
+++ b/leptonica/prog/tribune-word.png
Binary files differ
diff --git a/leptonica/prog/turingtest.png b/leptonica/prog/turingtest.png
new file mode 100644
index 00000000..5ecce26b
--- /dev/null
+++ b/leptonica/prog/turingtest.png
Binary files differ
diff --git a/leptonica/prog/two-peak-histo.na b/leptonica/prog/two-peak-histo.na
new file mode 100644
index 00000000..7ae1cdf6
--- /dev/null
+++ b/leptonica/prog/two-peak-histo.na
@@ -0,0 +1,260 @@
+
+Numa Version 1
+Number of numbers = 256
+ [0] = 5485.000000
+ [1] = 1064.000000
+ [2] = 542.000000
+ [3] = 531.000000
+ [4] = 588.000000
+ [5] = 462.000000
+ [6] = 600.000000
+ [7] = 798.000000
+ [8] = 838.000000
+ [9] = 769.000000
+ [10] = 669.000000
+ [11] = 599.000000
+ [12] = 1238.000000
+ [13] = 892.000000
+ [14] = 798.000000
+ [15] = 2891.000000
+ [16] = 4042.000000
+ [17] = 1296.000000
+ [18] = 3049.000000
+ [19] = 4714.000000
+ [20] = 4046.000000
+ [21] = 2449.000000
+ [22] = 2385.000000
+ [23] = 1245.000000
+ [24] = 926.000000
+ [25] = 1198.000000
+ [26] = 2606.000000
+ [27] = 867.000000
+ [28] = 580.000000
+ [29] = 542.000000
+ [30] = 540.000000
+ [31] = 400.000000
+ [32] = 367.000000
+ [33] = 437.000000
+ [34] = 417.000000
+ [35] = 345.000000
+ [36] = 228.000000
+ [37] = 211.000000
+ [38] = 215.000000
+ [39] = 145.000000
+ [40] = 98.000000
+ [41] = 112.000000
+ [42] = 80.000000
+ [43] = 89.000000
+ [44] = 97.000000
+ [45] = 84.000000
+ [46] = 83.000000
+ [47] = 43.000000
+ [48] = 35.000000
+ [49] = 66.000000
+ [50] = 66.000000
+ [51] = 50.000000
+ [52] = 60.000000
+ [53] = 40.000000
+ [54] = 42.000000
+ [55] = 39.000000
+ [56] = 53.000000
+ [57] = 47.000000
+ [58] = 55.000000
+ [59] = 57.000000
+ [60] = 71.000000
+ [61] = 38.000000
+ [62] = 52.000000
+ [63] = 41.000000
+ [64] = 43.000000
+ [65] = 39.000000
+ [66] = 35.000000
+ [67] = 24.000000
+ [68] = 27.000000
+ [69] = 24.000000
+ [70] = 25.000000
+ [71] = 19.000000
+ [72] = 16.000000
+ [73] = 36.000000
+ [74] = 21.000000
+ [75] = 24.000000
+ [76] = 11.000000
+ [77] = 18.000000
+ [78] = 17.000000
+ [79] = 17.000000
+ [80] = 11.000000
+ [81] = 14.000000
+ [82] = 11.000000
+ [83] = 11.000000
+ [84] = 21.000000
+ [85] = 15.000000
+ [86] = 9.000000
+ [87] = 9.000000
+ [88] = 22.000000
+ [89] = 9.000000
+ [90] = 11.000000
+ [91] = 6.000000
+ [92] = 16.000000
+ [93] = 16.000000
+ [94] = 9.000000
+ [95] = 10.000000
+ [96] = 10.000000
+ [97] = 12.000000
+ [98] = 9.000000
+ [99] = 9.000000
+ [100] = 6.000000
+ [101] = 7.000000
+ [102] = 8.000000
+ [103] = 11.000000
+ [104] = 6.000000
+ [105] = 4.000000
+ [106] = 4.000000
+ [107] = 5.000000
+ [108] = 10.000000
+ [109] = 8.000000
+ [110] = 13.000000
+ [111] = 6.000000
+ [112] = 7.000000
+ [113] = 8.000000
+ [114] = 10.000000
+ [115] = 11.000000
+ [116] = 17.000000
+ [117] = 12.000000
+ [118] = 6.000000
+ [119] = 7.000000
+ [120] = 10.000000
+ [121] = 10.000000
+ [122] = 15.000000
+ [123] = 11.000000
+ [124] = 10.000000
+ [125] = 16.000000
+ [126] = 20.000000
+ [127] = 14.000000
+ [128] = 16.000000
+ [129] = 19.000000
+ [130] = 18.000000
+ [131] = 13.000000
+ [132] = 15.000000
+ [133] = 27.000000
+ [134] = 15.000000
+ [135] = 15.000000
+ [136] = 16.000000
+ [137] = 20.000000
+ [138] = 21.000000
+ [139] = 20.000000
+ [140] = 29.000000
+ [141] = 24.000000
+ [142] = 24.000000
+ [143] = 25.000000
+ [144] = 29.000000
+ [145] = 30.000000
+ [146] = 25.000000
+ [147] = 30.000000
+ [148] = 36.000000
+ [149] = 44.000000
+ [150] = 43.000000
+ [151] = 40.000000
+ [152] = 36.000000
+ [153] = 51.000000
+ [154] = 56.000000
+ [155] = 33.000000
+ [156] = 40.000000
+ [157] = 51.000000
+ [158] = 39.000000
+ [159] = 32.000000
+ [160] = 28.000000
+ [161] = 28.000000
+ [162] = 24.000000
+ [163] = 23.000000
+ [164] = 24.000000
+ [165] = 22.000000
+ [166] = 19.000000
+ [167] = 34.000000
+ [168] = 26.000000
+ [169] = 39.000000
+ [170] = 45.000000
+ [171] = 47.000000
+ [172] = 45.000000
+ [173] = 55.000000
+ [174] = 42.000000
+ [175] = 68.000000
+ [176] = 78.000000
+ [177] = 147.000000
+ [178] = 237.000000
+ [179] = 395.000000
+ [180] = 407.000000
+ [181] = 387.000000
+ [182] = 377.000000
+ [183] = 381.000000
+ [184] = 405.000000
+ [185] = 370.000000
+ [186] = 360.000000
+ [187] = 353.000000
+ [188] = 376.000000
+ [189] = 325.000000
+ [190] = 363.000000
+ [191] = 332.000000
+ [192] = 358.000000
+ [193] = 400.000000
+ [194] = 436.000000
+ [195] = 451.000000
+ [196] = 533.000000
+ [197] = 580.000000
+ [198] = 582.000000
+ [199] = 631.000000
+ [200] = 709.000000
+ [201] = 708.000000
+ [202] = 638.000000
+ [203] = 621.000000
+ [204] = 687.000000
+ [205] = 677.000000
+ [206] = 691.000000
+ [207] = 715.000000
+ [208] = 845.000000
+ [209] = 995.000000
+ [210] = 1156.000000
+ [211] = 1250.000000
+ [212] = 1174.000000
+ [213] = 1080.000000
+ [214] = 1246.000000
+ [215] = 1239.000000
+ [216] = 1385.000000
+ [217] = 1536.000000
+ [218] = 1553.000000
+ [219] = 1525.000000
+ [220] = 1595.000000
+ [221] = 1527.000000
+ [222] = 1465.000000
+ [223] = 1391.000000
+ [224] = 1786.000000
+ [225] = 2353.000000
+ [226] = 2480.000000
+ [227] = 1712.000000
+ [228] = 890.000000
+ [229] = 379.000000
+ [230] = 155.000000
+ [231] = 31.000000
+ [232] = 15.000000
+ [233] = 3.000000
+ [234] = 3.000000
+ [235] = 0.000000
+ [236] = 0.000000
+ [237] = 0.000000
+ [238] = 0.000000
+ [239] = 0.000000
+ [240] = 0.000000
+ [241] = 0.000000
+ [242] = 0.000000
+ [243] = 0.000000
+ [244] = 0.000000
+ [245] = 0.000000
+ [246] = 0.000000
+ [247] = 0.000000
+ [248] = 0.000000
+ [249] = 0.000000
+ [250] = 0.000000
+ [251] = 0.000000
+ [252] = 0.000000
+ [253] = 0.000000
+ [254] = 0.000000
+ [255] = 0.000000
+
diff --git a/leptonica/prog/underline1.jpg b/leptonica/prog/underline1.jpg
new file mode 100644
index 00000000..20b976a5
--- /dev/null
+++ b/leptonica/prog/underline1.jpg
Binary files differ
diff --git a/leptonica/prog/underline2.jpg b/leptonica/prog/underline2.jpg
new file mode 100644
index 00000000..515a618f
--- /dev/null
+++ b/leptonica/prog/underline2.jpg
Binary files differ
diff --git a/leptonica/prog/underline3.jpg b/leptonica/prog/underline3.jpg
new file mode 100644
index 00000000..fa17d3ab
--- /dev/null
+++ b/leptonica/prog/underline3.jpg
Binary files differ
diff --git a/leptonica/prog/underline4.jpg b/leptonica/prog/underline4.jpg
new file mode 100644
index 00000000..dcfd0da4
--- /dev/null
+++ b/leptonica/prog/underline4.jpg
Binary files differ
diff --git a/leptonica/prog/underline5.jpg b/leptonica/prog/underline5.jpg
new file mode 100644
index 00000000..04031abf
--- /dev/null
+++ b/leptonica/prog/underline5.jpg
Binary files differ
diff --git a/leptonica/prog/underline6.jpg b/leptonica/prog/underline6.jpg
new file mode 100644
index 00000000..2a3fb7d0
--- /dev/null
+++ b/leptonica/prog/underline6.jpg
Binary files differ
diff --git a/leptonica/prog/underline7.jpg b/leptonica/prog/underline7.jpg
new file mode 100644
index 00000000..f909be6c
--- /dev/null
+++ b/leptonica/prog/underline7.jpg
Binary files differ
diff --git a/leptonica/prog/underlinetest.c b/leptonica/prog/underlinetest.c
new file mode 100644
index 00000000..42b1bf8f
--- /dev/null
+++ b/leptonica/prog/underlinetest.c
@@ -0,0 +1,95 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * underlinetest.c
+ *
+ * Example program for removing lines under text
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const char *files[] = {"underline1.jpg", "underline2.jpg",
+ "underline3.jpg", "underline4.jpg",
+ "underline5.jpg", "underline6.jpg",
+ "underline7.jpg"};
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i;
+PIX *pixs, *pixg, *pixg2, *pixb, *pixm, *pixsd, *pixsdd, *pixt, *pixd;
+PIXA *pixa;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/underline");
+
+ pixa = pixaCreate(0);
+ for (i = 0; i < 7; i++) {
+ lept_stderr("%d...", i + 1);
+ pixs = pixRead(files[i]);
+ pixg = pixConvertTo8(pixs, 0);
+ pixg2 = pixBackgroundNorm(pixg, NULL, NULL, 15, 15, 70, 105, 200, 5, 5);
+ pixSauvolaBinarizeTiled(pixg2, 8, 0.34, 1, 1, NULL, &pixb);
+ pixaAddPix(pixa, pixg, L_INSERT);
+ pixaAddPix(pixa, pixb, L_INSERT);
+ pixDestroy(&pixs);
+ pixDestroy(&pixg2);
+
+ /* Get a seed image; try to have at least one pixel
+ * in each underline c.c */
+ pixsd = pixMorphSequence(pixb, "c3.1 + o60.1", 0);
+
+ /* Get a mask image for the underlines.
+ * The o30.1 tries to remove accidental connections to text. */
+ pixm = pixMorphSequence(pixb, "c7.1 + o30.1", 0);
+
+ /* Fill into the seed, clipping to the mask */
+ pixSeedfillBinary(pixsd, pixsd, pixm, 8);
+ pixDestroy(&pixm);
+
+ /* Small vertical dilation for better removal */
+ pixsdd = pixMorphSequence(pixsd, "d1.3", 0);
+ pixaAddPix(pixa, pixsdd, L_INSERT);
+ pixDestroy(&pixsd);
+
+ /* Subtracat to get text without underlines */
+ pixt = pixSubtract(NULL, pixb, pixsdd);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ }
+ lept_stderr("\n");
+
+ pixd = pixaDisplayTiledInColumns(pixa, 4, 0.6, 20, 2);
+ pixWrite("/tmp/lept/underline/result.png", pixd, IFF_PNG);
+ pixDisplay(pixd, 100, 100);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+ return 0;
+}
diff --git a/leptonica/prog/w91frag.jpg b/leptonica/prog/w91frag.jpg
new file mode 100644
index 00000000..7cfdc279
--- /dev/null
+++ b/leptonica/prog/w91frag.jpg
Binary files differ
diff --git a/leptonica/prog/warped_paper.jpg b/leptonica/prog/warped_paper.jpg
new file mode 100644
index 00000000..44701cca
--- /dev/null
+++ b/leptonica/prog/warped_paper.jpg
Binary files differ
diff --git a/leptonica/prog/warped_sudoku.jpg b/leptonica/prog/warped_sudoku.jpg
new file mode 100644
index 00000000..4c6c79f5
--- /dev/null
+++ b/leptonica/prog/warped_sudoku.jpg
Binary files differ
diff --git a/leptonica/prog/warper_reg.c b/leptonica/prog/warper_reg.c
new file mode 100644
index 00000000..0e796996
--- /dev/null
+++ b/leptonica/prog/warper_reg.c
@@ -0,0 +1,138 @@
+/*====================================================================*
+ - 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_reg.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+static void DisplayResult(PIXA *pixac, PIX **ppixd, l_int32 newline);
+static void DisplayCaptcha(PIXA *pixac, PIX *pixs, l_int32 nterms,
+ l_uint32 seed, l_int32 newline);
+
+static const l_int32 size = 4;
+static const l_float32 xmag[] = {3.0f, 4.0f, 5.0f, 7.0f};
+static const l_float32 ymag[] = {5.0f, 6.0f, 8.0f, 10.0f};
+static const l_float32 xfreq[] = {0.11f, 0.10f, 0.10f, 0.12f};
+static const l_float32 yfreq[] = {0.11f, 0.13f, 0.13f, 0.15f};
+static const l_int32 nx[] = {4, 3, 2, 1};
+static const l_int32 ny[] = {4, 3, 2, 1};
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, k, newline;
+PIX *pixs, *pixt, *pixg, *pixd;
+PIXA *pixac;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pixs = pixRead("feyn-word.tif");
+ pixt = pixAddBorder(pixs, 25, 0);
+ pixg = pixConvertTo8(pixt, 0);
+
+ for (k = 0; k < size; k++) {
+ pixac = pixaCreate(0);
+ for (i = 0; i < 50; i++) {
+ pixd = pixRandomHarmonicWarp(pixg, xmag[k], ymag[k], xfreq[k],
+ yfreq[k], nx[k], ny[k], 7 * i, 255);
+ newline = (i % 10 == 0) ? 1 : 0;
+ DisplayResult(pixac, &pixd, newline);
+ }
+ pixd = pixaDisplayTiledInColumns(pixac, 10, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixaDestroy(&pixac);
+ pixDestroy(&pixd);
+ }
+
+ pixDestroy(&pixt);
+ pixDestroy(&pixg);
+
+ for (k = 1; k <= 4; k++) {
+ pixac = pixaCreate(0);
+ for (i = 0; i < 50; i++) {
+ newline = (i % 10 == 0) ? 1 : 0;
+ DisplayCaptcha(pixac, pixs, k, 7 * i, newline);
+ }
+ pixd = pixaDisplayTiledInColumns(pixac, 10, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG);
+ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display);
+ pixaDestroy(&pixac);
+ pixDestroy(&pixd);
+ }
+
+ pixDestroy(&pixs);
+ return regTestCleanup(rp);
+}
+
+
+static void
+DisplayResult(PIXA *pixac,
+ PIX **ppixd,
+ l_int32 newline)
+{
+l_uint32 color;
+PIX *pix1;
+
+ color = 0;
+ color = ((rand() >> 16) & 0xff) << L_RED_SHIFT |
+ ((rand() >> 16) & 0xff) << L_GREEN_SHIFT |
+ ((rand() >> 16) & 0xff) << L_BLUE_SHIFT;
+ pix1 = pixColorizeGray(*ppixd, color, 0);
+ pixaAddPix(pixac, pix1, L_INSERT);
+ pixDestroy(ppixd);
+ return;
+}
+
+
+static void
+DisplayCaptcha(PIXA *pixac,
+ PIX *pixs,
+ l_int32 nterms,
+ l_uint32 seed,
+ l_int32 newline)
+{
+l_uint32 color;
+PIX *pixd;
+
+ color = 0;
+ color = ((rand() >> 16) & 0xff) << L_RED_SHIFT |
+ ((rand() >> 16) & 0xff) << L_GREEN_SHIFT |
+ ((rand() >> 16) & 0xff) << L_BLUE_SHIFT;
+ pixd = pixSimpleCaptcha(pixs, 25, nterms, seed, color, 0);
+ pixaAddPix(pixac, pixd, L_INSERT);
+ return;
+}
diff --git a/leptonica/prog/warpertest.c b/leptonica/prog/warpertest.c
new file mode 100644
index 00000000..62b9e363
--- /dev/null
+++ b/leptonica/prog/warpertest.c
@@ -0,0 +1,256 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * warpertest.c
+ *
+ * Tests stereoscopic warp and associated shear and stretching functions.
+ *
+ * Puts output to both a tiled image and pdf. The pdf is useful for
+ * visualizing the difference between sampling and interpolation.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static const char *opstr[3] = {"", "interpolated", "sampled"};
+static const char *dirstr[3] = {"", "to left", "to right"};
+
+#define RUN_WARP 1
+#define RUN_QUAD_VERT_SHEAR 1
+#define RUN_LIN_HORIZ_STRETCH 1
+#define RUN_QUAD_HORIZ_STRETCH 1
+#define RUN_HORIZ_SHEAR 1
+#define RUN_VERT_SHEAR 1
+
+int main(int argc,
+ char **argv)
+{
+char buf[256];
+l_int32 w, h, i, j, k, index, op, dir, stretch;
+l_float32 del, angle, angledeg;
+BOX *box;
+L_BMF *bmf;
+PIX *pixs, *pix1, *pix2, *pixd;
+PIXA *pixa;
+static char mainName[] = "warpertest";
+
+ if (argc != 1)
+ return ERROR_INT("syntax: warpertest", mainName, 1);
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/warp");
+ bmf = bmfCreate(NULL, 6);
+
+ /* -------- Stereoscopic warping --------------*/
+#if RUN_WARP
+ pixs = pixRead("german.png");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixa = pixaCreate(50);
+ for (i = 0; i < 50; i++) { /* need to test > 2 widths ! */
+ j = 7 * i;
+ box = boxCreate(0, 0, w - j, h - j);
+ pix1 = pixClipRectangle(pixs, box, NULL);
+ pixd = pixWarpStereoscopic(pix1, 15, 22, 8, 30, -20, 1);
+ pixSetChromaSampling(pixd, 0);
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pix1);
+ boxDestroy(&box);
+ }
+ pixDestroy(&pixs);
+
+ pixaConvertToPdf(pixa, 100, 1.0, L_JPEG_ENCODE, 0, "warp.pdf",
+ "/tmp/lept/warp/warp.pdf");
+ pixd = pixaDisplayTiledInRows(pixa, 32, 2000, 1.0, 0, 20, 2);
+ pixWrite("/tmp/lept/warp/warp.jpg", pixd, IFF_JFIF_JPEG);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+#endif
+
+ /* -------- Quadratic Vertical Shear --------------*/
+#if RUN_QUAD_VERT_SHEAR
+ pixs = pixCreate(501, 501, 32);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixSetAll(pixs);
+ pixRenderLineArb(pixs, 0, 30, 500, 30, 5, 0, 0, 255);
+ pixRenderLineArb(pixs, 0, 110, 500, 110, 5, 0, 255, 0);
+ pixRenderLineArb(pixs, 0, 190, 500, 190, 5, 0, 255, 255);
+ pixRenderLineArb(pixs, 0, 270, 500, 270, 5, 255, 0, 0);
+ pixRenderLineArb(pixs, 0, 360, 500, 360, 5, 255, 0, 255);
+ pixRenderLineArb(pixs, 0, 450, 500, 450, 5, 255, 255, 0);
+ pixa = pixaCreate(50);
+ for (i = 0; i < 50; i++) {
+ j = 3 * i;
+ dir = ((i / 2) & 1) ? L_WARP_TO_RIGHT : L_WARP_TO_LEFT;
+ op = (i & 1) ? L_INTERPOLATED : L_SAMPLED;
+ box = boxCreate(0, 0, w - j, h - j);
+ pix1 = pixClipRectangle(pixs, box, NULL);
+ pix2 = pixQuadraticVShear(pix1, dir, 60, -20, op, L_BRING_IN_WHITE);
+
+ snprintf(buf, sizeof(buf), "%s, %s", dirstr[dir], opstr[op]);
+ pixd = pixAddSingleTextblock(pix2, bmf, buf, 0xff000000,
+ L_ADD_BELOW, 0);
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ boxDestroy(&box);
+ }
+ pixDestroy(&pixs);
+
+ pixaConvertToPdf(pixa, 100, 1.0, L_FLATE_ENCODE, 0, "quad_vshear.pdf",
+ "/tmp/lept/warp/quad_vshear.pdf");
+ pixd = pixaDisplayTiledInRows(pixa, 32, 2000, 1.0, 0, 20, 2);
+ pixWrite("/tmp/lept/warp/quad_vshear.jpg", pixd, IFF_PNG);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+#endif
+
+ /* -------- Linear Horizontal stretching --------------*/
+#if RUN_LIN_HORIZ_STRETCH
+ pixs = pixRead("german.png");
+ pixa = pixaCreate(50);
+ for (k = 0; k < 2; k++) {
+ for (i = 0; i < 25; i++) {
+ index = 25 * k + i;
+ stretch = 10 + 4 * i;
+ if (k == 0) stretch = -stretch;
+ dir = (k == 1) ? L_WARP_TO_RIGHT : L_WARP_TO_LEFT;
+ op = (i & 1) ? L_INTERPOLATED : L_SAMPLED;
+ pix1 = pixStretchHorizontal(pixs, dir, L_LINEAR_WARP,
+ stretch, op, L_BRING_IN_WHITE);
+ snprintf(buf, sizeof(buf), "%s, %s", dirstr[dir], opstr[op]);
+ pixd = pixAddSingleTextblock(pix1, bmf, buf, 0xff000000,
+ L_ADD_BELOW, 0);
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ }
+ pixDestroy(&pixs);
+
+ pixaConvertToPdf(pixa, 100, 1.0, L_JPEG_ENCODE, 0, "linear_hstretch.pdf",
+ "/tmp/lept/warp/linear_hstretch.pdf");
+ pixd = pixaDisplayTiledInRows(pixa, 32, 2500, 1.0, 0, 20, 2);
+ pixWrite("/tmp/lept/warp/linear_hstretch.jpg", pixd, IFF_JFIF_JPEG);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+#endif
+
+ /* -------- Quadratic Horizontal stretching --------------*/
+#if RUN_QUAD_HORIZ_STRETCH
+ pixs = pixRead("german.png");
+ pixa = pixaCreate(50);
+ for (k = 0; k < 2; k++) {
+ for (i = 0; i < 25; i++) {
+ index = 25 * k + i;
+ stretch = 10 + 4 * i;
+ if (k == 0) stretch = -stretch;
+ dir = (k == 1) ? L_WARP_TO_RIGHT : L_WARP_TO_LEFT;
+ op = (i & 1) ? L_INTERPOLATED : L_SAMPLED;
+ pix1 = pixStretchHorizontal(pixs, dir, L_QUADRATIC_WARP,
+ stretch, op, L_BRING_IN_WHITE);
+ snprintf(buf, sizeof(buf), "%s, %s", dirstr[dir], opstr[op]);
+ pixd = pixAddSingleTextblock(pix1, bmf, buf, 0xff000000,
+ L_ADD_BELOW, 0);
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ }
+ pixDestroy(&pixs);
+
+ pixaConvertToPdf(pixa, 100, 1.0, L_JPEG_ENCODE, 0, "quad_hstretch.pdf",
+ "/tmp/lept/warp/quad_hstretch.pdf");
+ pixd = pixaDisplayTiledInRows(pixa, 32, 2500, 1.0, 0, 20, 2);
+ pixWrite("/tmp/lept/warp/quad_hstretch.jpg", pixd, IFF_JFIF_JPEG);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+#endif
+
+ /* -------- Horizontal Shear --------------*/
+#if RUN_HORIZ_SHEAR
+ pixs = pixRead("german.png");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixa = pixaCreate(50);
+ for (i = 0; i < 25; i++) {
+ del = 0.2 / 12.;
+ angle = -0.2 + (i - (i & 1)) * del;
+ angledeg = 180. * angle / 3.14159265;
+ op = (i & 1) ? L_INTERPOLATED : L_SAMPLED;
+ if (op == L_SAMPLED)
+ pix1 = pixHShear(NULL, pixs, h / 2, angle, L_BRING_IN_WHITE);
+ else
+ pix1 = pixHShearLI(pixs, h / 2, angle, L_BRING_IN_WHITE);
+ snprintf(buf, sizeof(buf), "%6.2f degree, %s", angledeg, opstr[op]);
+ pixd = pixAddSingleTextblock(pix1, bmf, buf, 0xff000000,
+ L_ADD_BELOW, 0);
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ pixDestroy(&pixs);
+
+ pixaConvertToPdf(pixa, 100, 1.0, L_JPEG_ENCODE, 0, "hshear.pdf",
+ "/tmp/lept/warp/hshear.pdf");
+ pixd = pixaDisplayTiledInRows(pixa, 32, 2500, 1.0, 0, 20, 2);
+ pixWrite("/tmp/lept/warp/hshear.jpg", pixd, IFF_JFIF_JPEG);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+#endif
+
+ /* -------- Vertical Shear --------------*/
+#if RUN_VERT_SHEAR
+ pixs = pixRead("german.png");
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixa = pixaCreate(50);
+ for (i = 0; i < 25; i++) {
+ del = 0.2 / 12.;
+ angle = -0.2 + (i - (i & 1)) * del;
+ angledeg = 180. * angle / 3.14159265;
+ op = (i & 1) ? L_INTERPOLATED : L_SAMPLED;
+ if (op == L_SAMPLED)
+ pix1 = pixVShear(NULL, pixs, w / 2, angle, L_BRING_IN_WHITE);
+ else
+ pix1 = pixVShearLI(pixs, w / 2, angle, L_BRING_IN_WHITE);
+ snprintf(buf, sizeof(buf), "%6.2f degree, %s", angledeg, opstr[op]);
+ pixd = pixAddSingleTextblock(pix1, bmf, buf, 0xff000000,
+ L_ADD_BELOW, 0);
+ pixaAddPix(pixa, pixd, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ pixDestroy(&pixs);
+ pixaConvertToPdf(pixa, 100, 1.0, L_JPEG_ENCODE, 0, "vshear.pdf",
+ "/tmp/lept/warp/vshear.pdf");
+ pixd = pixaDisplayTiledInRows(pixa, 32, 2500, 1.0, 0, 20, 2);
+ pixWrite("/tmp/lept/warp/vshear.jpg", pixd, IFF_JFIF_JPEG);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixd);
+#endif
+
+ bmfDestroy(&bmf);
+ return 0;
+}
diff --git a/leptonica/prog/watershed_reg.c b/leptonica/prog/watershed_reg.c
new file mode 100644
index 00000000..f09fb44d
--- /dev/null
+++ b/leptonica/prog/watershed_reg.c
@@ -0,0 +1,157 @@
+/*====================================================================*
+ - 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_reg.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+void DoWatershed(L_REGPARAMS *rp, PIX *pixs);
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, j;
+l_float32 f;
+PIX *pix1, *pix2;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ pix1 = pixCreate(500, 500, 8);
+ pix2 = pixCreate(500, 500, 8);
+ for (i = 0; i < 500; i++) {
+ for (j = 0; j < 500; j++) {
+ f = 128.0 + 26.3 * sin(0.0438 * (l_float32)i);
+ f += 33.4 * cos(0.0712 * (l_float32)i);
+ f += 18.6 * sin(0.0561 * (l_float32)j);
+ f += 23.6 * cos(0.0327 * (l_float32)j);
+ pixSetPixel(pix1, j, i, (l_int32)f);
+ f = 128.0 + 26.3 * sin(0.0238 * (l_float32)i);
+ f += 33.4 * cos(0.0312 * (l_float32)i);
+ f += 18.6 * sin(0.0261 * (l_float32)j);
+ f += 23.6 * cos(0.0207 * (l_float32)j);
+ pixSetPixel(pix2, j, i, (l_int32)f);
+ }
+ }
+ DoWatershed(rp, pix1); /* 0 - 11 */
+ DoWatershed(rp, pix2); /* 12 - 23 */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return regTestCleanup(rp);
+}
+
+
+void
+DoWatershed(L_REGPARAMS *rp,
+ PIX *pixs)
+{
+l_uint8 *data;
+size_t size;
+l_int32 w, h, empty;
+l_uint32 redval, greenval;
+L_WSHED *wshed;
+PIX *pixc, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8, *pix9;
+PIXA *pixa;
+PTA *pta;
+
+ /* Find local extrema */
+ pixa = pixaCreate(0);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ regTestWritePixAndCheck(rp, pixs, IFF_PNG); /* 0 */
+ pixaAddPix(pixa, pixs, L_COPY);
+ startTimer();
+ pixLocalExtrema(pixs, 0, 0, &pix1, &pix2);
+ lept_stderr("Time for extrema: %7.3f\n", stopTimer());
+ pixSetOrClearBorder(pix1, 2, 2, 2, 2, PIX_CLR);
+ composeRGBPixel(255, 0, 0, &redval);
+ composeRGBPixel(0, 255, 0, &greenval);
+ pixc = pixConvertTo32(pixs);
+ pixPaintThroughMask(pixc, pix2, 0, 0, greenval);
+ pixPaintThroughMask(pixc, pix1, 0, 0, redval);
+ regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 1 */
+ pixaAddPix(pixa, pixc, L_INSERT);
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */
+ pixaAddPix(pixa, pix1, L_COPY);
+
+ /* Generate seeds for watershed */
+ pixSelectMinInConnComp(pixs, pix1, &pta, NULL);
+ pix3 = pixGenerateFromPta(pta, w, h);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3 */
+ pixaAddPix(pixa, pix3, L_COPY);
+ pix4 = pixConvertTo32(pixs);
+ pixPaintThroughMask(pix4, pix3, 0, 0, greenval);
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 4 */
+ pixaAddPix(pixa, pix4, L_COPY);
+ pix5 = pixRemoveSeededComponents(NULL, pix3, pix1, 8, 2);
+ regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 5 */
+ pixaAddPix(pixa, pix5, L_COPY);
+ pixZero(pix5, &empty);
+ regTestCompareValues(rp, 1, empty, 0.0); /* 6 */
+
+ /* Make and display watershed */
+ wshed = wshedCreate(pixs, pix3, 10, 0);
+ startTimer();
+ wshedApply(wshed);
+ lept_stderr("Time for wshed: %7.3f\n", stopTimer());
+ pix6 = pixaDisplayRandomCmap(wshed->pixad, w, h);
+ regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 7 */
+ pixaAddPix(pixa, pix6, L_COPY);
+ numaWriteMem(&data, &size, wshed->nalevels);
+ regTestWriteDataAndCheck(rp, data, size, "na"); /* 8 */
+ pix7 = wshedRenderFill(wshed);
+ regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 9 */
+ pixaAddPix(pixa, pix7, L_COPY);
+ pix8 = wshedRenderColors(wshed);
+ regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 10 */
+ pixaAddPix(pixa, pix8, L_COPY);
+ wshedDestroy(&wshed);
+
+ pix9 = pixaDisplayTiledInColumns(pixa, 3, 1.0, 20, 0);
+ regTestWritePixAndCheck(rp, pix9, IFF_PNG); /* 11 */
+ pixDisplayWithTitle(pix9, 100, 100, NULL, rp->display);
+
+ lept_free(data);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ pixDestroy(&pix9);
+ pixaDestroy(&pixa);
+ ptaDestroy(&pta);
+}
+
diff --git a/leptonica/prog/weasel-113c.png b/leptonica/prog/weasel-113c.png
new file mode 100644
index 00000000..2fb7877d
--- /dev/null
+++ b/leptonica/prog/weasel-113c.png
Binary files differ
diff --git a/leptonica/prog/weasel-44c.png b/leptonica/prog/weasel-44c.png
new file mode 100644
index 00000000..69752534
--- /dev/null
+++ b/leptonica/prog/weasel-44c.png
Binary files differ
diff --git a/leptonica/prog/weasel-4c.2.png b/leptonica/prog/weasel-4c.2.png
new file mode 100644
index 00000000..4bd031fd
--- /dev/null
+++ b/leptonica/prog/weasel-4c.2.png
Binary files differ
diff --git a/leptonica/prog/weasel-64g.png b/leptonica/prog/weasel-64g.png
new file mode 100644
index 00000000..3ff8bd21
--- /dev/null
+++ b/leptonica/prog/weasel-64g.png
Binary files differ
diff --git a/leptonica/prog/weasel-8g.png b/leptonica/prog/weasel-8g.png
new file mode 100644
index 00000000..504d15bd
--- /dev/null
+++ b/leptonica/prog/weasel-8g.png
Binary files differ
diff --git a/leptonica/prog/weasel2.4c.bmp b/leptonica/prog/weasel2.4c.bmp
new file mode 100644
index 00000000..aa45a96f
--- /dev/null
+++ b/leptonica/prog/weasel2.4c.bmp
Binary files differ
diff --git a/leptonica/prog/weasel2.4c.png b/leptonica/prog/weasel2.4c.png
new file mode 100644
index 00000000..c7c1c440
--- /dev/null
+++ b/leptonica/prog/weasel2.4c.png
Binary files differ
diff --git a/leptonica/prog/weasel2.4g.png b/leptonica/prog/weasel2.4g.png
new file mode 100644
index 00000000..2ccbb370
--- /dev/null
+++ b/leptonica/prog/weasel2.4g.png
Binary files differ
diff --git a/leptonica/prog/weasel2.png b/leptonica/prog/weasel2.png
new file mode 100644
index 00000000..fc7c87dc
--- /dev/null
+++ b/leptonica/prog/weasel2.png
Binary files differ
diff --git a/leptonica/prog/weasel32.png b/leptonica/prog/weasel32.png
new file mode 100644
index 00000000..f4509d25
--- /dev/null
+++ b/leptonica/prog/weasel32.png
Binary files differ
diff --git a/leptonica/prog/weasel4.11c.png b/leptonica/prog/weasel4.11c.png
new file mode 100644
index 00000000..c8b23e15
--- /dev/null
+++ b/leptonica/prog/weasel4.11c.png
Binary files differ
diff --git a/leptonica/prog/weasel4.16c.png b/leptonica/prog/weasel4.16c.png
new file mode 100644
index 00000000..95d7ff28
--- /dev/null
+++ b/leptonica/prog/weasel4.16c.png
Binary files differ
diff --git a/leptonica/prog/weasel4.16g.png b/leptonica/prog/weasel4.16g.png
new file mode 100644
index 00000000..8310c464
--- /dev/null
+++ b/leptonica/prog/weasel4.16g.png
Binary files differ
diff --git a/leptonica/prog/weasel4.5g.png b/leptonica/prog/weasel4.5g.png
new file mode 100644
index 00000000..4689622d
--- /dev/null
+++ b/leptonica/prog/weasel4.5g.png
Binary files differ
diff --git a/leptonica/prog/weasel4.8g.png b/leptonica/prog/weasel4.8g.png
new file mode 100644
index 00000000..c13f4962
--- /dev/null
+++ b/leptonica/prog/weasel4.8g.png
Binary files differ
diff --git a/leptonica/prog/weasel4.png b/leptonica/prog/weasel4.png
new file mode 100644
index 00000000..d1261e6d
--- /dev/null
+++ b/leptonica/prog/weasel4.png
Binary files differ
diff --git a/leptonica/prog/weasel8.149g.png b/leptonica/prog/weasel8.149g.png
new file mode 100644
index 00000000..79ceaa57
--- /dev/null
+++ b/leptonica/prog/weasel8.149g.png
Binary files differ
diff --git a/leptonica/prog/weasel8.16g.nocmap.png b/leptonica/prog/weasel8.16g.nocmap.png
new file mode 100644
index 00000000..e6c2365d
--- /dev/null
+++ b/leptonica/prog/weasel8.16g.nocmap.png
Binary files differ
diff --git a/leptonica/prog/weasel8.16g.png b/leptonica/prog/weasel8.16g.png
new file mode 100644
index 00000000..f4bd13eb
--- /dev/null
+++ b/leptonica/prog/weasel8.16g.png
Binary files differ
diff --git a/leptonica/prog/weasel8.240c.png b/leptonica/prog/weasel8.240c.png
new file mode 100644
index 00000000..a1fed2c0
--- /dev/null
+++ b/leptonica/prog/weasel8.240c.png
Binary files differ
diff --git a/leptonica/prog/weasel8.5g.nocmap.png b/leptonica/prog/weasel8.5g.nocmap.png
new file mode 100644
index 00000000..03e61cad
--- /dev/null
+++ b/leptonica/prog/weasel8.5g.nocmap.png
Binary files differ
diff --git a/leptonica/prog/weasel8.5g.png b/leptonica/prog/weasel8.5g.png
new file mode 100644
index 00000000..145a84e1
--- /dev/null
+++ b/leptonica/prog/weasel8.5g.png
Binary files differ
diff --git a/leptonica/prog/weasel8.png b/leptonica/prog/weasel8.png
new file mode 100644
index 00000000..a01dfad6
--- /dev/null
+++ b/leptonica/prog/weasel8.png
Binary files differ
diff --git a/leptonica/prog/webpanimio_reg.c b/leptonica/prog/webpanimio_reg.c
new file mode 100644
index 00000000..63769c68
--- /dev/null
+++ b/leptonica/prog/webpanimio_reg.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * webpanimio_reg.c
+ *
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This is the Leptonica regression test for animated webp
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ *
+ * This tests writing animated webp files from a pixa of images.
+ *
+ * webp supports 32 bpp rgb and rgba.
+ * Lossy writing is slow; reading is fast, comparable to reading jpeg files.
+ * Lossless writing is extremely slow.
+ *
+ * Use webpinfo to inspect the contents of an animated webp file.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+l_int32 niters, duration;
+PIX *pix1, *pix2;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+#if !HAVE_LIBJPEG
+ lept_stderr("libjpeg is required for webpanimio_reg\n\n");
+ regTestCleanup(rp);
+ return 0;
+#endif /* abort */
+
+#if !HAVE_LIBWEBP || !HAVE_LIBWEBP_ANIM
+ lept_stderr("webp and webpanim are not enabled\n"
+ "See environ.h:\n"
+ " #define HAVE_LIBWEBP\n"
+ " #define HAVE_LIBWEBP_ANIM\n"
+ "See prog/Makefile:\n"
+ " link in -lwebp\n"
+ " link in -lwebpmux\n\n");
+ regTestCleanup(rp);
+ return 0;
+#endif /* abort */
+
+ lept_rmdir("lept/webpanim");
+ lept_mkdir("lept/webpanim");
+
+ niters = 5;
+ duration = 250; /* ms */
+ pix1 = pixRead("marge.jpg");
+ pix2 = pixRotate180(NULL, pix1);
+ pixa = pixaCreate(6);
+ pixaAddPix(pixa, pix1, L_COPY);
+ pixaAddPix(pixa, pix2, L_COPY);
+ pixaWriteWebPAnim("/tmp/lept/webpanim/margeanim.webp", pixa, niters,
+ duration, 80, 0);
+ regTestCheckFile(rp, "/tmp/lept/webpanim/margeanim.webp");
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ return regTestCleanup(rp);
+}
diff --git a/leptonica/prog/webpio_reg.c b/leptonica/prog/webpio_reg.c
new file mode 100644
index 00000000..43a583f4
--- /dev/null
+++ b/leptonica/prog/webpio_reg.c
@@ -0,0 +1,147 @@
+/*====================================================================*
+ - 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_reg.c
+ *
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This is the Leptonica regression test for lossy read/write
+ * I/O in webp format.
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ *
+ * This tests reading and writing of images in webp format.
+ *
+ * webp supports 32 bpp rgb and rgba.
+ * Lossy writing is slow; reading is fast, comparable to reading jpeg files.
+ * Lossless writing is extremely slow.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+#include <math.h>
+
+void DoWebpTest1(L_REGPARAMS *rp, const char *fname);
+void DoWebpTest2(L_REGPARAMS *rp, const char *fname, l_int32 quality,
+ l_int32 lossless, l_float32 expected, l_float32 delta);
+
+
+int main(int argc,
+ char **argv)
+{
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+#if !HAVE_LIBJPEG
+ lept_stderr("libjpeg is required for webpio_reg\n\n");
+ regTestCleanup(rp);
+ return 0;
+#endif /* abort */
+
+#if !HAVE_LIBWEBP
+ lept_stderr("webpio is not enabled\n"
+ "libwebp is required for webpio_reg\n"
+ "See environ.h: #define HAVE_LIBWEBP\n"
+ "See prog/Makefile: link in -lwebp\n\n");
+ regTestCleanup(rp);
+ return 0;
+#endif /* abort */
+
+ lept_rmdir("lept/webp");
+ lept_mkdir("lept/webp");
+
+ DoWebpTest1(rp, "weasel2.4c.png");
+ DoWebpTest1(rp, "weasel8.240c.png");
+ DoWebpTest1(rp, "karen8.jpg");
+ DoWebpTest1(rp, "test24.jpg");
+
+ DoWebpTest2(rp, "test24.jpg", 50, 0, 43.50, 1.0);
+ DoWebpTest2(rp, "test24.jpg", 75, 0, 46.07, 1.0);
+ DoWebpTest2(rp, "test24.jpg", 90, 0, 51.09, 2.0);
+ DoWebpTest2(rp, "test24.jpg", 100, 0, 54.979, 5.0);
+ DoWebpTest2(rp, "test24.jpg", 0, 1, 1000.0, 0.1);
+
+ return regTestCleanup(rp);
+}
+
+
+void DoWebpTest1(L_REGPARAMS *rp,
+ const char *fname)
+{
+char buf[256];
+PIX *pixs, *pix1, *pix2;
+
+ startTimer();
+ pixs = pixRead(fname);
+ lept_stderr("Time to read jpg: %7.3f\n", stopTimer());
+ startTimer();
+ snprintf(buf, sizeof(buf), "/tmp/lept/webp/webpio.%d.webp", rp->index + 1);
+ pixWrite(buf, pixs, IFF_WEBP);
+ lept_stderr("Time to write webp: %7.3f\n", stopTimer());
+ regTestCheckFile(rp, buf);
+ startTimer();
+ pix1 = pixRead(buf);
+ lept_stderr("Time to read webp: %7.3f\n", stopTimer());
+ pix2 = pixConvertTo32(pixs);
+ regTestCompareSimilarPix(rp, pix1, pix2, 20, 0.1, 0);
+ pixDisplayWithTitle(pix1, 100, 100, "pix1", rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return;
+}
+
+void DoWebpTest2(L_REGPARAMS *rp,
+ const char *fname,
+ l_int32 quality,
+ l_int32 lossless,
+ l_float32 expected,
+ l_float32 delta)
+{
+char buf[256];
+l_float32 psnr;
+PIX *pixs, *pix1;
+
+ pixs = pixRead(fname);
+ snprintf(buf, sizeof(buf), "/tmp/lept/webp/webpio.%d.webp", rp->index + 1);
+ if (lossless) startTimer();
+ pixWriteWebP("/tmp/lept/webp/junk.webp", pixs, quality, lossless);
+ if (lossless) lept_stderr("Lossless write: %7.3f sec\n", stopTimer());
+ pix1 = pixRead("/tmp/lept/webp/junk.webp");
+ pixGetPSNR(pixs, pix1, 4, &psnr);
+ if (lossless)
+ lept_stderr("lossless; psnr should be 1000: psnr = %7.3f\n", psnr);
+ else
+ lept_stderr("qual = %d, psnr = %7.3f\n", quality, psnr);
+ regTestCompareValues(rp, expected, psnr, delta);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ return;
+}
diff --git a/leptonica/prog/wet-day.jpg b/leptonica/prog/wet-day.jpg
new file mode 100644
index 00000000..b1d299b4
--- /dev/null
+++ b/leptonica/prog/wet-day.jpg
Binary files differ
diff --git a/leptonica/prog/witten.tif b/leptonica/prog/witten.tif
new file mode 100644
index 00000000..29685d94
--- /dev/null
+++ b/leptonica/prog/witten.tif
Binary files differ
diff --git a/leptonica/prog/wordboxes_reg.c b/leptonica/prog/wordboxes_reg.c
new file mode 100644
index 00000000..a75a2185
--- /dev/null
+++ b/leptonica/prog/wordboxes_reg.c
@@ -0,0 +1,288 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * wordboxes_reg.c
+ *
+ * This tests:
+ * - functions that make word boxes
+ * - the function that finds the nearest box to a given box in a boxa
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void MakeWordBoxes1(PIX *pixs, l_float32 scalefact, l_int32 thresh,
+ l_int32 index, L_REGPARAMS *rp);
+void MakeWordBoxes2(PIX *pixs, l_float32 scalefact, l_int32 thresh,
+ L_REGPARAMS *rp);
+void TestBoxaAdjacency(PIX *pixs, L_REGPARAMS *rp);
+
+#define DO_ALL 1
+
+int main(int argc,
+ char **argv)
+{
+BOX *box1, *box2;
+BOXA *boxa1;
+BOXAA *boxaa1;
+PIX *pix1, *pix2, *pix3, *pix4;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+#if DO_ALL
+ /* Make word boxes using pixWordMaskByDilation() */
+ pix1 = pixRead("lucasta.150.jpg");
+ MakeWordBoxes1(pix1, 1.0, 140, 0, rp); /* 0 */
+ MakeWordBoxes1(pix1, 0.6, 140, 1, rp); /* 1 */
+ pixDestroy(&pix1);
+#endif
+
+#if DO_ALL
+ pix1 = pixRead("zanotti-78.jpg");
+ MakeWordBoxes1(pix1, 1.0, 140, 2, rp); /* 2 */
+ MakeWordBoxes1(pix1, 0.6, 140, 3, rp); /* 3 */
+ pixDestroy(&pix1);
+#endif
+
+#if DO_ALL
+ pix1 = pixRead("words.15.tif");
+ MakeWordBoxes1(pix1, 1.0, 140, 4, rp); /* 4 */
+ MakeWordBoxes1(pix1, 0.6, 140, 5, rp); /* 5 */
+ pixDestroy(&pix1);
+#endif
+
+#if DO_ALL
+ pix1 = pixRead("words.44.tif");
+ MakeWordBoxes1(pix1, 1.0, 140, 6, rp); /* 6 */
+ MakeWordBoxes1(pix1, 0.6, 140, 7, rp); /* 7 */
+ pixDestroy(&pix1);
+#endif
+
+#if DO_ALL
+ /* Make word boxes using the higher-level functions
+ * pixGetWordsInTextlines() and pixGetWordBoxesInTextlines() */
+
+ pix1 = pixRead("lucasta.150.jpg");
+ MakeWordBoxes2(pix1, 0.7, 140, rp); /* 8, 9 */
+ pixDestroy(&pix1);
+#endif
+
+#if DO_ALL
+ pix1 = pixRead("zanotti-78.jpg");
+ MakeWordBoxes2(pix1, 0.7, 140, rp); /* 10, 11 */
+ pixDestroy(&pix1);
+#endif
+
+#if DO_ALL
+ /* Test boxa adjacency function */
+ pix1 = pixRead("lucasta.150.jpg");
+ TestBoxaAdjacency(pix1, rp); /* 12 - 15 */
+ pixDestroy(&pix1);
+#endif
+
+#if DO_ALL
+ /* Test word and character box finding */
+ pix1 = pixRead("zanotti-78.jpg");
+ box1 = boxCreate(0, 0, 1500, 700);
+ pix2 = pixClipRectangle(pix1, box1, NULL);
+ box2 = boxCreate(150, 130, 1500, 355);
+ pixFindWordAndCharacterBoxes(pix2, box2, 130, &boxa1, &boxaa1,
+ "/tmp/lept/testboxes");
+ pix3 = pixRead("/tmp/lept/testboxes/words.png");
+ pix4 = pixRead("/tmp/lept/testboxes/chars.png");
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 16 */
+ regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 17 */
+ pixDisplayWithTitle(pix3, 200, 1000, NULL, rp->display);
+ pixDisplayWithTitle(pix4, 200, 100, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ boxaDestroy(&boxa1);
+ boxaaDestroy(&boxaa1);
+#endif
+
+ return regTestCleanup(rp);
+}
+
+void
+MakeWordBoxes1(PIX *pixs,
+ l_float32 scalefact,
+ l_int32 thresh,
+ l_int32 index,
+ L_REGPARAMS *rp)
+{
+BOXA *boxa1, *boxa2;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXA *pixa1;
+
+ pix1 = pixScale(pixs, scalefact, scalefact);
+ pix2 = pixConvertTo1(pix1, thresh);
+ pixa1 = pixaCreate(3);
+ pixWordMaskByDilation(pix2, &pix3, NULL, pixa1);
+ pix4 = NULL;
+ if (pix3) {
+ boxa1 = pixConnComp(pix3, NULL, 8);
+ boxa2 = boxaTransform(boxa1, 0, 0, 1.0/scalefact, 1.0/scalefact);
+ pix4 = pixConvertTo32(pixs);
+ pixRenderBoxaArb(pix4, boxa2, 2, 255, 0, 0);
+ pix5 = pixaDisplayTiledInColumns(pixa1, 1, 1.0, 25, 2);
+ pixDisplayWithTitle(pix5, 200 * index, 0, NULL, rp->display);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix5);
+ }
+ regTestWritePixAndCheck(rp, pix4, IFF_JFIF_JPEG);
+ pixDisplayWithTitle(pix4, 200 * index, 800, NULL, rp->display);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix4);
+ pixaDestroy(&pixa1);
+}
+
+void
+MakeWordBoxes2(PIX *pixs,
+ l_float32 scalefact,
+ l_int32 thresh,
+ L_REGPARAMS *rp)
+{
+l_int32 default_minwidth = 10;
+l_int32 default_minheight = 10;
+l_int32 default_maxwidth = 400;
+l_int32 default_maxheight = 70;
+l_int32 minwidth, minheight, maxwidth, maxheight;
+BOXA *boxa1, *boxa2;
+NUMA *na;
+PIX *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa;
+
+ minwidth = scalefact * default_minwidth;
+ minheight = scalefact * default_minheight;
+ maxwidth = scalefact * default_maxwidth;
+ maxheight = scalefact * default_maxheight;
+
+ /* Get the word boxes */
+ pix1 = pixScale(pixs, scalefact, scalefact);
+ pix2 = pixConvertTo1(pix1, thresh);
+ pixGetWordsInTextlines(pix2, minwidth, minheight,
+ maxwidth, maxheight, &boxa1, &pixa, &na);
+ pixaDestroy(&pixa);
+ numaDestroy(&na);
+ boxa2 = boxaTransform(boxa1, 0, 0, 1.0 / scalefact, 1.0 / scalefact);
+ pix3 = pixConvertTo32(pixs);
+ pixRenderBoxaArb(pix3, boxa2, 2, 255, 0, 0);
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG);
+ pixDisplayWithTitle(pix3, 900, 0, NULL, rp->display);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+
+ /* Do it again with this interface. The result should be the same. */
+ pixGetWordBoxesInTextlines(pix2, minwidth, minheight,
+ maxwidth, maxheight, &boxa1, NULL);
+ boxa2 = boxaTransform(boxa1, 0, 0, 1.0 / scalefact, 1.0 / scalefact);
+ pix4 = pixConvertTo32(pixs);
+ pixRenderBoxaArb(pix4, boxa2, 2, 255, 0, 0);
+ if (regTestComparePix(rp, pix3, pix4)) {
+ L_ERROR("pix not the same", "MakeWordBoxes2");
+ pixDisplayWithTitle(pix4, 1200, 0, NULL, rp->display);
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+}
+
+void
+TestBoxaAdjacency(PIX *pixs,
+ L_REGPARAMS *rp)
+{
+l_int32 i, j, k, n;
+BOX *box1, *box2;
+BOXA *boxa0, *boxa1, *boxa2;
+PIX *pix0, *pix1, *pix2, *pix3;
+NUMAA *naai, *naad;
+
+ pix0 = pixConvertTo1(pixs, 140);
+
+ /* Make a word mask and remove small components */
+ pixWordMaskByDilation(pix0, &pix1, NULL, NULL);
+ boxa0 = pixConnComp(pix1, NULL, 8);
+ boxa1 = boxaSelectBySize(boxa0, 8, 8, L_SELECT_IF_BOTH,
+ L_SELECT_IF_GT, NULL);
+ pix2 = pixConvertTo32(pixs);
+ pixRenderBoxaArb(pix2, boxa1, 2, 255, 0, 0);
+ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG);
+ pixDisplayWithTitle(pix2, 600, 700, NULL, rp->display);
+ pixDestroy(&pix1);
+
+ /* Find the adjacent boxes and their distances */
+ boxaFindNearestBoxes(boxa1, L_NON_NEGATIVE, 0, &naai, &naad);
+ numaaWrite("/tmp/lept/regout/index.naa", naai);
+ regTestCheckFile(rp, "/tmp/lept/regout/index.naa");
+ numaaWrite("/tmp/lept/regout/dist.naa", naad);
+ regTestCheckFile(rp, "/tmp/lept/regout/dist.naa");
+
+ /* For a few boxes, show the (up to 4) adjacent boxes */
+ n = boxaGetCount(boxa1);
+ pix3 = pixConvertTo32(pixs);
+ for (i = 10; i < n; i += 25) {
+ box1 = boxaGetBox(boxa1, i, L_COPY);
+ pixRenderBoxArb(pix3, box1, 2, 255, 0, 0);
+ boxa2 = boxaCreate(4);
+ for (j = 0; j < 4; j++) {
+ numaaGetValue(naai, i, j, NULL, &k);
+ if (k >= 0) {
+ box2 = boxaGetBox(boxa1, k, L_COPY);
+ boxaAddBox(boxa2, box2, L_INSERT);
+ }
+ }
+ pixRenderBoxaArb(pix3, boxa2, 2, 0, 255, 0);
+ boxDestroy(&box1);
+ boxaDestroy(&boxa2);
+ }
+ regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG);
+ pixDisplayWithTitle(pix3, 1100, 700, NULL, rp->display);
+
+ pixDestroy(&pix0);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ boxaDestroy(&boxa0);
+ boxaDestroy(&boxa1);
+ numaaDestroy(&naai);
+ numaaDestroy(&naad);
+}
+
diff --git a/leptonica/prog/words.15.tif b/leptonica/prog/words.15.tif
new file mode 100644
index 00000000..6a3ed9bf
--- /dev/null
+++ b/leptonica/prog/words.15.tif
Binary files differ
diff --git a/leptonica/prog/words.44.tif b/leptonica/prog/words.44.tif
new file mode 100644
index 00000000..4e593543
--- /dev/null
+++ b/leptonica/prog/words.44.tif
Binary files differ
diff --git a/leptonica/prog/wordsinorder.c b/leptonica/prog/wordsinorder.c
new file mode 100644
index 00000000..14eb4d87
--- /dev/null
+++ b/leptonica/prog/wordsinorder.c
@@ -0,0 +1,145 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * wordsinorder.c
+ *
+ * wordsinorder dirin rootname [firstpage npages]
+ *
+ * dirin: directory of input pages
+ * rootname: used for naming the two output files (templates
+ * and c.c. data)
+ * firstpage: <optional> 0-based; default is 0
+ * npages: <optional> use 0 for all pages; default is 0
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Input variables */
+static const l_int32 MIN_WORD_WIDTH = 6;
+static const l_int32 MIN_WORD_HEIGHT = 4;
+static const l_int32 MAX_WORD_WIDTH = 500;
+static const l_int32 MAX_WORD_HEIGHT = 100;
+
+#define BUF_SIZE 512
+#define RENDER_PAGES 1
+
+int main(int argc,
+ char **argv)
+{
+char filename[BUF_SIZE];
+char *dirin, *rootname, *fname;
+l_int32 i, j, w, h, firstpage, npages, nfiles, ncomp;
+l_int32 index, ival, rval, gval, bval;
+BOX *box;
+BOXA *boxa;
+BOXAA *baa;
+NUMA *nai;
+NUMAA *naa;
+SARRAY *safiles;
+PIX *pixs, *pix1, *pix2, *pixd;
+PIXCMAP *cmap;
+static char mainName[] = "wordsinorder";
+
+ if (argc != 3 && argc != 5)
+ return ERROR_INT(
+ " Syntax: wordsinorder dirin rootname [firstpage, npages]",
+ mainName, 1);
+ dirin = argv[1];
+ rootname = argv[2];
+ if (argc == 3) {
+ firstpage = 0;
+ npages = 0;
+ }
+ else {
+ firstpage = atoi(argv[3]);
+ npages = atoi(argv[4]);
+ }
+ setLeptDebugOK(1);
+
+ /* Compute the word bounding boxes at 2x reduction, along with
+ * the textlines that they are in. */
+ safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages);
+ nfiles = sarrayGetCount(safiles);
+ baa = boxaaCreate(nfiles);
+ naa = numaaCreate(nfiles);
+ for (i = 0; i < nfiles; i++) {
+ fname = sarrayGetString(safiles, i, L_NOCOPY);
+ if ((pixs = pixRead(fname)) == NULL) {
+ L_WARNING("image file %d not read\n", mainName, i);
+ continue;
+ }
+ pix1 = pixReduceRankBinary2(pixs, 1, NULL);
+ pixGetWordBoxesInTextlines(pix1, MIN_WORD_WIDTH, MIN_WORD_HEIGHT,
+ MAX_WORD_WIDTH, MAX_WORD_HEIGHT,
+ &boxa, &nai);
+ boxaaAddBoxa(baa, boxa, L_INSERT);
+ numaaAddNuma(naa, nai, L_INSERT);
+ pixDestroy(&pix1);
+
+#if RENDER_PAGES
+ /* Show the results on a 2x reduced image, where each
+ * word is outlined and the color of the box depends on the
+ * computed textline. */
+ pix1 = pixReduceRankBinary2(pixs, 2, NULL);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ pixd = pixCreate(w, h, 8);
+ cmap = pixcmapCreateRandom(8, 1, 1); /* first color is black */
+ pixSetColormap(pixd, cmap);
+
+ pix2 = pixUnpackBinary(pix1, 8, 1);
+ pixRasterop(pixd, 0, 0, w, h, PIX_SRC | PIX_DST, pix2, 0, 0);
+ ncomp = boxaGetCount(boxa);
+ for (j = 0; j < ncomp; j++) {
+ box = boxaGetBox(boxa, j, L_CLONE);
+ numaGetIValue(nai, j, &ival);
+ index = 1 + (ival % 254); /* omit black and white */
+ pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+ pixRenderBoxArb(pixd, box, 2, rval, gval, bval);
+ boxDestroy(&box);
+ }
+
+ snprintf(filename, BUF_SIZE, "%s.%05d", rootname, i);
+ lept_stderr("filename: %s\n", filename);
+ pixWrite(filename, pixd, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+#endif /* RENDER_PAGES */
+ }
+
+ boxaaDestroy(&baa);
+ numaaDestroy(&naa);
+ sarrayDestroy(&safiles);
+ return 0;
+}
+
diff --git a/leptonica/prog/writemtiff.c b/leptonica/prog/writemtiff.c
new file mode 100644
index 00000000..32f7bae6
--- /dev/null
+++ b/leptonica/prog/writemtiff.c
@@ -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.
+ *====================================================================*/
+
+/*
+ * writemtiff.c
+ *
+ * Writes all matched files into a multipage tiff
+ *
+ * Usage:
+ * (1) To write all files in <dirin>:
+ * writemtiff <dirin> <fileout>
+ * (2) To write files in <dirin> matching a given <pattern>:
+ * writemtiff <dirin> <pattern> <fileout>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+int main(int argc,
+ char **argv)
+{
+static char mainName[] = "writemtiff";
+
+ if (argc != 3 && argc != 4)
+ return ERROR_INT(" Syntax: writemtiff dirin [pattern] fileout",
+ mainName, 1);
+
+ setLeptDebugOK(1);
+ if (argc == 3)
+ writeMultipageTiff(argv[1], NULL, argv[2]);
+ else /* argc == 4 */
+ writeMultipageTiff(argv[1], argv[2], argv[3]);
+ return 0;
+}
+
diff --git a/leptonica/prog/writetext_reg.c b/leptonica/prog/writetext_reg.c
new file mode 100644
index 00000000..ca75f2d8
--- /dev/null
+++ b/leptonica/prog/writetext_reg.c
@@ -0,0 +1,177 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * writetext_reg.c
+ *
+ * Regression test for writing a block of text in one of 4 locations
+ * relative to a pix. This tests writing on 8 different types of images.
+ * Output is written to /tmp/lept/regout/pixd[1,2,3,4].png
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void AddTextAndSave(PIXA *pixa, PIX *pixs, L_BMF *bmf, const char *textstr,
+ l_int32 location, l_uint32 val);
+
+const char *textstr[] =
+ {"This is a simple test of text writing: 8 bpp",
+ "This is a simple test of text writing: 32 bpp",
+ "This is a simple test of text writing: 8 bpp cmapped",
+ "This is a simple test of text writing: 4 bpp cmapped",
+ "This is a simple test of text writing: 4 bpp",
+ "This is a simple test of text writing: 2 bpp cmapped",
+ "This is a simple test of text writing: 2 bpp",
+ "This is a simple test of text writing: 1 bpp"};
+
+const char *topstr[] =
+ {"Text is added above each image",
+ "Text is added over the top of each image",
+ "Text is added over the bottom of each image",
+ "Text is added below each image"};
+
+const l_int32 loc[] = {1, 5, 6, 2};
+
+const l_uint32 colors[6] = {0x4090e000, 0x40e09000, 0x9040e000, 0x90e04000,
+ 0xe0409000, 0xe0904000};
+
+
+int main(int argc,
+ char **argv)
+{
+char buf[512];
+l_int32 i;
+L_BMF *bmf, *bmftop;
+PIX *pixs, *pixt, *pixd;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8;
+PIXA *pixa;
+L_REGPARAMS *rp;
+SARRAY *sa;
+
+#if !defined(HAVE_LIBPNG)
+ L_ERROR("This test requires libpng to run.\n", "writetext_reg");
+ exit(77);
+#endif
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ bmf = bmfCreate("./fonts", 6);
+ bmftop = bmfCreate("./fonts", 10);
+ pixs = pixRead("lucasta.047.jpg");
+ pix1 = pixScale(pixs, 0.4, 0.4); /* 8 bpp grayscale */
+ pix2 = pixConvertTo32(pix1); /* 32 bpp rgb */
+ pix3 = pixThresholdOn8bpp(pix1, 12, 1); /* 8 bpp cmapped */
+ pix4 = pixThresholdTo4bpp(pix1, 10, 1); /* 4 bpp cmapped */
+ pix5 = pixThresholdTo4bpp(pix1, 10, 0); /* 4 bpp not cmapped */
+ pix6 = pixThresholdTo2bpp(pix1, 3, 1); /* 2 bpp cmapped */
+ pix7 = pixThresholdTo2bpp(pix1, 3, 0); /* 2 bpp not cmapped */
+ pix8 = pixThresholdToBinary(pix1, 160); /* 1 bpp */
+
+ for (i = 0; i < 4; i++) {
+ pixa = pixaCreate(0);
+ AddTextAndSave(pixa, pix1, bmf, textstr[0], loc[i], 800);
+ AddTextAndSave(pixa, pix2, bmf, textstr[1], loc[i], 0xff000000);
+ AddTextAndSave(pixa, pix3, bmf, textstr[2], loc[i], 0x00ff0000);
+ AddTextAndSave(pixa, pix4, bmf, textstr[3], loc[i], 0x0000ff00);
+ AddTextAndSave(pixa, pix5, bmf, textstr[4], loc[i], 800);
+ AddTextAndSave(pixa, pix6, bmf, textstr[5], loc[i], 0xff000000);
+ AddTextAndSave(pixa, pix7, bmf, textstr[6], loc[i], 800);
+ AddTextAndSave(pixa, pix8, bmf, textstr[7], loc[i], 800);
+ pixt = pixaDisplayTiledInColumns(pixa, 4, 1.0, 30, 2);
+ pixd = pixAddSingleTextblock(pixt, bmftop, topstr[i],
+ 0xff00ff00, L_ADD_ABOVE, NULL);
+ regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 0 - 4 */
+ pixDisplayWithTitle(pixd, 50 * i, 50, NULL, rp->display);
+ pixDestroy(&pixt);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ }
+
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ bmfDestroy(&bmf);
+ bmfDestroy(&bmftop);
+
+ /* Write multiple lines in different colors, filling up
+ * the colormap and requesting even more colors. */
+ pixs = pixRead("weasel4.11c.png");
+ pix1 = pixConvertTo8(pixs, 0);
+ pix2 = pixScale(pixs, 8.0, 8.0);
+ pix3 = pixQuantFromCmap(pix2, pixGetColormap(pixs), 4, 5,
+ L_EUCLIDEAN_DISTANCE);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pix3, 0, 500, NULL, rp->display);
+ bmf = bmfCreate("fonts", 10);
+ sa = sarrayCreate(6);
+ for (i = 0; i < 6; i++) {
+ snprintf(buf, sizeof(buf), "This is textline %d\n", i);
+ sarrayAddString(sa, buf, L_COPY);
+ }
+ for (i = 0; i < 6; i++) {
+ pixSetTextline(pix3, bmf, sarrayGetString(sa, i, L_NOCOPY),
+ colors[i], 50, 120 + 60 * i, NULL, NULL);
+ }
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 6 */
+ pixDisplayWithTitle(pix3, 600, 500, NULL, rp->display);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ bmfDestroy(&bmf);
+ sarrayDestroy(&sa);
+ return regTestCleanup(rp);
+}
+
+
+void
+AddTextAndSave(PIXA *pixa,
+ PIX *pixs,
+ L_BMF *bmf,
+ const char *textstr,
+ l_int32 location,
+ l_uint32 val)
+{
+l_int32 n, newrow, ovf;
+PIX *pix1;
+
+ pix1 = pixAddSingleTextblock(pixs, bmf, textstr, val, location, &ovf);
+ n = pixaGetCount(pixa);
+ newrow = (n % 4) ? 0 : 1;
+ pixaAddPix(pixa, pix1, L_INSERT);
+ if (ovf) lept_stderr("Overflow writing text in image %d\n", n + 1);
+}
diff --git a/leptonica/prog/wyom.jpg b/leptonica/prog/wyom.jpg
new file mode 100644
index 00000000..9a06cadd
--- /dev/null
+++ b/leptonica/prog/wyom.jpg
Binary files differ
diff --git a/leptonica/prog/xformbox_reg.c b/leptonica/prog/xformbox_reg.c
new file mode 100644
index 00000000..8c92997b
--- /dev/null
+++ b/leptonica/prog/xformbox_reg.c
@@ -0,0 +1,325 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * xformbox_reg.c
+ *
+ * Tests ordered box transforms (rotation, scaling, translation).
+ * Also tests the various box hashing graphics operations.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Consts for second set */
+static const l_int32 SHIFTX_2 = 50;
+static const l_int32 SHIFTY_2 = 70;
+static const l_float32 SCALEX_2 = 1.17;
+static const l_float32 SCALEY_2 = 1.13;
+static const l_float32 ROTATION_2 = 0.10; /* radian */
+
+ /* Consts for third set */
+static const l_int32 SHIFTX_3 = 44;
+static const l_int32 SHIFTY_3 = 39;
+static const l_float32 SCALEX_3 = 0.83;
+static const l_float32 SCALEY_3 = 0.78;
+static const l_float32 ROTATION_3 = 0.11; /* radian */
+
+
+l_int32 RenderTransformedBoxa(PIX *pixt, BOXA *boxa, l_int32 i);
+
+
+int main(int argc,
+ char **argv)
+{
+l_int32 i, n, ws, hs, w, h, rval, gval, bval, order;
+l_float32 *mat1, *mat2, *mat3;
+l_float32 matd[9];
+BOX *box, *boxt;
+BOXA *boxa, *boxat, *boxa1, *boxa2, *boxa3, *boxa4, *boxa5;
+PIX *pix, *pixs, *pixc, *pixt, *pix1, *pix2, *pix3;
+PIXA *pixa;
+L_REGPARAMS *rp;
+
+ if (regTestSetup(argc, argv, &rp))
+ return 1;
+
+ /* ----------------------------------------------------------- *
+ * Test hash rendering in 3 modes *
+ * ----------------------------------------------------------- */
+ pixs = pixRead("feyn.tif");
+ box = boxCreate(461, 429, 1393, 342);
+ pix1 = pixClipRectangle(pixs, box, NULL);
+ boxa = pixConnComp(pix1, NULL, 8);
+ n = boxaGetCount(boxa);
+ pix2 = pixConvertTo8(pix1, 1);
+ pix3 = pixConvertTo32(pix1);
+ for (i = 0; i < n; i++) {
+ boxt = boxaGetBox(boxa, i, L_CLONE);
+ rval = (1413 * (i + 1)) % 256;
+ gval = (4917 * (i + 1)) % 256;
+ bval = (7341 * (i + 1)) % 256;
+ pixRenderHashBox(pix1, boxt, 8, 2, i % 4, 1, L_SET_PIXELS);
+ pixRenderHashBoxArb(pix2, boxt, 7, 2, i % 4, 1, rval, gval, bval);
+ pixRenderHashBoxBlend(pix3, boxt, 7, 2, i % 4, 1,
+ rval, gval, bval, 0.5);
+ boxDestroy(&boxt);
+ }
+ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */
+ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 2 */
+ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display);
+ pixDisplayWithTitle(pix2, 0, 300, NULL, rp->display);
+ pixDisplayWithTitle(pix3, 0, 570, NULL, rp->display);
+ boxaDestroy(&boxa);
+ boxDestroy(&box);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* ----------------------------------------------------------- *
+ * Test orthogonal box rotation and hash rendering *
+ * ----------------------------------------------------------- */
+ pixs = pixRead("feyn.tif");
+ box = boxCreate(461, 429, 1393, 342);
+ pix1 = pixClipRectangle(pixs, box, NULL);
+ pixc = pixConvertTo32(pix1);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ boxa1 = pixConnComp(pix1, NULL, 8);
+ pixa = pixaCreate(4);
+ for (i = 0; i < 4; i++) {
+ pix2 = pixRotateOrth(pixc, i);
+ boxa2 = boxaRotateOrth(boxa1, w, h, i);
+ rval = (1413 * (i + 4)) % 256;
+ gval = (4917 * (i + 4)) % 256;
+ bval = (7341 * (i + 4)) % 256;
+ pixRenderHashBoxaArb(pix2, boxa2, 10, 3, i, 1, rval, gval, bval);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ boxaDestroy(&boxa2);
+ }
+ pix3 = pixaDisplayTiledInRows(pixa, 32, 1200, 0.7, 0, 30, 3);
+ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3 */
+ pixDisplayWithTitle(pix3, 0, 800, NULL, rp->display);
+ boxDestroy(&box);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixDestroy(&pixc);
+ boxaDestroy(&boxa1);
+ pixaDestroy(&pixa);
+
+ /* ----------------------------------------------------------- *
+ * Test box transforms with either translation or scaling *
+ * combined with rotation, using the simple 'ordered' *
+ * function. Show that the order of the operations does *
+ * not matter; different hashing schemes end up in the *
+ * identical boxes. *
+ * ----------------------------------------------------------- */
+ pix = pixRead("feyn.tif");
+ box = boxCreate(420, 360, 1500, 465);
+ pixt = pixClipRectangle(pix, box, NULL);
+ pixs = pixAddBorderGeneral(pixt, 0, 200, 0, 0, 0);
+ boxDestroy(&box);
+ pixDestroy(&pix);
+ pixDestroy(&pixt);
+ boxa = pixConnComp(pixs, NULL, 8);
+ n = boxaGetCount(boxa);
+ pixa = pixaCreate(0);
+
+ pixt = pixConvertTo32(pixs);
+ for (i = 0; i < 3; i++) {
+ if (i == 0)
+ order = L_TR_SC_RO;
+ else if (i == 1)
+ order = L_TR_RO_SC;
+ else
+ order = L_SC_TR_RO;
+ boxat = boxaTransformOrdered(boxa, SHIFTX_2, SHIFTY_2, 1.0, 1.0,
+ 450, 250, ROTATION_2, order);
+ RenderTransformedBoxa(pixt, boxat, i);
+ boxaDestroy(&boxat);
+ }
+ pixaAddPix(pixa, pixt, L_INSERT);
+
+ pixt = pixConvertTo32(pixs);
+ for (i = 0; i < 3; i++) {
+ if (i == 0)
+ order = L_RO_TR_SC;
+ else if (i == 1)
+ order = L_RO_SC_TR;
+ else
+ order = L_SC_RO_TR;
+ boxat = boxaTransformOrdered(boxa, SHIFTX_2, SHIFTY_2, 1.0, 1.0,
+ 450, 250, ROTATION_2, order);
+ RenderTransformedBoxa(pixt, boxat, i + 4);
+ boxaDestroy(&boxat);
+ }
+ pixaAddPix(pixa, pixt, L_INSERT);
+
+ pixt = pixConvertTo32(pixs);
+ for (i = 0; i < 3; i++) {
+ if (i == 0)
+ order = L_TR_SC_RO;
+ else if (i == 1)
+ order = L_SC_RO_TR;
+ else
+ order = L_SC_TR_RO;
+ boxat = boxaTransformOrdered(boxa, 0, 0, SCALEX_2, SCALEY_2,
+ 450, 250, ROTATION_2, order);
+ RenderTransformedBoxa(pixt, boxat, i + 8);
+ boxaDestroy(&boxat);
+ }
+ pixaAddPix(pixa, pixt, L_INSERT);
+
+ pixt = pixConvertTo32(pixs);
+ for (i = 0; i < 3; i++) {
+ if (i == 0)
+ order = L_RO_TR_SC;
+ else if (i == 1)
+ order = L_RO_SC_TR;
+ else
+ order = L_TR_RO_SC;
+ boxat = boxaTransformOrdered(boxa, 0, 0, SCALEX_2, SCALEY_2,
+ 450, 250, ROTATION_2, order);
+ RenderTransformedBoxa(pixt, boxat, i + 12);
+ boxaDestroy(&boxat);
+ }
+ pixaAddPix(pixa, pixt, L_INSERT);
+
+ pixt = pixaDisplayTiledInColumns(pixa, 1, 0.5, 20, 0);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 4 */
+ pixDisplayWithTitle(pixt, 1000, 0, NULL, rp->display);
+ pixDestroy(&pixt);
+ pixDestroy(&pixs);
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa);
+
+
+ /* ----------------------------------------------------------- *
+ * Do more testing of box and pta transforms. Show that *
+ * resulting boxes are identical by three methods. *
+ * ----------------------------------------------------------- */
+ /* Set up pix and boxa */
+ pixa = pixaCreate(0);
+ pix = pixRead("lucasta.1.300.tif");
+ pixTranslate(pix, pix, 70, 0, L_BRING_IN_WHITE);
+ pixt = pixCloseBrick(NULL, pix, 14, 5);
+ pixOpenBrick(pixt, pixt, 1, 2);
+ boxa = pixConnComp(pixt, NULL, 8);
+ pixs = pixConvertTo32(pix);
+ pixc = pixCopy(NULL, pixs);
+ RenderTransformedBoxa(pixc, boxa, 113);
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixDestroy(&pix);
+ pixDestroy(&pixt);
+
+ /* (a) Do successive discrete operations: shift, scale, rotate */
+ pix1 = pixTranslate(NULL, pixs, SHIFTX_3, SHIFTY_3, L_BRING_IN_WHITE);
+ boxa1 = boxaTranslate(boxa, SHIFTX_3, SHIFTY_3);
+ pixc = pixCopy(NULL, pix1);
+ RenderTransformedBoxa(pixc, boxa1, 213);
+ pixaAddPix(pixa, pixc, L_INSERT);
+
+ pix2 = pixScale(pix1, SCALEX_3, SCALEY_3);
+ boxa2 = boxaScale(boxa1, SCALEX_3, SCALEY_3);
+ pixc = pixCopy(NULL, pix2);
+ RenderTransformedBoxa(pixc, boxa2, 313);
+ pixaAddPix(pixa, pixc, L_INSERT);
+
+ pixGetDimensions(pix2, &w, &h, NULL);
+ pix3 = pixRotateAM(pix2, ROTATION_3, L_BRING_IN_WHITE);
+ boxa3 = boxaRotate(boxa2, w / 2, h / 2, ROTATION_3);
+ pixc = pixCopy(NULL, pix3);
+ RenderTransformedBoxa(pixc, boxa3, 413);
+ pixaAddPix(pixa, pixc, L_INSERT);
+
+ /* (b) Set up and use the composite transform */
+ mat1 = createMatrix2dTranslate(SHIFTX_3, SHIFTY_3);
+ mat2 = createMatrix2dScale(SCALEX_3, SCALEY_3);
+ mat3 = createMatrix2dRotate(w / 2, h / 2, ROTATION_3);
+ l_productMat3(mat3, mat2, mat1, matd, 3);
+ boxa4 = boxaAffineTransform(boxa, matd);
+ pixc = pixCopy(NULL, pix3);
+ RenderTransformedBoxa(pixc, boxa4, 513);
+ pixaAddPix(pixa, pixc, L_INSERT);
+
+ /* (c) Use the special 'ordered' function */
+ pixGetDimensions(pixs, &ws, &hs, NULL);
+ boxa5 = boxaTransformOrdered(boxa, SHIFTX_3, SHIFTY_3,
+ SCALEX_3, SCALEY_3,
+ ws / 2, hs / 2, ROTATION_3, L_TR_SC_RO);
+ pixc = pixCopy(NULL, pix3);
+ RenderTransformedBoxa(pixc, boxa5, 613);
+ pixaAddPix(pixa, pixc, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+ boxaDestroy(&boxa4);
+ boxaDestroy(&boxa5);
+ lept_free(mat1);
+ lept_free(mat2);
+ lept_free(mat3);
+
+ pixt = pixaDisplayTiledInColumns(pixa, 2, 0.5, 20, 0);
+ regTestWritePixAndCheck(rp, pixt, IFF_PNG); /* 5 */
+ pixDisplayWithTitle(pixt, 1000, 300, NULL, rp->display);
+ pixDestroy(&pixt);
+ pixDestroy(&pixs);
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa);
+ return regTestCleanup(rp);
+}
+
+
+l_int32
+RenderTransformedBoxa(PIX *pixt,
+ BOXA *boxa,
+ l_int32 i)
+{
+l_int32 j, n, rval, gval, bval;
+BOX *box;
+
+ n = boxaGetCount(boxa);
+ rval = (1413 * i) % 256;
+ gval = (4917 * i) % 256;
+ bval = (7341 * i) % 256;
+ for (j = 0; j < n; j++) {
+ box = boxaGetBox(boxa, j, L_CLONE);
+ pixRenderHashBoxArb(pixt, box, 10, 3, i % 4, 1, rval, gval, bval);
+ boxDestroy(&box);
+ }
+ return 0;
+}
+
+
+
diff --git a/leptonica/prog/xtractprotos.c b/leptonica/prog/xtractprotos.c
new file mode 100644
index 00000000..9781b1c5
--- /dev/null
+++ b/leptonica/prog/xtractprotos.c
@@ -0,0 +1,263 @@
+/*====================================================================*
+ - 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.
+ *
+ * Note: this uses cpp to preprocess the input. (The name of the cpp
+ * tempfile is constructed below. It has a "." in the tail, which
+ * Cygwin needs to prevent it from appending ".exe" to the filename.)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+#define L_BUFSIZE 512 /* hardcoded below in sscanf() */
+static const char *version = "1.5";
+
+
+int main(int argc,
+ char **argv)
+{
+char *filein, *str, *tempfile, *prestring, *outprotos, *protostr;
+char buf[L_BUFSIZE];
+l_int32 i, maxindex, in_line, nflags, protos_added, firstfile, len, ret;
+size_t nbytes;
+L_BYTEA *ba, *ba2;
+SARRAY *sa;
+static char mainName[] = "xtractprotos";
+
+ if (argc == 1) {
+ lept_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;
+ }
+
+ setLeptDebugOK(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=%490s", buf);
+ if (ret != 1) {
+ lept_stderr("parse failure for prestring\n");
+ return 1;
+ }
+ if ((len = strlen(buf)) > L_BUFSIZE - 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=%490s", buf);
+ if (ret != 1) {
+ lept_stderr("parse failure for protos\n");
+ return 1;
+ }
+ outprotos = stringNew(buf);
+ if (!strncmp(outprotos, "inline", 7))
+ in_line = TRUE;
+ }
+ }
+ }
+
+ if (argc - nflags < 2) {
+ lept_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, "/*", L_COPY);
+ snprintf(buf, L_BUFSIZE,
+ " * These prototypes were autogen'd by xtractprotos, v. %s",
+ version);
+ sarrayAddString(sa, buf, L_COPY);
+ sarrayAddString(sa, " */", L_COPY);
+ sarrayAddString(sa, "#ifdef __cplusplus", L_COPY);
+ sarrayAddString(sa, "extern \"C\" {", L_COPY);
+ sarrayAddString(sa, "#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;
+ if ((tempfile = l_makeTempFilename()) == NULL) {
+ lept_stderr("failure to make a writeable temp file\n");
+ return 1;
+ }
+ for (i = firstfile; i < argc; i++) {
+ filein = argv[i];
+ len = strlen(filein);
+ if (filein[len - 1] == 'h') /* skip .h files */
+ continue;
+ snprintf(buf, L_BUFSIZE, "cpp -ansi -DNO_PROTOS %s %s",
+ filein, tempfile);
+ ret = system(buf); /* cpp */
+ if (ret) {
+ lept_stderr("cpp failure for %s; continuing\n", filein);
+ continue;
+ }
+
+ if ((str = parseForProtos(tempfile, prestring)) == NULL) {
+ lept_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);
+ }
+ lept_rmfile(tempfile);
+ lept_free(tempfile);
+
+ /* Lastly the extern C tail */
+ sa = sarrayCreate(0);
+ sarrayAddString(sa, "\n#ifdef __cplusplus", L_COPY);
+ sarrayAddString(sa, "}", L_COPY);
+ sarrayAddString(sa, "#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 */
+ lept_stderr("%s\n", protostr);
+ lept_free(protostr);
+ return 0;
+ }
+
+ /* If no protos were found, do nothing further */
+ if (!protos_added) {
+ lept_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/leptonica/prog/yuvtest.c b/leptonica/prog/yuvtest.c
new file mode 100644
index 00000000..da6ecaa1
--- /dev/null
+++ b/leptonica/prog/yuvtest.c
@@ -0,0 +1,221 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * yuvtest.c
+ *
+ * Test the yuv to rgb conversion.
+ *
+ * Note that the yuv gamut is greater than rgb, so although any
+ * rgb image can be converted to yuv (and back), any possible
+ * yuv value does not necessarily represent a valid rgb value.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+void AddTransformsRGB(PIXA *pixa, L_BMF *bmf, l_int32 gval);
+void AddTransformsYUV(PIXA *pixa, L_BMF *bmf, l_int32 yval);
+
+
+l_int32 main(int argc,
+ char **argv)
+{
+l_int32 i, rval, gval, bval, yval, uval, vval;
+l_float32 *a[3], b[3];
+L_BMF *bmf;
+PIX *pixd;
+PIXA *pixa;
+
+ setLeptDebugOK(1);
+ lept_mkdir("lept/yuv");
+
+ /* Explore the range of rgb --> yuv transforms. All rgb
+ * values transform to a valid value of yuv, so when transforming
+ * back we get the same rgb values that we started with. */
+ pixa = pixaCreate(0);
+ bmf = bmfCreate("fonts", 6);
+ for (gval = 0; gval <= 255; gval += 20)
+ AddTransformsRGB(pixa, bmf, gval);
+
+ pixd = pixaDisplayTiledAndScaled(pixa, 32, 755, 1, 0, 20, 2);
+ pixDisplay(pixd, 100, 0);
+ pixWrite("/tmp/lept/yuv/yuv1.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+
+ /* Now start with all "valid" yuv values, not all of which are
+ * related to a valid rgb value. Our yuv --> rgb transform
+ * clips the rgb components to [0 ... 255], so when transforming
+ * back we get different values whenever the initial yuv
+ * value is out of the rgb gamut. */
+ pixa = pixaCreate(0);
+ for (yval = 16; yval <= 235; yval += 16)
+ AddTransformsYUV(pixa, bmf, yval);
+
+ pixd = pixaDisplayTiledAndScaled(pixa, 32, 755, 1, 0, 20, 2);
+ pixDisplay(pixd, 600, 0);
+ pixWrite("/tmp/lept/yuv/yuv2.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ pixaDestroy(&pixa);
+ bmfDestroy(&bmf);
+
+
+ /* --------- Try out a special case by hand, and show that --------- *
+ * ------- the transform matrices we are using are inverses ---------*/
+
+ /* First, use our functions for the transform */
+ lept_stderr("Start with: yval = 143, uval = 79, vval = 103\n");
+ convertYUVToRGB(143, 79, 103, &rval, &gval, &bval);
+ lept_stderr(" ==> rval = %d, gval = %d, bval = %d\n", rval, gval, bval);
+ convertRGBToYUV(rval, gval, bval, &yval, &uval, &vval);
+ lept_stderr(" ==> yval = %d, uval = %d, vval = %d\n", yval, uval, vval);
+
+ /* Next, convert yuv --> rbg by solving for rgb --> yuv transform.
+ * [ a00 a01 a02 ] r = b0 (y - 16)
+ * [ a10 a11 a12 ] * g = b1 (u - 128)
+ * [ a20 a21 a22 ] b = b2 (v - 128)
+ */
+ b[0] = 143.0 - 16.0; /* y - 16 */
+ b[1] = 79.0 - 128.0; /* u - 128 */
+ b[2] = 103.0 - 128.0; /* v - 128 */
+ for (i = 0; i < 3; i++)
+ a[i] = (l_float32 *)lept_calloc(3, sizeof(l_float32));
+ a[0][0] = 65.738 / 256.0;
+ a[0][1] = 129.057 / 256.0;
+ a[0][2] = 25.064 / 256.0;
+ a[1][0] = -37.945 / 256.0;
+ a[1][1] = -74.494 / 256.0;
+ a[1][2] = 112.439 / 256.0;
+ a[2][0] = 112.439 / 256.0;
+ a[2][1] = -94.154 / 256.0;
+ a[2][2] = -18.285 / 256.0;
+ lept_stderr("Here's the original matrix: yuv --> rgb:\n");
+ for (i = 0; i < 3; i++)
+ lept_stderr(" %7.3f %7.3f %7.3f\n", 256.0 * a[i][0],
+ 256.0 * a[i][1], 256.0 * a[i][2]);
+ gaussjordan(a, b, 3);
+ lept_stderr("\nInput (yuv) = (143,79,103); solve for rgb:\n"
+ "rval = %7.3f, gval = %7.3f, bval = %7.3f\n", b[0], b[1], b[2]);
+ lept_stderr("Here's the inverse matrix: rgb --> yuv:\n");
+ for (i = 0; i < 3; i++)
+ lept_stderr(" %7.3f %7.3f %7.3f\n", 256.0 * a[i][0],
+ 256.0 * a[i][1], 256.0 * a[i][2]);
+
+ /* Now, convert back: rgb --> yuv;
+ * Do this by solving for yuv --> rgb transform.
+ * Use the b[] found previously (the rgb values), and
+ * the a[][] which now holds the rgb --> yuv transform. */
+ gaussjordan(a, b, 3);
+ lept_stderr("\nInput rgb; solve for yuv:\n"
+ "yval = %7.3f, uval = %7.3f, vval = %7.3f\n",
+ b[0] + 16.0, b[1] + 128.0, b[2] + 128.0);
+ lept_stderr("Inverting the matrix again: yuv --> rgb:\n");
+ for (i = 0; i < 3; i++)
+ lept_stderr(" %7.3f %7.3f %7.3f\n", 256.0 * a[i][0],
+ 256.0 * a[i][1], 256.0 * a[i][2]);
+
+ for (i = 0; i < 3; i++) lept_free(a[i]);
+ return 0;
+}
+
+
+void
+AddTransformsRGB(PIXA *pixa,
+ L_BMF *bmf,
+ l_int32 gval)
+{
+char textbuf[256];
+l_int32 i, j, wpls;
+l_uint32 *datas, *lines;
+PIX *pixs, *pixt1, *pixt2, *pixt3, *pixt4;
+PIXA *pixat;
+
+ pixs = pixCreate(255, 255, 32);
+ wpls = pixGetWpl(pixs);
+ datas = pixGetData(pixs);
+ for (i = 0; i < 255; i++) { /* r */
+ lines = datas + i * wpls;
+ for (j = 0; j < 255; j++) /* b */
+ composeRGBPixel(i, gval, j, lines + j);
+ }
+
+ pixat = pixaCreate(3);
+ pixaAddPix(pixat, pixs, L_INSERT);
+ pixt1 = pixConvertRGBToYUV(NULL, pixs);
+ pixaAddPix(pixat, pixt1, L_INSERT);
+ pixt2 = pixConvertYUVToRGB(NULL, pixt1);
+ pixaAddPix(pixat, pixt2, L_INSERT);
+ pixt3 = pixaDisplayTiledAndScaled(pixat, 32, 255, 3, 0, 20, 2);
+ snprintf(textbuf, sizeof(textbuf), "gval = %d", gval);
+ pixt4 = pixAddSingleTextblock(pixt3, bmf, textbuf, 0xff000000,
+ L_ADD_BELOW, NULL);
+ pixaAddPix(pixa, pixt4, L_INSERT);
+ pixDestroy(&pixt3);
+ pixaDestroy(&pixat);
+ return;
+}
+
+
+void
+AddTransformsYUV(PIXA *pixa,
+ L_BMF *bmf,
+ l_int32 yval)
+{
+char textbuf[256];
+l_int32 i, j, wpls;
+l_uint32 *datas, *lines;
+PIX *pixs, *pixt1, *pixt2, *pixt3, *pixt4;
+PIXA *pixat;
+
+ pixs = pixCreate(225, 225, 32);
+ wpls = pixGetWpl(pixs);
+ datas = pixGetData(pixs);
+ for (i = 0; i < 225; i++) { /* v */
+ lines = datas + i * wpls;
+ for (j = 0; j < 225; j++) /* u */
+ composeRGBPixel(yval + 16, j + 16, i + 16, lines + j);
+ }
+
+ pixat = pixaCreate(3);
+ pixaAddPix(pixat, pixs, L_INSERT);
+ pixt1 = pixConvertYUVToRGB(NULL, pixs);
+ pixaAddPix(pixat, pixt1, L_INSERT);
+ pixt2 = pixConvertRGBToYUV(NULL, pixt1);
+ pixaAddPix(pixat, pixt2, L_INSERT);
+ pixt3 = pixaDisplayTiledAndScaled(pixat, 32, 225, 3, 0, 20, 2);
+ snprintf(textbuf, sizeof(textbuf), "yval = %d", yval);
+ pixt4 = pixAddSingleTextblock(pixt3, bmf, textbuf, 0xff000000,
+ L_ADD_BELOW, NULL);
+ pixaAddPix(pixa, pixt4, L_INSERT);
+ pixDestroy(&pixt3);
+ pixaDestroy(&pixat);
+ return;
+}
+
diff --git a/leptonica/prog/zanotti-78.jpg b/leptonica/prog/zanotti-78.jpg
new file mode 100644
index 00000000..a2924414
--- /dev/null
+++ b/leptonica/prog/zanotti-78.jpg
Binary files differ
diff --git a/leptonica/prog/zier.jpg b/leptonica/prog/zier.jpg
new file mode 100644
index 00000000..bd00825e
--- /dev/null
+++ b/leptonica/prog/zier.jpg
Binary files differ
diff --git a/leptonica/src/CMakeLists.txt b/leptonica/src/CMakeLists.txt
new file mode 100644
index 00000000..1dca15d3
--- /dev/null
+++ b/leptonica/src/CMakeLists.txt
@@ -0,0 +1,99 @@
+#
+# leptonica
+#
+
+################################################################################
+
+########################################
+# SHARED LIBRARY leptonica
+########################################
+
+file(GLOB hdr "*.h")
+file(GLOB src "*.c")
+if (MSVC)
+ set_source_files_properties(${src} PROPERTIES LANGUAGE CXX)
+endif()
+
+string(REPLACE "-O3" "-O2" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
+
+add_library (leptonica ${src} ${hdr})
+set_target_properties (leptonica PROPERTIES VERSION ${VERSION_PLAIN})
+set_target_properties (leptonica PROPERTIES SOVERSION 5.4.0)
+if (WIN32)
+set_target_properties (leptonica PROPERTIES OUTPUT_NAME leptonica-${VERSION_PLAIN})
+set_target_properties (leptonica PROPERTIES DEBUG_OUTPUT_NAME leptonica-${VERSION_PLAIN}d)
+else()
+set_target_properties (leptonica PROPERTIES OUTPUT_NAME leptonica)
+set_target_properties (leptonica PROPERTIES DEBUG_OUTPUT_NAME leptonica)
+endif()
+
+if (BUILD_SHARED_LIBS)
+ target_compile_definitions (leptonica PRIVATE -DLIBLEPT_EXPORTS)
+endif()
+
+if (GIF_LIBRARIES)
+ target_include_directories (leptonica PUBLIC ${GIF_INCLUDE_DIR})
+ target_link_libraries (leptonica ${GIF_LIBRARIES})
+endif()
+if (JPEG_LIBRARIES)
+ target_include_directories (leptonica PUBLIC ${JPEG_INCLUDE_DIR})
+ target_link_libraries (leptonica ${JPEG_LIBRARIES})
+endif()
+if (JP2K_FOUND)
+ target_include_directories (leptonica PUBLIC ${JP2K_INCLUDE_DIRS})
+ target_link_libraries (leptonica ${JP2K_LIBRARIES})
+endif()
+if (PNG_LIBRARIES)
+ target_include_directories (leptonica PUBLIC ${PNG_INCLUDE_DIRS})
+ target_link_libraries (leptonica ${PNG_LIBRARIES})
+endif()
+if (TIFF_LIBRARIES)
+ target_include_directories (leptonica PUBLIC ${TIFF_INCLUDE_DIR})
+ target_link_libraries (leptonica ${TIFF_LIBRARIES})
+endif()
+if (WEBP_FOUND)
+ target_include_directories (leptonica PUBLIC ${WEBP_INCLUDE_DIRS})
+ target_link_libraries (leptonica ${WEBP_LIBRARIES})
+endif()
+if (ZLIB_LIBRARIES)
+ target_include_directories (leptonica PUBLIC ${ZLIB_INCLUDE_DIRS})
+ target_link_libraries (leptonica ${ZLIB_LIBRARIES})
+endif()
+
+if (UNIX)
+ target_link_libraries (leptonica m)
+ set(lib ${CMAKE_SHARED_LIBRARY_PREFIX})
+ set(dll ${CMAKE_SHARED_LIBRARY_SUFFIX})
+ set(old_target ${lib}lept${dll})
+ if (SYM_LINK)
+ add_custom_target(lept ALL
+ ${CMAKE_COMMAND} -E create_symlink "$<TARGET_FILE:leptonica>" ${old_target}
+ DEPENDS ${PROJECT_NAME}
+ COMMENT "Creating lept symlink")
+ install(FILES ${CMAKE_BINARY_DIR}/src/${old_target} DESTINATION lib)
+ endif(SYM_LINK)
+endif()
+
+if (NOT SW_BUILD)
+ export(TARGETS leptonica FILE ${CMAKE_BINARY_DIR}/LeptonicaTargets.cmake)
+else()
+ target_link_libraries (leptonica
+ org.sw.demo.gif
+ org.sw.demo.jpeg
+ org.sw.demo.glennrp.png
+ org.sw.demo.tiff
+ org.sw.demo.webmproject.webp
+ org.sw.demo.uclouvain.openjpeg.openjp2
+ )
+ file(WRITE ${CMAKE_BINARY_DIR}/LeptonicaTargets.cmake "include(${CMAKE_BINARY_DIR}/cppan.cmake)\n")
+ export(TARGETS leptonica APPEND FILE ${CMAKE_BINARY_DIR}/LeptonicaTargets.cmake)
+endif()
+
+install(TARGETS leptonica EXPORT LeptonicaTargets RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
+install(EXPORT LeptonicaTargets DESTINATION lib/cmake)
+install(FILES
+ ${hdr}
+ ${CMAKE_BINARY_DIR}/src/endianness.h
+ DESTINATION include/leptonica)
+
+################################################################################
diff --git a/leptonica/src/Makefile.am b/leptonica/src/Makefile.am
new file mode 100644
index 00000000..cb4679b3
--- /dev/null
+++ b/leptonica/src/Makefile.am
@@ -0,0 +1,102 @@
+AM_CPPFLAGS = $(ZLIB_CFLAGS) $(LIBPNG_CFLAGS) $(JPEG_CFLAGS) $(LIBTIFF_CFLAGS) $(LIBWEBP_CFLAGS) $(LIBWEBPMUX_CFLAGS) $(LIBJP2K_CFLAGS)
+
+lib_LTLIBRARIES = liblept.la
+liblept_la_LIBADD = $(LIBPNG_LIBS) $(JPEG_LIBS) $(GIFLIB_LIBS) $(LIBTIFF_LIBS) $(LIBWEBP_LIBS) $(LIBWEBPMUX_LIBS) $(LIBJP2K_LIBS) $(GDI_LIBS) $(LIBM) $(ZLIB_LIBS)
+
+liblept_la_LDFLAGS = -no-undefined -version-info 5:4: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 \
+ bootnumgen3.c bootnumgen4.c \
+ boxbasic.c boxfunc1.c boxfunc2.c boxfunc3.c \
+ boxfunc4.c boxfunc5.c bytearray.c \
+ ccbord.c ccthin.c checkerboard.c \
+ classapp.c colorcontent.c colorfill.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 dnafunc1.c dnahash.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 \
+ fmorphauto.c fmorphgen.1.c fmorphgenlow.1.c \
+ fpix1.c fpix2.c gifio.c gifiostub.c \
+ gplot.c graphics.c graymorph.c \
+ grayquant.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 partify.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 ptafunc2.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 roplow.c \
+ rotate.c rotateam.c rotateorth.c rotateshear.c \
+ runlength.c sarray1.c sarray2.c \
+ scale1.c scale2.c seedfill.c \
+ sel1.c sel2.c selgen.c \
+ shear.c skew.c spixio.c \
+ stack.c stringcode.c \
+ strokes.c sudoku.c textops.c \
+ tiffio.c tiffiostub.c \
+ utils1.c utils2.c warper.c watershed.c \
+ webpio.c webpiostub.c webpanimio.c webpanimiostub.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 colorfill.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 = hmttemplate1.txt hmttemplate2.txt \
+ leptonica-license.txt \
+ morphtemplate1.txt morphtemplate2.txt \
+ stringtemplate1.txt stringtemplate2.txt
+
+$(top_builddir)/prog/xtractprotos$(EXEEXT): liblept.la
+ $(MAKE) -C $(top_builddir)/prog xtractprotos$(EXEEXT)
+
+allheaders: $(top_builddir)/prog/xtractprotos$(EXEEXT) $(liblept_la_SOURCES)
+ cd $(srcdir) && $(abs_top_builddir)/prog/xtractprotos$(EXEEXT) -prestring=LEPT_DLL -protos=inline $(liblept_la_SOURCES)
+
+install-data-hook:
+ cd $(DESTDIR)$(libdir);\
+ for ext in a la so sl dylib; do\
+ if test -f liblept.$$ext; then\
+ $(LN_S) liblept.$$ext libleptonica.$$ext;\
+ fi;\
+ done
+
+uninstall-hook:
+ cd $(DESTDIR)$(libdir);\
+ for ext in a la so sl dylib; do\
+ rm -f libleptonica.$$ext;\
+ done
diff --git a/leptonica/src/adaptmap.c b/leptonica/src/adaptmap.c
new file mode 100644
index 00000000..c5c07162
--- /dev/null
+++ b/leptonica/src/adaptmap.c
@@ -0,0 +1,2948 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file adaptmap.c
+ * <pre>
+ *
+ * -------------------------------------------------------------------
+ *
+ * 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
+ * static l_int32 pixMinMaxTiles()
+ * static l_int32 pixSetLowContrast()
+ * static 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.
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Default input parameters for pixBackgroundNormSimple()
+ * Notes:
+ * (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 DefaultTileWidth = 10; /*!< default tile width */
+static const l_int32 DefaultTileHeight = 15; /*!< default tile height */
+static const l_int32 DefaultFgThreshold = 60; /*!< default fg threshold */
+static const l_int32 DefaultMinCount = 40; /*!< default minimum count */
+static const l_int32 DefaultBgVal = 200; /*!< default bg value */
+static const l_int32 DefaultXSmoothSize = 2; /*!< default x smooth size */
+static const l_int32 DefaultYSmoothSize = 1; /*!< default y smooth size */
+
+static l_int32 pixMinMaxTiles(PIX *pixs, l_int32 sx, l_int32 sy,
+ l_int32 mindiff, l_int32 smoothx, l_int32 smoothy,
+ PIX **ppixmin, PIX **ppixmax);
+static l_int32 pixSetLowContrast(PIX *pixs1, PIX *pixs2, l_int32 mindiff);
+static PIX *pixLinearTRCTiled(PIX *pixd, PIX *pixs, l_int32 sx, l_int32 sy,
+ PIX *pixmin, PIX *pixmax);
+static l_int32 *iaaGetLinearTRC(l_int32 **iaa, l_int32 diff);
+
+#ifndef NO_CONSOLE_IO
+#define DEBUG_GLOBAL 0 /*!< set to 1 to debug pixGlobalNormNoSatRGB() */
+#endif /* ~NO_CONSOLE_IO */
+
+/*------------------------------------------------------------------*
+ * Clean background to white using background normalization *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixCleanBackgroundToWhite()
+ *
+ * \param[in] pixs 8 bpp grayscale or 32 bpp rgb
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
+ * \param[in] pixg [optional] 8 bpp grayscale version; can be null
+ * \param[in] gamma gamma correction; must be > 0.0; typically ~1.0
+ * \param[in] blackval dark value to set to black (0)
+ * \param[in] whiteval light value to set to white (255)
+ * \return pixd 8 bpp or 32 bpp rgb, or NULL on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+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);
+ if (!pixd)
+ return (PIX *)ERROR_PTR("background norm failedd", procName, NULL);
+ pixGammaTRC(pixd, pixd, gamma, blackval, whiteval);
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Adaptive background normalization *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixBackgroundNormSimple()
+ *
+ * \param[in] pixs 8 bpp grayscale or 32 bpp rgb
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
+ * \param[in] pixg [optional] 8 bpp grayscale version; can be null
+ * \return pixd 8 bpp or 32 bpp rgb, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixBackgroundNormSimple(PIX *pixs,
+ PIX *pixim,
+ PIX *pixg)
+{
+ return pixBackgroundNorm(pixs, pixim, pixg,
+ DefaultTileWidth, DefaultTileHeight,
+ DefaultFgThreshold, DefaultMinCount,
+ DefaultBgVal, DefaultXSmoothSize,
+ DefaultYSmoothSize);
+}
+
+
+/*!
+ * \brief pixBackgroundNorm()
+ *
+ * \param[in] pixs 8 bpp grayscale or 32 bpp rgb
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
+ * \param[in] pixg [optional] 8 bpp grayscale version; can be null
+ * \param[in] sx, sy tile size in pixels
+ * \param[in] thresh threshold for determining foreground
+ * \param[in] mincount min threshold on counts in a tile
+ * \param[in] bgval target bg val; typ. > 128
+ * \param[in] smoothx half-width of block convolution kernel width
+ * \param[in] smoothy half-width of block convolution kernel height
+ * \return pixd 8 bpp or 32 bpp rgb, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ L_WARNING("pixmi not made; return a copy of source\n", procName);
+ pixDestroy(&pixm);
+ return pixCopy(NULL, pixs);
+ } 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) {
+ L_WARNING("not all pixm*i are made; return src copy\n", procName);
+ pixd = pixCopy(NULL, pixs);
+ } 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;
+}
+
+
+/*!
+ * \brief pixBackgroundNormMorph()
+ *
+ * \param[in] pixs 8 bpp grayscale or 32 bpp rgb
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
+ * \param[in] reduction at which morph closings are done; between 2 and 16
+ * \param[in] size of square Sel for the closing; use an odd number
+ * \param[in] bgval target bg val; typ. > 128
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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. *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief pixBackgroundNormGrayArray()
+ *
+ * \param[in] pixs 8 bpp grayscale
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
+ * \param[in] sx, sy tile size in pixels
+ * \param[in] thresh threshold for determining foreground
+ * \param[in] mincount min threshold on counts in a tile
+ * \param[in] bgval target bg val; typ. > 128
+ * \param[in] smoothx half-width of block convolution kernel width
+ * \param[in] smoothy half-width of block convolution kernel height
+ * \param[out] ppixd 16 bpp array of inverted background value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixBackgroundNormRGBArrays()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
+ * \param[in] pixg [optional] 8 bpp grayscale version; can be null
+ * \param[in] sx, sy tile size in pixels
+ * \param[in] thresh threshold for determining foreground
+ * \param[in] mincount min threshold on counts in a tile
+ * \param[in] bgval target bg val; typ. > 128
+ * \param[in] smoothx half-width of block convolution kernel width
+ * \param[in] smoothy half-width of block convolution kernel height
+ * \param[out] ppixr 16 bpp array of inverted R background value
+ * \param[out] ppixg 16 bpp array of inverted G background value
+ * \param[out] ppixb 16 bpp array of inverted B background value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixBackgroundNormGrayArrayMorph()
+ *
+ * \param[in] pixs 8 bpp grayscale
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
+ * \param[in] reduction at which morph closings are done; between 2 and 16
+ * \param[in] size of square Sel for the closing; use an odd number
+ * \param[in] bgval target bg val; typ. > 128
+ * \param[out] ppixd 16 bpp array of inverted background value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixBackgroundNormRGBArraysMorph()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
+ * \param[in] reduction at which morph closings are done; between 2 and 16
+ * \param[in] size of square Sel for the closing; use an odd number
+ * \param[in] bgval target bg val; typ. > 128
+ * \param[out] ppixr 16 bpp array of inverted R background value
+ * \param[out] ppixg 16 bpp array of inverted G background value
+ * \param[out] ppixb 16 bpp array of inverted B background value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixGetBackgroundGrayMap()
+ *
+ * \param[in] pixs 8 bpp grayscale; not cmapped
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null;
+ * it should not have only foreground pixels
+ * \param[in] sx, sy tile size in pixels
+ * \param[in] thresh threshold for determining foreground
+ * \param[in] mincount min threshold on counts in a tile
+ * \param[out] ppixd 8 bpp grayscale map
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+ if (!pixf)
+ return ERROR_INT("pixf not made", procName, 1);
+
+
+ /* ------------- 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;
+}
+
+
+/*!
+ * \brief pixGetBackgroundRGBMap()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null; it
+ * should not have all foreground pixels
+ * \param[in] pixg [optional] 8 bpp grayscale version; can be null
+ * \param[in] sx, sy tile size in pixels
+ * \param[in] thresh threshold for determining foreground
+ * \param[in] mincount min threshold on counts in a tile
+ * \param[out] ppixmr red component map
+ * \param[out] ppixmg green component map
+ * \param[out] ppixmb blue component map
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixGetBackgroundGrayMapMorph()
+ *
+ * \param[in] pixs 8 bpp grayscale; not cmapped
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null; it
+ * should not have all foreground pixels
+ * \param[in] reduction factor at which closing is performed
+ * \param[in] size of square Sel for the closing; use an odd number
+ * \param[out] ppixm grayscale map
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixGetBackgroundGrayMapMorph(PIX *pixs,
+ PIX *pixim,
+ l_int32 reduction,
+ l_int32 size,
+ PIX **ppixm)
+{
+l_int32 nx, ny, empty, fgpixels;
+l_float32 scale;
+PIX *pixm, *pix1, *pix2, *pix3, *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;
+ pix1 = pixScaleBySampling(pixs, scale, scale);
+ pix2 = pixCloseGray(pix1, size, size);
+ pix3 = pixExtendByReplication(pix2, 1, 1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* 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, pix3);
+ }
+ else
+ pixm = pixClone(pix3);
+ pixDestroy(&pix3);
+
+ /* 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);
+ pixDestroy(&pixims);
+ 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;
+}
+
+
+/*!
+ * \brief pixGetBackgroundRGBMapMorph()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null; it
+ * should not have all foreground pixels
+ * \param[in] reduction factor at which closing is performed
+ * \param[in] size of square Sel for the closing; use an odd number
+ * \param[out] ppixmr red component map
+ * \param[out] ppixmg green component map
+ * \param[out] ppixmb blue component map
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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, *pix1, *pix2, *pix3, *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);
+
+ /* 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;
+ }
+
+ /* Generate an 8 bpp version of the image mask, if it exists */
+ scale = 1. / (l_float32)reduction;
+ pixims = NULL;
+ pixm = NULL;
+ if (pixim) {
+ pixims = pixScale(pixim, scale, scale);
+ pixm = pixConvertTo8(pixims, FALSE);
+ }
+
+ /* 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. */
+ pix1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_RED);
+ pix2 = pixCloseGray(pix1, size, size);
+ pix3 = pixExtendByReplication(pix2, 1, 1);
+ if (pixim)
+ pixmr = pixAnd(NULL, pixm, pix3);
+ else
+ pixmr = pixClone(pix3);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ pix1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_GREEN);
+ pix2 = pixCloseGray(pix1, size, size);
+ pix3 = pixExtendByReplication(pix2, 1, 1);
+ if (pixim)
+ pixmg = pixAnd(NULL, pixm, pix3);
+ else
+ pixmg = pixClone(pix3);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ pix1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_BLUE);
+ pix2 = pixCloseGray(pix1, size, size);
+ pix3 = pixExtendByReplication(pix2, 1, 1);
+ if (pixim)
+ pixmb = pixAnd(NULL, pixm, pix3);
+ else
+ pixmb = pixClone(pix3);
+ pixDestroy(&pixm);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* 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);
+ pixDestroy(&pixims);
+ 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;
+}
+
+
+/*!
+ * \brief pixFillMapHoles()
+ *
+ * \param[in] pix 8 bpp; a map, with one pixel for each tile in
+ * a larger image
+ * \param[in] nx number of horizontal pixel tiles that are entirely
+ * covered with pixels in the original source image
+ * \param[in] ny ditto for the number of vertical pixel tiles
+ * \param[in] filltype L_FILL_WHITE or L_FILL_BLACK
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 */
+
+ 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;
+ }
+ }
+ }
+
+ 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 */
+ /* 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, pix, j + 1, 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, pix, j - 1, 0);
+ }
+ }
+ }
+ if (w > nx) { /* replicate the last column */
+ pixRasterop(pix, w - 1, 0, 1, h, PIX_SRC, pix, w - 2, 0);
+ }
+
+ numaDestroy(&na);
+ return 0;
+}
+
+
+/*!
+ * \brief pixExtendByReplication()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] addw number of extra pixels horizontally to add
+ * \param[in] addh number of extra pixels vertically to add
+ * \return pixd extended with replicated pixel values, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The pixel values are extended to the left and down, as required.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixSmoothConnectedRegions()
+ *
+ * \param[in] pixs 8 bpp grayscale; no colormap
+ * \param[in] pixm [optional] 1 bpp; if null, this is a no-op
+ * \param[in] factor subsampling factor for getting average; >= 1
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 */
+
+/*!
+ * \brief pixGetForegroundGrayMap()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
+ * \param[in] sx, sy src tile size, in pixels
+ * \param[in] thresh threshold for determining foreground
+ * \param[out] ppixd 8 bpp grayscale map
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixGetInvBackgroundMap()
+ *
+ * \param[in] pixs 8 bpp grayscale; no colormap
+ * \param[in] bgval target bg val; typ. > 128
+ * \param[in] smoothx half-width of block convolution kernel width
+ * \param[in] smoothy half-width of block convolution kernel height
+ * \return pixd 16 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) bgval should typically be > 120 and < 240
+ * (2) pixd is a normalization image; the original image is
+ * multiplied by pixd and the result is divided by 256.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixApplyInvBackgroundGrayMap()
+ *
+ * \param[in] pixs 8 bpp grayscale; no colormap
+ * \param[in] pixm 16 bpp, inverse background map
+ * \param[in] sx tile width in pixels
+ * \param[in] 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);
+ if ((pixd = pixCreateTemplate(pixs)) == NULL)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ 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;
+}
+
+
+/*!
+ * \brief pixApplyInvBackgroundRGBMap()
+ *
+ * \param[in] pixs 32 bpp rbg
+ * \param[in] pixmr 16 bpp, red inverse background map
+ * \param[in] pixmg 16 bpp, green inverse background map
+ * \param[in] pixmb 16 bpp, blue inverse background map
+ * \param[in] sx tile width in pixels
+ * \param[in] 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);
+ if ((pixd = pixCreateTemplate(pixs)) == NULL)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixApplyVariableGrayMap()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] pixg 8 bpp, variable map
+ * \param[in] target typ. 128 for threshold
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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));
+ }
+ }
+ }
+
+ if ((pixd = pixCreateNoInit(w, h, 8)) == NULL) {
+ LEPT_FREE(lut);
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ }
+ 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);
+ }
+ }
+ }
+
+ LEPT_FREE(lut);
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Non-adaptive (global) mapping *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixGlobalNormRGB()
+ *
+ * \param[in] pixd [optional] null, existing or equal to pixs
+ * \param[in] pixs 32 bpp rgb, or colormapped
+ * \param[in] rval, gval, bval pixel values in pixs that are
+ * linearly mapped to mapval
+ * \param[in] mapval use 255 for mapping to white
+ * \return pixd 32 bpp rgb or colormapped, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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));
+
+ /* Extract copies of the internal arrays */
+ rarray = numaGetIArray(nar);
+ garray = numaGetIArray(nag);
+ barray = numaGetIArray(nab);
+ if (!nar || !nag || !nab || !rarray || !garray || !barray) {
+ L_ERROR("allocation failure in arrays\n", procName);
+ goto cleanup_arrays;
+ }
+
+ 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);
+ }
+ }
+ }
+
+cleanup_arrays:
+ numaDestroy(&nar);
+ numaDestroy(&nag);
+ numaDestroy(&nab);
+ LEPT_FREE(rarray);
+ LEPT_FREE(garray);
+ LEPT_FREE(barray);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixGlobalNormNoSatRGB()
+ *
+ * \param[in] pixd [optional] null, existing or equal to pixs
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] rval, gval, bval pixel values in pixs that are
+ * linearly mapped to mapval; but see below
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[in] rank between 0.0 and 1.0; typ. use a value near 1.0
+ * \return pixd 32 bpp rgb, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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
+ lept_stderr("rankrval = %7.2f, rankgval = %7.2f, rankbval = %7.2f\n",
+ rankrval, rankgval, rankbval);
+ lept_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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixThresholdSpreadNorm()
+ *
+ * \param[in] pixs 8 bpp grayscale; not colormapped
+ * \param[in] filtertype L_SOBEL_EDGE or L_TWO_SIDED_EDGE;
+ * \param[in] edgethresh threshold on magnitude of edge filter;
+ * typ 10-20
+ * \param[in] smoothx, smoothy half-width of convolution kernel applied to
+ * spread threshold: use 0 for no smoothing
+ * \param[in] gamma gamma correction; typ. about 0.7
+ * \param[in] minval input value that gives 0 for output; typ. -25
+ * \param[in] maxval input value that gives 255 for output;
+ * typ. 255
+ * \param[in] targetthresh target threshold for normalization
+ * \param[out] ppixth [optional] computed local threshold value
+ * \param[out] ppixb [optional] thresholded normalized image
+ * \param[out] ppixd [optional] normalized image
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixBackgroundNormFlex()
+ *
+ * \param[in] pixs 8 bpp grayscale; not colormapped
+ * \param[in] sx, sy desired tile dimensions; size may vary;
+ * use values between 3 and 10
+ * \param[in] smoothx, smoothy half-width of convolution kernel applied to
+ * threshold array: use values between 1 and 3
+ * \param[in] delta difference parameter in basin filling;
+ * use 0 to skip
+ * \return pixd 8 bpp, background-normalized), or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixContrastNorm()
+ *
+ * \param[in] pixd [optional] 8 bpp; null or equal to pixs
+ * \param[in] pixs 8 bpp grayscale; not colormapped
+ * \param[in] sx, sy tile dimensions
+ * \param[in] mindiff minimum difference to accept as valid
+ * \param[in] smoothx, smoothy half-width of convolution kernel applied to
+ * min and max arrays: use 0 for no smoothing
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixMinMaxTiles()
+ *
+ * \param[in] pixs 8 bpp grayscale; not colormapped
+ * \param[in] sx, sy tile dimensions
+ * \param[in] mindiff minimum difference to accept as valid
+ * \param[in] smoothx, smoothy half-width of convolution kernel applied to
+ * min and max arrays: use 0 for no smoothing
+ * \param[out] ppixmin tiled minima
+ * \param[out] ppixmax tiled maxima
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSetLowContrast()
+ *
+ * \param[in] pixs1 8 bpp
+ * \param[in] pixs2 8 bpp
+ * \param[in] mindiff minimum difference to accept as valid
+ * \return 0 if OK; 1 if no pixel diffs are large enough, or on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static l_ok
+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;
+}
+
+
+/*!
+ * \brief pixLinearTRCTiled()
+ *
+ * \param[in] pixd [optional] 8 bpp
+ * \param[in] pixs 8 bpp, not colormapped
+ * \param[in] sx, sy tile dimensions
+ * \param[in] pixmin pix of min values in tiles
+ * \param[in] pixmax pix of max values in tiles
+ * \return pixd always
+ *
+ * <pre>
+ * 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[].
+ * </pre>
+ */
+static 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);
+
+ if ((iaa = (l_int32 **)LEPT_CALLOC(256, sizeof(l_int32 *))) == NULL)
+ return (PIX *)ERROR_PTR("iaa not made", procName, NULL);
+ if ((pixd = pixCopy(pixd, pixs)) == NULL) {
+ LEPT_FREE(iaa);
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ }
+ 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) {
+ L_ERROR("shouldn't happen! i,j = %d,%d, minval = %d\n",
+ procName, i, j, minval);
+ continue;
+ }
+ if ((ia = iaaGetLinearTRC(iaa, maxval - minval)) == NULL) {
+ L_ERROR("failure to make ia for j = %d!\n", procName, j);
+ continue;
+ }
+ 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++)
+ LEPT_FREE(iaa[i]);
+ LEPT_FREE(iaa);
+ return pixd;
+}
+
+
+/*!
+ * \brief iaaGetLinearTRC()
+ *
+ * \param[in] iaa bare array of ptrs to l_int32
+ * \param[in] 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/leptonica/src/affine.c b/leptonica/src/affine.c
new file mode 100644
index 00000000..5c0214e4
--- /dev/null
+++ b/leptonica/src/affine.c
@@ -0,0 +1,1624 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file affine.c
+ * <pre>
+ *
+ * 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 transforming 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixAffineSampledPta()
+ *
+ * \param[in] pixs all depths
+ * \param[in] ptad 3 pts of final coordinate space
+ * \param[in] ptas 3 pts of initial coordinate space
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixAffineSampled()
+ *
+ * \param[in] pixs all depths
+ * \param[in] vc vector of 6 coefficients for affine transformation
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixAffinePta()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] ptad 3 pts of final coordinate space
+ * \param[in] ptas 3 pts of initial coordinate space
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Brings in either black or white pixels from the boundary
+ * (2) Removes any existing colormap, if necessary, before transforming
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixAffine()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] vc vector of 6 coefficients for affine transformation
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Brings in either black or white pixels from the boundary
+ * (2) Removes any existing colormap, if necessary, before transforming
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixAffinePtaColor()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] ptad 3 pts of final coordinate space
+ * \param[in] ptas 3 pts of initial coordinate space
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixAffineColor()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] vc vector of 6 coefficients for affine transformation
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixAffinePtaGray()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] ptad 3 pts of final coordinate space
+ * \param[in] ptas 3 pts of initial coordinate space
+ * \param[in] grayval e.g.: 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;
+}
+
+
+
+/*!
+ * \brief pixAffineGray()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] vc vector of 6 coefficients for affine transformation
+ * \param[in] grayval e.g.: 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixAffinePtaWithAlpha()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] ptad 3 pts of final coordinate space
+ * \param[in] ptas 3 pts of initial coordinate space
+ * \param[in] pixg [optional] 8 bpp, can be null
+ * \param[in] fract between 0.0 and 1.0, with 0.0 fully transparent
+ * and 1.0 fully opaque
+ * \param[in] border of pixels added to capture transformed source pixels
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief getAffineXformCoeffs()
+ *
+ * \param[in] ptas source 3 points; unprimed
+ * \param[in] ptad transformed 3 points; primed
+ * \param[out] pvc vector of coefficients of transform
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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':
+ * \code
+ * boxad = boxaAffineTransform(boxas, mat);
+ * affineInvertXform(mat, &matinv);
+ * pixd = pixAffine(pixs, matinv, L_BRING_IN_WHITE);
+ * \endcode
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief affineInvertXform()
+ *
+ * \param[in] vc vector of 6 coefficients
+ * \param[out] pvci inverted transform
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ *
+ * </pre>
+ */
+l_ok
+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);
+
+#if 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); /* this inverts matrix a */
+ 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];
+ for (i = 0; i < 3; i++)
+ LEPT_FREE(a[i]);
+
+#else
+
+ /* Alternative version, inverting a 2x2 matrix */
+ { l_float32 *a2[2];
+ for (i = 0; i < 2; i++)
+ a2[i] = (l_float32 *)LEPT_CALLOC(2, sizeof(l_float32));
+ a2[0][0] = vc[0];
+ a2[0][1] = vc[1];
+ a2[1][0] = vc[3];
+ a2[1][1] = vc[4];
+ b[0] = vc[2];
+ b[1] = vc[5];
+ gaussjordan(a2, b, 2); /* this inverts matrix a2 */
+ vci = (l_float32 *)LEPT_CALLOC(6, sizeof(l_float32));
+ *pvci = vci;
+ vci[0] = a2[0][0];
+ vci[1] = a2[0][1];
+ vci[2] = -b[0]; /* note sign */
+ vci[3] = a2[1][0];
+ vci[4] = a2[1][1];
+ vci[5] = -b[1]; /* note sign */
+ for (i = 0; i < 2; i++)
+ LEPT_FREE(a2[i]);
+ }
+#endif
+
+ return 0;
+}
+
+
+/*!
+ * \brief affineXformSampledPt()
+ *
+ * \param[in] vc vector of 6 coefficients
+ * \param[in] x, y initial point
+ * \param[out] pxp, pyp transformed point
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds the nearest pixel coordinates of the transformed point.
+ * (2) It does not check ptrs for returned data!
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief affineXformPt()
+ *
+ * \param[in] vc vector of 6 coefficients
+ * \param[in] x, y initial point
+ * \param[out] pxp, pyp transformed point
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This computes the floating point location of the transformed point.
+ * (2) It does not check ptrs for returned data!
+ * </pre>
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief linearInterpolatePixelColor()
+ *
+ * \param[in] datas ptr to beginning of image data
+ * \param[in] wpls 32-bit word/line for this data array
+ * \param[in] w, h of image
+ * \param[in] x, y floating pt location for evaluation
+ * \param[in] colorval color brought in from the outside when the
+ * input x,y location is outside the image;
+ * in 0xrrggbb00 format)
+ * \param[out] pval interpolated color value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 valid, 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 x or y are invalid. (x,y) must be in the source image.
+ * Failure to detect an invalid point will cause a mem address fault.
+ * Occasionally, x or y will be a nan, and relational checks always
+ * fail for nans. Therefore we check if the point is inside the pix */
+ valid = (x >= 0.0 && y >= 0.0 && x < w && y < h);
+ if (!valid) 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)
+ lept_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;
+ composeRGBPixel(rval, gval, bval, pval);
+ return 0;
+}
+
+
+/*!
+ * \brief linearInterpolatePixelGray()
+ *
+ * \param[in] datas ptr to beginning of image data
+ * \param[in] wpls 32-bit word/line for this data array
+ * \param[in] w, h of image
+ * \param[in] x, y floating pt location for evaluation
+ * \param[in] grayval color brought in from the outside when the
+ * input x,y location is outside the image
+ * \param[out] pval interpolated gray value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 valid, 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 x or y is invalid. (x,y) must be in the source image.
+ * Failure to detect an invalid point will cause a mem address fault.
+ * Occasionally, x or y will be a nan, and relational checks always
+ * fail for nans. Therefore we check if the point is inside the pix */
+ valid = (x >= 0.0 && y >= 0.0 && x < w && y < h);
+ if (!valid) 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)
+ lept_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;}
+
+/*!
+ * \brief gaussjordan()
+ *
+ * \param[in] a n x n matrix
+ * \param[in] b n x 1 right-hand side column vector
+ * \param[in] n dimension
+ * \return 0 if ok, 1 on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+l_int32
+gaussjordan(l_float32 **a,
+ l_float32 *b,
+ l_int32 n)
+{
+l_int32 i, icol, irow, j, k, col, row, success;
+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);
+
+ success = TRUE;
+ indexc = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32));
+ indexr = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32));
+ ipiv = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32));
+ if (!indexc || !indexr || !ipiv) {
+ L_ERROR("array not made\n", procName);
+ success = FALSE;
+ goto cleanup_arrays;
+ }
+
+ icol = irow = 0; /* silence static checker */
+ 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) {
+ L_ERROR("singular matrix\n", procName);
+ success = FALSE;
+ goto cleanup_arrays;
+ }
+ }
+ }
+ }
+ ++(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) {
+ L_ERROR("singular matrix\n", procName);
+ success = FALSE;
+ goto cleanup_arrays;
+ }
+ 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]]);
+ }
+ }
+
+cleanup_arrays:
+ LEPT_FREE(indexr);
+ LEPT_FREE(indexc);
+ LEPT_FREE(ipiv);
+ return (success) ? 0 : 1;
+}
+
+
+/*-------------------------------------------------------------*
+ * Sequential affine image transformation *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixAffineSequential()
+ *
+ * \param[in] pixs
+ * \param[in] ptad 3 pts of final coordinate space
+ * \param[in] ptas 3 pts of initial coordinate space
+ * \param[in] bw pixels of additional border width during computation
+ * \param[in] bh pixels of additional border height during computation
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+#if DEBUG
+l_float32 rad2deg;
+#endif /* DEBUG */
+PIX *pix1, *pix2, *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);
+
+ pix1 = pix2 = pixd = NULL;
+
+ 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 ((pix1 = pixAddBorderGeneral(pixs, bw, bw, bh, bh, 0)) == NULL)
+ return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+ } else {
+ pix1 = 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) {
+ L_ERROR("x2s == x1!\n", procName);
+ goto cleanup_pix;
+ }
+ 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) {
+ L_ERROR("x2sp == x1p!\n", procName);
+ goto cleanup_pix;
+ }
+ 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(pix1, y1, th3, L_BRING_IN_WHITE);
+ pixVShearIP(pix1, 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 ((pix2 = pixScale(pix1, scalex, scaley)) == NULL) {
+ L_ERROR("pix2 not made\n", procName);
+ goto cleanup_pix;
+ }
+
+#if DEBUG
+ rad2deg = 180. / 3.1415926535;
+ lept_stderr("th3 = %5.1f deg, ph2 = %5.1f deg\n",
+ rad2deg * th3, rad2deg * ph2);
+ lept_stderr("th3' = %5.1f deg, ph2' = %5.1f deg\n",
+ rad2deg * th3p, rad2deg * ph2p);
+ lept_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(pix2, 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(pix2, x1p, -ph2p, L_BRING_IN_WHITE);
+ pixHShearIP(pix2, y1p, -th3p, L_BRING_IN_WHITE);
+
+ if (bw != 0 || bh != 0) {
+ if ((pixd = pixRemoveBorderGeneral(pix2, bw, bw, bh, bh)) == NULL)
+ L_ERROR("pixd not made\n", procName);
+ } else {
+ pixd = pixClone(pix2);
+ }
+
+cleanup_pix:
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return pixd;
+}
diff --git a/leptonica/src/affinecompose.c b/leptonica/src/affinecompose.c
new file mode 100644
index 00000000..8f4805b1
--- /dev/null
+++ b/leptonica/src/affinecompose.c
@@ -0,0 +1,665 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file affinecompose.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+/*-------------------------------------------------------------*
+ * Composable coordinate transforms *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief createMatrix2dTranslate()
+ *
+ * \param[in] transx x component of translation wrt. the origin
+ * \param[in] transy y component of translation wrt. the origin
+ * \return 3x3 transform matrix, or NULL on error
+ *
+ * <pre>
+ * 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 translation 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.
+ * </pre>
+ */
+l_float32 *
+createMatrix2dTranslate(l_float32 transx,
+ l_float32 transy)
+{
+l_float32 *mat;
+
+ mat = (l_float32 *)LEPT_CALLOC(9, sizeof(l_float32));
+ mat[0] = mat[4] = mat[8] = 1;
+ mat[2] = transx;
+ mat[5] = transy;
+ return mat;
+}
+
+
+/*!
+ * \brief createMatrix2dScale()
+ *
+ * \param[in] scalex horizontal scale factor
+ * \param[in] scaley vertical scale factor
+ * \return 3x3 transform matrix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_float32 *
+createMatrix2dScale(l_float32 scalex,
+ l_float32 scaley)
+{
+l_float32 *mat;
+
+ mat = (l_float32 *)LEPT_CALLOC(9, sizeof(l_float32));
+ mat[0] = scalex;
+ mat[4] = scaley;
+ mat[8] = 1;
+ return mat;
+}
+
+
+/*!
+ * \brief createMatrix2dRotate()
+ *
+ * \param[in] xc, yc location of center of rotation
+ * \param[in] angle rotation in radians; clockwise is positive
+ * \return 3x3 transform matrix, or NULL on error
+ *
+ * <pre>
+ * 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: cosa + b = cosa * cosb - sina * sinb
+ * sina + b = sina * cosb + cosa * sinb ]
+ *
+ * x' - xc = x - xc) * cosa - (y - yc * sina
+ * y' - yc = x - xc) * sina + (y - yc * cosa
+ * </pre>
+ */
+l_float32 *
+createMatrix2dRotate(l_float32 xc,
+ l_float32 yc,
+ l_float32 angle)
+{
+l_float32 sina, cosa;
+l_float32 *mat;
+
+ mat = (l_float32 *)LEPT_CALLOC(9, sizeof(l_float32));
+ 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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief ptaTranslate()
+ *
+ * \param[in] ptas for initial points
+ * \param[in] transx x component of translation wrt. the origin
+ * \param[in] transy y component of translation wrt. the origin
+ * \return ptad translated points, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See createMatrix2dTranslate() for details of transform.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaScale()
+ *
+ * \param[in] ptas for initial points
+ * \param[in] scalex horizontal scale factor
+ * \param[in] scaley vertical scale factor
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See createMatrix2dScale() for details of transform.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaRotate()
+ *
+ * \param[in] ptas for initial points
+ * \param[in] xc, yc location of center of rotation
+ * \param[in] angle rotation in radians; clockwise is positive
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief boxaTranslate()
+ *
+ * \param[in] boxas
+ * \param[in] transx x component of translation wrt. the origin
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief boxaScale()
+ *
+ * \param[in] boxas
+ * \param[in] scalex horizontal scale factor
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief boxaRotate()
+ *
+ * \param[in] boxas
+ * \param[in] xc, yc location of center of rotation
+ * \param[in] 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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief ptaAffineTransform()
+ *
+ * \param[in] ptas for initial points
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief boxaAffineTransform()
+ *
+ * \param[in] boxas
+ * \param[in] 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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief l_productMatVec()
+ *
+ * \param[in] mat square matrix, as a 1-dimensional %size^2 array
+ * \param[in] vecs input column vector of length %size
+ * \param[in] vecd result column vector
+ * \param[in] size matrix is %size x %size; vectors are length %size
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_productMat2()
+ *
+ * \param[in] mat1 square matrix, as a 1-dimensional size^2 array
+ * \param[in] mat2 square matrix, as a 1-dimensional size^2 array
+ * \param[in] matd square matrix; product stored here
+ * \param[in] size of matrices
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_productMat3()
+ *
+ * \param[in] mat1 square matrix, as a 1-dimensional size^2 array
+ * \param[in] mat2 square matrix, as a 1-dimensional size^2 array
+ * \param[in] mat3 square matrix, as a 1-dimensional size^2 array
+ * \param[in] matd square matrix; product stored here
+ * \param[in] size of matrices
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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_t)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;
+}
+
+
+/*!
+ * \brief l_productMat4()
+ *
+ * \param[in] mat1 square matrix, as a 1-dimensional size^2 array
+ * \param[in] mat2 square matrix, as a 1-dimensional size^2 array
+ * \param[in] mat3 square matrix, as a 1-dimensional size^2 array
+ * \param[in] mat4 square matrix, as a 1-dimensional size^2 array
+ * \param[in] matd square matrix; product stored here
+ * \param[in] size of matrices
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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_t)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/leptonica/src/allheaders.h b/leptonica/src/allheaders.h
new file mode 100644
index 00000000..491fe52f
--- /dev/null
+++ b/leptonica/src/allheaders.h
@@ -0,0 +1,2779 @@
+/*====================================================================*
+ - 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 81
+#define LIBLEPT_PATCH_VERSION 0
+
+#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_ok 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_ok 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_ok pixBackgroundNormGrayArrayMorph ( PIX *pixs, PIX *pixim, l_int32 reduction, l_int32 size, l_int32 bgval, PIX **ppixd );
+LEPT_DLL extern l_ok pixBackgroundNormRGBArraysMorph ( PIX *pixs, PIX *pixim, l_int32 reduction, l_int32 size, l_int32 bgval, PIX **ppixr, PIX **ppixg, PIX **ppixb );
+LEPT_DLL extern l_ok pixGetBackgroundGrayMap ( PIX *pixs, PIX *pixim, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, PIX **ppixd );
+LEPT_DLL extern l_ok 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_ok pixGetBackgroundGrayMapMorph ( PIX *pixs, PIX *pixim, l_int32 reduction, l_int32 size, PIX **ppixm );
+LEPT_DLL extern l_ok pixGetBackgroundRGBMapMorph ( PIX *pixs, PIX *pixim, l_int32 reduction, l_int32 size, PIX **ppixmr, PIX **ppixmg, PIX **ppixmb );
+LEPT_DLL extern l_ok 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_ok 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_ok 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 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_ok getAffineXformCoeffs ( PTA *ptas, PTA *ptad, l_float32 **pvc );
+LEPT_DLL extern l_ok affineInvertXform ( l_float32 *vc, l_float32 **pvci );
+LEPT_DLL extern l_ok affineXformSampledPt ( l_float32 *vc, l_int32 x, l_int32 y, l_int32 *pxp, l_int32 *pyp );
+LEPT_DLL extern l_ok affineXformPt ( l_float32 *vc, l_int32 x, l_int32 y, l_float32 *pxp, l_float32 *pyp );
+LEPT_DLL extern l_ok 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_ok 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_ok l_productMatVec ( l_float32 *mat, l_float32 *vecs, l_float32 *vecd, l_int32 size );
+LEPT_DLL extern l_ok l_productMat2 ( l_float32 *mat1, l_float32 *mat2, l_float32 *matd, l_int32 size );
+LEPT_DLL extern l_ok l_productMat3 ( l_float32 *mat1, l_float32 *mat2, l_float32 *mat3, l_float32 *matd, l_int32 size );
+LEPT_DLL extern l_ok 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 ( const 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 ( const 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 ( const 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 ( const 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 ( const 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 ( const 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, PIXA *pixadb );
+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_ok 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, l_int32 debug );
+LEPT_DLL extern L_BBUFFER * bbufferCreate ( const 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_ok bbufferRead ( L_BBUFFER *bb, l_uint8 *src, l_int32 nbytes );
+LEPT_DLL extern l_ok bbufferReadStream ( L_BBUFFER *bb, FILE *fp, l_int32 nbytes );
+LEPT_DLL extern l_ok bbufferExtendArray ( L_BBUFFER *bb, l_int32 nbytes );
+LEPT_DLL extern l_ok bbufferWrite ( L_BBUFFER *bb, l_uint8 *dest, size_t nbytes, size_t *pnout );
+LEPT_DLL extern l_ok 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_ok getBilinearXformCoeffs ( PTA *ptas, PTA *ptad, l_float32 **pvc );
+LEPT_DLL extern l_ok bilinearXformSampledPt ( l_float32 *vc, l_int32 x, l_int32 y, l_int32 *pxp, l_int32 *pyp );
+LEPT_DLL extern l_ok bilinearXformPt ( l_float32 *vc, l_int32 x, l_int32 y, l_float32 *pxp, l_float32 *pyp );
+LEPT_DLL extern l_ok 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_ok pixSauvolaBinarizeTiled ( PIX *pixs, l_int32 whsize, l_float32 factor, l_int32 nx, l_int32 ny, PIX **ppixth, PIX **ppixd );
+LEPT_DLL extern l_ok pixSauvolaBinarize ( PIX *pixs, l_int32 whsize, l_float32 factor, l_int32 addborder, PIX **ppixm, PIX **ppixsd, PIX **ppixth, PIX **ppixd );
+LEPT_DLL extern l_ok 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 l_ok pixThresholdByHisto ( PIX *pixs, l_int32 factor, l_int32 halfw, l_float32 delta, l_int32 *pthresh, PIX **ppixd, PIX **ppixhisto );
+LEPT_DLL extern PIX * pixExpandBinaryReplicate ( PIX *pixs, l_int32 xfact, l_int32 yfact );
+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_ok 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_ok pixLinearEdgeFade ( PIX *pixs, l_int32 dir, l_int32 fadeto, l_float32 distfract, l_float32 maxfade );
+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_ok bmfGetWidth ( L_BMF *bmf, char chr, l_int32 *pw );
+LEPT_DLL extern l_ok 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_ok pixaSaveFont ( const char *indir, const char *outdir, l_int32 fontsize );
+LEPT_DLL extern PIX * pixReadStreamBmp ( FILE *fp );
+LEPT_DLL extern PIX * pixReadMemBmp ( const l_uint8 *cdata, size_t size );
+LEPT_DLL extern l_ok pixWriteStreamBmp ( FILE *fp, PIX *pix );
+LEPT_DLL extern l_ok pixWriteMemBmp ( l_uint8 **pfdata, size_t *pfsize, PIX *pixs );
+LEPT_DLL extern PIXA * l_bootnum_gen1 ( void );
+LEPT_DLL extern PIXA * l_bootnum_gen2 ( void );
+LEPT_DLL extern PIXA * l_bootnum_gen3 ( void );
+LEPT_DLL extern PIXA * l_bootnum_gen4 ( l_int32 nsamp );
+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_ok boxGetGeometry ( BOX *box, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_ok boxSetGeometry ( BOX *box, l_int32 x, l_int32 y, l_int32 w, l_int32 h );
+LEPT_DLL extern l_ok boxGetSideLocations ( BOX *box, l_int32 *pl, l_int32 *pr, l_int32 *pt, l_int32 *pb );
+LEPT_DLL extern l_ok boxSetSideLocations ( BOX *box, l_int32 l, l_int32 r, l_int32 t, l_int32 b );
+LEPT_DLL extern l_int32 boxGetRefcount ( BOX *box );
+LEPT_DLL extern l_ok boxChangeRefcount ( BOX *box, l_int32 delta );
+LEPT_DLL extern l_ok 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_ok boxaAddBox ( BOXA *boxa, BOX *box, l_int32 copyflag );
+LEPT_DLL extern l_ok boxaExtendArray ( BOXA *boxa );
+LEPT_DLL extern l_ok boxaExtendArrayToSize ( BOXA *boxa, size_t 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 NUMA * boxaFindInvalidBoxes ( BOXA *boxa );
+LEPT_DLL extern l_ok boxaGetBoxGeometry ( BOXA *boxa, l_int32 index, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_ok boxaIsFull ( BOXA *boxa, l_int32 *pfull );
+LEPT_DLL extern l_ok boxaReplaceBox ( BOXA *boxa, l_int32 index, BOX *box );
+LEPT_DLL extern l_ok boxaInsertBox ( BOXA *boxa, l_int32 index, BOX *box );
+LEPT_DLL extern l_ok boxaRemoveBox ( BOXA *boxa, l_int32 index );
+LEPT_DLL extern l_ok boxaRemoveBoxAndSave ( BOXA *boxa, l_int32 index, BOX **pbox );
+LEPT_DLL extern BOXA * boxaSaveValid ( BOXA *boxas, l_int32 copyflag );
+LEPT_DLL extern l_ok boxaInitFull ( BOXA *boxa, BOX *box );
+LEPT_DLL extern l_ok 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_ok boxaaAddBoxa ( BOXAA *baa, BOXA *ba, l_int32 copyflag );
+LEPT_DLL extern l_ok boxaaExtendArray ( BOXAA *baa );
+LEPT_DLL extern l_ok 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_ok boxaaInitFull ( BOXAA *baa, BOXA *boxa );
+LEPT_DLL extern l_ok boxaaExtendWithInit ( BOXAA *baa, l_int32 maxindex, BOXA *boxa );
+LEPT_DLL extern l_ok boxaaReplaceBoxa ( BOXAA *baa, l_int32 index, BOXA *boxa );
+LEPT_DLL extern l_ok boxaaInsertBoxa ( BOXAA *baa, l_int32 index, BOXA *boxa );
+LEPT_DLL extern l_ok boxaaRemoveBoxa ( BOXAA *baa, l_int32 index );
+LEPT_DLL extern l_ok 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 BOXAA * boxaaReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok boxaaWrite ( const char *filename, BOXAA *baa );
+LEPT_DLL extern l_ok boxaaWriteStream ( FILE *fp, BOXAA *baa );
+LEPT_DLL extern l_ok boxaaWriteMem ( l_uint8 **pdata, size_t *psize, 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_ok boxaWriteDebug ( const char *filename, BOXA *boxa );
+LEPT_DLL extern l_ok boxaWrite ( const char *filename, BOXA *boxa );
+LEPT_DLL extern l_ok boxaWriteStream ( FILE *fp, BOXA *boxa );
+LEPT_DLL extern l_ok boxaWriteStderr ( BOXA *boxa );
+LEPT_DLL extern l_ok boxaWriteMem ( l_uint8 **pdata, size_t *psize, BOXA *boxa );
+LEPT_DLL extern l_ok boxPrintStreamInfo ( FILE *fp, BOX *box );
+LEPT_DLL extern l_ok boxContains ( BOX *box1, BOX *box2, l_int32 *presult );
+LEPT_DLL extern l_ok boxIntersects ( BOX *box1, BOX *box2, l_int32 *presult );
+LEPT_DLL extern BOXA * boxaContainedInBox ( BOXA *boxas, BOX *box );
+LEPT_DLL extern l_ok boxaContainedInBoxCount ( BOXA *boxa, BOX *box, l_int32 *pcount );
+LEPT_DLL extern l_ok boxaContainedInBoxa ( BOXA *boxa1, BOXA *boxa2, l_int32 *pcontained );
+LEPT_DLL extern BOXA * boxaIntersectsBox ( BOXA *boxas, BOX *box );
+LEPT_DLL extern l_ok boxaIntersectsBoxCount ( BOXA *boxa, BOX *box, l_int32 *pcount );
+LEPT_DLL extern BOXA * boxaClipToBox ( BOXA *boxas, BOX *box );
+LEPT_DLL extern BOXA * boxaCombineOverlaps ( BOXA *boxas, PIXA *pixadb );
+LEPT_DLL extern l_ok boxaCombineOverlapsInPair ( BOXA *boxas1, BOXA *boxas2, BOXA **pboxad1, BOXA **pboxad2, PIXA *pixadb );
+LEPT_DLL extern BOX * boxOverlapRegion ( BOX *box1, BOX *box2 );
+LEPT_DLL extern BOX * boxBoundingRegion ( BOX *box1, BOX *box2 );
+LEPT_DLL extern l_ok boxOverlapFraction ( BOX *box1, BOX *box2, l_float32 *pfract );
+LEPT_DLL extern l_ok 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_ok boxOverlapDistance ( BOX *box1, BOX *box2, l_int32 *ph_ovl, l_int32 *pv_ovl );
+LEPT_DLL extern l_ok boxSeparationDistance ( BOX *box1, BOX *box2, l_int32 *ph_sep, l_int32 *pv_sep );
+LEPT_DLL extern l_ok boxCompareSize ( BOX *box1, BOX *box2, l_int32 type, l_int32 *prel );
+LEPT_DLL extern l_ok 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 BOX * boxaGetNearestToLine ( BOXA *boxa, l_int32 x, l_int32 y );
+LEPT_DLL extern l_ok boxaFindNearestBoxes ( BOXA *boxa, l_int32 dist_select, l_int32 range, NUMAA **pnaaindex, NUMAA **pnaadist );
+LEPT_DLL extern l_ok boxaGetNearestByDirection ( BOXA *boxa, l_int32 i, l_int32 dir, l_int32 dist_select, l_int32 range, l_int32 *pindex, l_int32 *pdist );
+LEPT_DLL extern l_ok boxGetCenter ( BOX *box, l_float32 *pcx, l_float32 *pcy );
+LEPT_DLL extern l_ok 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_ok 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 BOXA * boxaAdjustSides ( BOXA *boxas, l_int32 delleft, l_int32 delright, l_int32 deltop, l_int32 delbot );
+LEPT_DLL extern l_ok boxaAdjustBoxSides ( BOXA *boxa, l_int32 index, l_int32 delleft, l_int32 delright, l_int32 deltop, l_int32 delbot );
+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 l_ok boxSetSide ( BOX *boxs, 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_ok boxEqual ( BOX *box1, BOX *box2, l_int32 *psame );
+LEPT_DLL extern l_ok boxaEqual ( BOXA *boxa1, BOXA *boxa2, l_int32 maxdist, NUMA **pnaindex, l_int32 *psame );
+LEPT_DLL extern l_ok boxSimilar ( BOX *box1, BOX *box2, l_int32 leftdiff, l_int32 rightdiff, l_int32 topdiff, l_int32 botdiff, l_int32 *psimilar );
+LEPT_DLL extern l_ok 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_ok boxaJoin ( BOXA *boxad, BOXA *boxas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern l_ok boxaaJoin ( BOXAA *baad, BOXAA *baas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern l_ok 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 * boxaShiftWithPta ( BOXA *boxas, PTA *pta, l_int32 dir );
+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_ok boxaExtractAsNuma ( BOXA *boxa, NUMA **pnal, NUMA **pnat, NUMA **pnar, NUMA **pnab, NUMA **pnaw, NUMA **pnah, l_int32 keepinvalid );
+LEPT_DLL extern l_ok boxaExtractAsPta ( BOXA *boxa, PTA **pptal, PTA **pptat, PTA **pptar, PTA **pptab, PTA **pptaw, PTA **pptah, l_int32 keepinvalid );
+LEPT_DLL extern PTA * boxaExtractCorners ( BOXA *boxa, l_int32 loc );
+LEPT_DLL extern l_ok boxaGetRankVals ( BOXA *boxa, l_float32 fract, l_int32 *px, l_int32 *py, l_int32 *pr, l_int32 *pb, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_ok boxaGetMedianVals ( BOXA *boxa, l_int32 *px, l_int32 *py, l_int32 *pr, l_int32 *pb, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_ok boxaGetAverageSize ( BOXA *boxa, l_float32 *pw, l_float32 *ph );
+LEPT_DLL extern l_ok 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 BOXAA * boxaaTranspose ( BOXAA *baas );
+LEPT_DLL extern l_ok 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 ( PIX *pixs, BOXAA *baa, l_int32 linewba, l_int32 linewb, l_uint32 colorba, l_uint32 colorb, l_int32 w, l_int32 h );
+LEPT_DLL extern PIXA * pixaDisplayBoxaa ( PIXA *pixas, BOXAA *baa, l_int32 colorflag, l_int32 width );
+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_ok 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 * boxaSelectByWHRatio ( BOXA *boxas, l_float32 ratio, l_int32 relation, l_int32 *pchanged );
+LEPT_DLL extern NUMA * boxaMakeWHRatioIndicator ( BOXA *boxa, l_float32 ratio, 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_ok 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 l_ok boxaGetExtent ( BOXA *boxa, l_int32 *pw, l_int32 *ph, BOX **pbox );
+LEPT_DLL extern l_ok boxaGetCoverage ( BOXA *boxa, l_int32 wc, l_int32 hc, l_int32 exactflag, l_float32 *pfract );
+LEPT_DLL extern l_ok boxaaSizeRange ( BOXAA *baa, l_int32 *pminw, l_int32 *pminh, l_int32 *pmaxw, l_int32 *pmaxh );
+LEPT_DLL extern l_ok boxaSizeRange ( BOXA *boxa, l_int32 *pminw, l_int32 *pminh, l_int32 *pmaxw, l_int32 *pmaxh );
+LEPT_DLL extern l_ok boxaLocationRange ( BOXA *boxa, l_int32 *pminx, l_int32 *pminy, l_int32 *pmaxx, l_int32 *pmaxy );
+LEPT_DLL extern l_ok boxaGetSizes ( BOXA *boxa, NUMA **pnaw, NUMA **pnah );
+LEPT_DLL extern l_ok boxaGetArea ( BOXA *boxa, l_int32 *parea );
+LEPT_DLL extern PIX * boxaDisplayTiled ( BOXA *boxas, PIXA *pixa, l_int32 first, l_int32 last, l_int32 maxwidth, l_int32 linewidth, l_float32 scalefactor, l_int32 background, l_int32 spacing, l_int32 border );
+LEPT_DLL extern BOXA * boxaSmoothSequenceMedian ( BOXA *boxas, l_int32 halfwin, l_int32 subflag, l_int32 maxdiff, l_int32 extrapixels, 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, l_int32 extrapixels );
+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, l_int32 start );
+LEPT_DLL extern BOXA * boxaReconcilePairWidth ( BOXA *boxas, l_int32 delw, l_int32 op, l_float32 factor, NUMA *na );
+LEPT_DLL extern l_ok boxaSizeConsistency1 ( BOXA *boxas, l_int32 type, l_float32 threshp, l_float32 threshm, l_float32 *pfvarp, l_float32 *pfvarm, l_int32 *psame );
+LEPT_DLL extern l_ok boxaSizeConsistency2 ( BOXA *boxas, l_float32 *pfdevw, l_float32 *pfdevh, l_int32 debug );
+LEPT_DLL extern BOXA * boxaReconcileAllByMedian ( BOXA *boxas, l_int32 select1, l_int32 select2, l_int32 thresh, l_int32 extra, PIXA *pixadb );
+LEPT_DLL extern BOXA * boxaReconcileSidesByMedian ( BOXA *boxas, l_int32 select, l_int32 thresh, l_int32 extra, PIXA *pixadb );
+LEPT_DLL extern BOXA * boxaReconcileSizeByMedian ( BOXA *boxas, l_int32 type, l_float32 dfract, l_float32 sfract, l_float32 factor, NUMA **pnadelw, NUMA **pnadelh, l_float32 *pratiowh );
+LEPT_DLL extern l_ok boxaPlotSides ( BOXA *boxa, const char *plotname, NUMA **pnal, NUMA **pnat, NUMA **pnar, NUMA **pnab, PIX **ppixd );
+LEPT_DLL extern l_ok boxaPlotSizes ( BOXA *boxa, const char *plotname, NUMA **pnaw, NUMA **pnah, PIX **ppixd );
+LEPT_DLL extern BOXA * boxaFillSequence ( BOXA *boxas, l_int32 useflag, l_int32 debug );
+LEPT_DLL extern l_ok boxaSizeVariation ( BOXA *boxa, l_int32 type, l_float32 *pdel_evenodd, l_float32 *prms_even, l_float32 *prms_odd, l_float32 *prms_all );
+LEPT_DLL extern l_ok boxaMedianDimensions ( BOXA *boxas, l_int32 *pmedw, l_int32 *pmedh, l_int32 *pmedwe, l_int32 *pmedwo, l_int32 *pmedhe, l_int32 *pmedho, NUMA **pnadelw, NUMA **pnadelh );
+LEPT_DLL extern L_BYTEA * l_byteaCreate ( size_t nbytes );
+LEPT_DLL extern L_BYTEA * l_byteaInitFromMem ( const 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_ok l_byteaAppendData ( L_BYTEA *ba, const l_uint8 *newdata, size_t newbytes );
+LEPT_DLL extern l_ok l_byteaAppendString ( L_BYTEA *ba, const char *str );
+LEPT_DLL extern l_ok l_byteaJoin ( L_BYTEA *ba1, L_BYTEA **pba2 );
+LEPT_DLL extern l_ok l_byteaSplit ( L_BYTEA *ba1, size_t splitloc, L_BYTEA **pba2 );
+LEPT_DLL extern l_ok l_byteaFindEachSequence ( L_BYTEA *ba, const l_uint8 *sequence, size_t seqlen, L_DNA **pda );
+LEPT_DLL extern l_ok l_byteaWrite ( const char *fname, L_BYTEA *ba, size_t startloc, size_t nbytes );
+LEPT_DLL extern l_ok l_byteaWriteStream ( FILE *fp, L_BYTEA *ba, size_t startloc, size_t nbytes );
+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_ok 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 PTAA * pixGetOuterBordersPtaa ( PIX *pixs );
+LEPT_DLL extern l_ok pixGetOuterBorder ( CCBORD *ccb, PIX *pixs, BOX *box );
+LEPT_DLL extern l_ok ccbaGenerateGlobalLocs ( CCBORDA *ccba );
+LEPT_DLL extern l_ok ccbaGenerateStepChains ( CCBORDA *ccba );
+LEPT_DLL extern l_ok ccbaStepChainsToPixCoords ( CCBORDA *ccba, l_int32 coordtype );
+LEPT_DLL extern l_ok ccbaGenerateSPGlobalLocs ( CCBORDA *ccba, l_int32 ptsflag );
+LEPT_DLL extern l_ok 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_ok ccbaWrite ( const char *filename, CCBORDA *ccba );
+LEPT_DLL extern l_ok ccbaWriteStream ( FILE *fp, CCBORDA *ccba );
+LEPT_DLL extern CCBORDA * ccbaRead ( const char *filename );
+LEPT_DLL extern CCBORDA * ccbaReadStream ( FILE *fp );
+LEPT_DLL extern l_ok ccbaWriteSVG ( const char *filename, CCBORDA *ccba );
+LEPT_DLL extern char * ccbaWriteSVGString ( CCBORDA *ccba );
+LEPT_DLL extern PIXA * pixaThinConnected ( PIXA *pixas, l_int32 type, l_int32 connectivity, l_int32 maxiters );
+LEPT_DLL extern PIX * pixThinConnected ( PIX *pixs, l_int32 type, l_int32 connectivity, l_int32 maxiters );
+LEPT_DLL extern PIX * pixThinConnectedBySet ( PIX *pixs, l_int32 type, SELA *sela, l_int32 maxiters );
+LEPT_DLL extern SELA * selaMakeThinSets ( l_int32 index, l_int32 debug );
+LEPT_DLL extern l_ok pixFindCheckerboardCorners ( PIX *pixs, l_int32 size, l_int32 dilation, l_int32 nsels, PIX **ppix_corners, PTA **ppta_corners, PIXA *pixadb );
+LEPT_DLL extern l_ok 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_ok 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_ok pixGetWordsInTextlines ( PIX *pixs, l_int32 minwidth, l_int32 minheight, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxad, PIXA **ppixad, NUMA **pnai );
+LEPT_DLL extern l_ok pixGetWordBoxesInTextlines ( PIX *pixs, l_int32 minwidth, l_int32 minheight, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxad, NUMA **pnai );
+LEPT_DLL extern l_ok pixFindWordAndCharacterBoxes ( PIX *pixs, BOX *boxs, l_int32 thresh, BOXA **pboxaw, BOXAA **pboxaac, const char *debugdir );
+LEPT_DLL extern NUMAA * boxaExtractSortedPattern ( BOXA *boxa, NUMA *na );
+LEPT_DLL extern l_ok 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_ok pixColorContent ( PIX *pixs, l_int32 rref, l_int32 gref, l_int32 bref, l_int32 mingray, PIX **ppixr, PIX **ppixg, PIX **ppixb );
+LEPT_DLL extern PIX * pixColorMagnitude ( PIX *pixs, l_int32 rref, l_int32 gref, l_int32 bref, l_int32 type );
+LEPT_DLL extern l_ok pixColorFraction ( PIX *pixs, l_int32 darkthresh, l_int32 lightthresh, l_int32 diffthresh, l_int32 factor, l_float32 *ppixfract, l_float32 *pcolorfract );
+LEPT_DLL extern PIX * pixColorShiftWhitePoint ( PIX *pixs, l_int32 rref, l_int32 gref, l_int32 bref );
+LEPT_DLL extern PIX * pixMaskOverColorPixels ( PIX *pixs, l_int32 threshdiff, l_int32 mindist );
+LEPT_DLL extern PIX * pixMaskOverGrayPixels ( PIX *pixs, l_int32 maxlimit, l_int32 satlimit );
+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_ok pixFindColorRegions ( PIX *pixs, PIX *pixm, l_int32 factor, l_int32 lightthresh, l_int32 darkthresh, l_int32 mindiff, l_int32 colordiff, l_float32 edgefract, l_float32 *pcolorfract, PIX **pcolormask1, PIX **pcolormask2, PIXA *pixadb );
+LEPT_DLL extern l_ok pixNumSignificantGrayColors ( PIX *pixs, l_int32 darkthresh, l_int32 lightthresh, l_float32 minfract, l_int32 factor, l_int32 *pncolors );
+LEPT_DLL extern l_ok pixColorsForQuantization ( PIX *pixs, l_int32 thresh, l_int32 *pncolors, l_int32 *piscolor, l_int32 debug );
+LEPT_DLL extern l_ok pixNumColors ( PIX *pixs, l_int32 factor, l_int32 *pncolors );
+LEPT_DLL extern PIX * pixConvertRGBToCmapLossless ( PIX *pixs );
+LEPT_DLL extern l_ok 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_ok makeRGBIndexTables ( l_uint32 **prtab, l_uint32 **pgtab, l_uint32 **pbtab, l_int32 sigbits );
+LEPT_DLL extern l_ok getRGBFromIndex ( l_uint32 index, l_int32 sigbits, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern l_ok pixHasHighlightRed ( PIX *pixs, l_int32 factor, l_float32 minfract, l_float32 fthresh, l_int32 *phasred, l_float32 *pratio, PIX **ppixdb );
+LEPT_DLL extern L_COLORFILL * l_colorfillCreate ( PIX *pixs, l_int32 nx, l_int32 ny );
+LEPT_DLL extern void l_colorfillDestroy ( L_COLORFILL **pcf );
+LEPT_DLL extern l_ok pixColorContentByLocation ( L_COLORFILL *cf, l_int32 rref, l_int32 gref, l_int32 bref, l_int32 minmax, l_int32 maxdiff, l_int32 minarea, l_int32 smooth, l_int32 debug );
+LEPT_DLL extern PIX * pixColorFill ( PIX *pixs, l_int32 minmax, l_int32 maxdiff, l_int32 smooth, l_int32 minarea, l_int32 debug );
+LEPT_DLL extern PIXA * makeColorfillTestData ( l_int32 w, l_int32 h, l_int32 nseeds, l_int32 range );
+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_ok 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_ok 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_ok pixelShiftByComponent ( l_int32 rval, l_int32 gval, l_int32 bval, l_uint32 srcval, l_uint32 dstval, l_uint32 *ppixel );
+LEPT_DLL extern l_ok pixelFractionalShift ( l_int32 rval, l_int32 gval, l_int32 bval, l_float32 fract, l_uint32 *ppixel );
+LEPT_DLL extern PIX * pixMapWithInvariantHue ( PIX *pixd, PIX *pixs, l_uint32 srcval, l_float32 fract );
+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 ( const PIXCMAP *cmaps );
+LEPT_DLL extern void pixcmapDestroy ( PIXCMAP **pcmap );
+LEPT_DLL extern l_ok pixcmapIsValid ( const PIXCMAP *cmap, PIX *pix, l_int32 *pvalid );
+LEPT_DLL extern l_ok pixcmapAddColor ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_ok pixcmapAddRGBA ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 aval );
+LEPT_DLL extern l_ok pixcmapAddNewColor ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *pindex );
+LEPT_DLL extern l_ok pixcmapAddNearestColor ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *pindex );
+LEPT_DLL extern l_ok pixcmapUsableColor ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *pusable );
+LEPT_DLL extern l_ok pixcmapAddBlackOrWhite ( PIXCMAP *cmap, l_int32 color, l_int32 *pindex );
+LEPT_DLL extern l_ok pixcmapSetBlackAndWhite ( PIXCMAP *cmap, l_int32 setblack, l_int32 setwhite );
+LEPT_DLL extern l_int32 pixcmapGetCount ( const PIXCMAP *cmap );
+LEPT_DLL extern l_int32 pixcmapGetFreeCount ( PIXCMAP *cmap );
+LEPT_DLL extern l_int32 pixcmapGetDepth ( PIXCMAP *cmap );
+LEPT_DLL extern l_ok pixcmapGetMinDepth ( PIXCMAP *cmap, l_int32 *pmindepth );
+LEPT_DLL extern l_ok pixcmapClear ( PIXCMAP *cmap );
+LEPT_DLL extern l_ok pixcmapGetColor ( PIXCMAP *cmap, l_int32 index, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern l_ok pixcmapGetColor32 ( PIXCMAP *cmap, l_int32 index, l_uint32 *pval32 );
+LEPT_DLL extern l_ok pixcmapGetRGBA ( PIXCMAP *cmap, l_int32 index, l_int32 *prval, l_int32 *pgval, l_int32 *pbval, l_int32 *paval );
+LEPT_DLL extern l_ok pixcmapGetRGBA32 ( PIXCMAP *cmap, l_int32 index, l_uint32 *pval32 );
+LEPT_DLL extern l_ok pixcmapResetColor ( PIXCMAP *cmap, l_int32 index, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_ok 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_ok pixcmapHasColor ( PIXCMAP *cmap, l_int32 *pcolor );
+LEPT_DLL extern l_ok pixcmapIsOpaque ( PIXCMAP *cmap, l_int32 *popaque );
+LEPT_DLL extern l_ok pixcmapIsBlackAndWhite ( PIXCMAP *cmap, l_int32 *pblackwhite );
+LEPT_DLL extern l_ok pixcmapCountGrayColors ( PIXCMAP *cmap, l_int32 *pngray );
+LEPT_DLL extern l_ok pixcmapGetRankIntensity ( PIXCMAP *cmap, l_float32 rankval, l_int32 *pindex );
+LEPT_DLL extern l_ok pixcmapGetNearestIndex ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *pindex );
+LEPT_DLL extern l_ok pixcmapGetNearestGrayIndex ( PIXCMAP *cmap, l_int32 val, l_int32 *pindex );
+LEPT_DLL extern l_ok pixcmapGetDistanceToColor ( PIXCMAP *cmap, l_int32 index, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *pdist );
+LEPT_DLL extern l_ok pixcmapGetRangeValues ( PIXCMAP *cmap, l_int32 select, l_int32 *pminval, l_int32 *pmaxval, l_int32 *pminindex, l_int32 *pmaxindex );
+LEPT_DLL extern PIXCMAP * pixcmapGrayToFalseColor ( l_float32 gamma );
+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 * pixcmapConvertTo4 ( PIXCMAP *cmaps );
+LEPT_DLL extern PIXCMAP * pixcmapConvertTo8 ( PIXCMAP *cmaps );
+LEPT_DLL extern PIXCMAP * pixcmapRead ( const char *filename );
+LEPT_DLL extern PIXCMAP * pixcmapReadStream ( FILE *fp );
+LEPT_DLL extern PIXCMAP * pixcmapReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok pixcmapWrite ( const char *filename, const PIXCMAP *cmap );
+LEPT_DLL extern l_ok pixcmapWriteStream ( FILE *fp, const PIXCMAP *cmap );
+LEPT_DLL extern l_ok pixcmapWriteMem ( l_uint8 **pdata, size_t *psize, const PIXCMAP *cmap );
+LEPT_DLL extern l_ok pixcmapToArrays ( const PIXCMAP *cmap, l_int32 **prmap, l_int32 **pgmap, l_int32 **pbmap, l_int32 **pamap );
+LEPT_DLL extern l_ok pixcmapToRGBTable ( PIXCMAP *cmap, l_uint32 **ptab, l_int32 *pncolors );
+LEPT_DLL extern l_ok 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_ok pixcmapGammaTRC ( PIXCMAP *cmap, l_float32 gamma, l_int32 minval, l_int32 maxval );
+LEPT_DLL extern l_ok pixcmapContrastTRC ( PIXCMAP *cmap, l_float32 factor );
+LEPT_DLL extern l_ok pixcmapShiftIntensity ( PIXCMAP *cmap, l_float32 fraction );
+LEPT_DLL extern l_ok 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_ok makeRGBToIndexTables ( l_int32 cqlevels, l_uint32 **prtab, l_uint32 **pgtab, l_uint32 **pbtab );
+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_ok pixRemoveUnusedColors ( PIX *pixs );
+LEPT_DLL extern l_ok 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, l_int32 debugflag );
+LEPT_DLL extern PIX * pixColorSegmentCluster ( PIX *pixs, l_int32 maxdist, l_int32 maxcolors, l_int32 debugflag );
+LEPT_DLL extern l_ok pixAssignToNearestColor ( PIX *pixd, PIX *pixs, PIX *pixm, l_int32 level, l_int32 *countarray );
+LEPT_DLL extern l_ok pixColorSegmentClean ( PIX *pixs, l_int32 selsize, l_int32 *countarray );
+LEPT_DLL extern l_ok 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_ok convertRGBToHSV ( l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *phval, l_int32 *psval, l_int32 *pvval );
+LEPT_DLL extern l_ok convertHSVToRGB ( l_int32 hval, l_int32 sval, l_int32 vval, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern l_ok pixcmapConvertRGBToHSV ( PIXCMAP *cmap );
+LEPT_DLL extern l_ok 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_ok 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_ok convertRGBToYUV ( l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *pyval, l_int32 *puval, l_int32 *pvval );
+LEPT_DLL extern l_ok convertYUVToRGB ( l_int32 yval, l_int32 uval, l_int32 vval, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern l_ok pixcmapConvertRGBToYUV ( PIXCMAP *cmap );
+LEPT_DLL extern l_ok pixcmapConvertYUVToRGB ( PIXCMAP *cmap );
+LEPT_DLL extern FPIXA * pixConvertRGBToXYZ ( PIX *pixs );
+LEPT_DLL extern PIX * fpixaConvertXYZToRGB ( FPIXA *fpixa );
+LEPT_DLL extern l_ok convertRGBToXYZ ( l_int32 rval, l_int32 gval, l_int32 bval, l_float32 *pfxval, l_float32 *pfyval, l_float32 *pfzval );
+LEPT_DLL extern l_ok 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_ok convertXYZToLAB ( l_float32 xval, l_float32 yval, l_float32 zval, l_float32 *plval, l_float32 *paval, l_float32 *pbval );
+LEPT_DLL extern l_ok 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_ok convertRGBToLAB ( l_int32 rval, l_int32 gval, l_int32 bval, l_float32 *pflval, l_float32 *pfaval, l_float32 *pfbval );
+LEPT_DLL extern l_ok convertLABToRGB ( l_float32 flval, l_float32 faval, l_float32 fbval, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern PIX * pixMakeGamutRGB ( l_int32 scale );
+LEPT_DLL extern l_ok pixEqual ( PIX *pix1, PIX *pix2, l_int32 *psame );
+LEPT_DLL extern l_ok pixEqualWithAlpha ( PIX *pix1, PIX *pix2, l_int32 use_alpha, l_int32 *psame );
+LEPT_DLL extern l_ok pixEqualWithCmap ( PIX *pix1, PIX *pix2, l_int32 *psame );
+LEPT_DLL extern l_ok cmapEqual ( PIXCMAP *cmap1, PIXCMAP *cmap2, l_int32 ncomps, l_int32 *psame );
+LEPT_DLL extern l_ok pixUsesCmapColor ( PIX *pixs, l_int32 *pcolor );
+LEPT_DLL extern l_ok pixCorrelationBinary ( PIX *pix1, PIX *pix2, l_float32 *pval );
+LEPT_DLL extern PIX * pixDisplayDiffBinary ( PIX *pix1, PIX *pix2 );
+LEPT_DLL extern l_ok pixCompareBinary ( PIX *pix1, PIX *pix2, l_int32 comptype, l_float32 *pfract, PIX **ppixdiff );
+LEPT_DLL extern l_ok 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_ok 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_ok 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_ok 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_ok pixTestForSimilarity ( PIX *pix1, PIX *pix2, l_int32 factor, l_int32 mindiff, l_float32 maxfract, l_float32 maxave, l_int32 *psimilar, l_int32 details );
+LEPT_DLL extern l_ok pixGetDifferenceStats ( PIX *pix1, PIX *pix2, l_int32 factor, l_int32 mindiff, l_float32 *pfractdiff, l_float32 *pavediff, l_int32 details );
+LEPT_DLL extern NUMA * pixGetDifferenceHistogram ( PIX *pix1, PIX *pix2, l_int32 factor );
+LEPT_DLL extern l_ok 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_ok pixGetPSNR ( PIX *pix1, PIX *pix2, l_int32 factor, l_float32 *ppsnr );
+LEPT_DLL extern l_ok pixaComparePhotoRegionsByHisto ( PIXA *pixa, l_float32 minratio, l_float32 textthresh, l_int32 factor, l_int32 n, l_float32 simthresh, NUMA **pnai, l_float32 **pscores, PIX **ppixd, l_int32 debug );
+LEPT_DLL extern l_ok pixComparePhotoRegionsByHisto ( PIX *pix1, PIX *pix2, BOX *box1, BOX *box2, l_float32 minratio, l_int32 factor, l_int32 n, l_float32 *pscore, l_int32 debugflag );
+LEPT_DLL extern l_ok pixGenPhotoHistos ( PIX *pixs, BOX *box, l_int32 factor, l_float32 thresh, l_int32 n, NUMAA **pnaa, l_int32 *pw, l_int32 *ph, l_int32 debugindex );
+LEPT_DLL extern PIX * pixPadToCenterCentroid ( PIX *pixs, l_int32 factor );
+LEPT_DLL extern l_ok pixCentroid8 ( PIX *pixs, l_int32 factor, l_float32 *pcx, l_float32 *pcy );
+LEPT_DLL extern l_ok pixDecideIfPhotoImage ( PIX *pix, l_int32 factor, l_float32 thresh, l_int32 n, NUMAA **pnaa, PIXA *pixadebug );
+LEPT_DLL extern l_ok 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_ok pixCompareGrayByHisto ( PIX *pix1, PIX *pix2, BOX *box1, BOX *box2, l_float32 minratio, l_int32 maxgray, l_int32 factor, l_int32 n, l_float32 *pscore, l_int32 debugflag );
+LEPT_DLL extern l_ok 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_ok pixCompareWithTranslation ( PIX *pix1, PIX *pix2, l_int32 thresh, l_int32 *pdelx, l_int32 *pdely, l_float32 *pscore, l_int32 debugflag );
+LEPT_DLL extern l_ok 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_ok 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 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_ok pixSeedfill ( PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y, l_int32 connectivity );
+LEPT_DLL extern l_ok pixSeedfill4 ( PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y );
+LEPT_DLL extern l_ok pixSeedfill8 ( PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y );
+LEPT_DLL extern l_ok 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_ok 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_ok 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 ( void );
+LEPT_DLL extern l_ok 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_ok 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_ok 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_ok dewarpaDestroyDewarp ( L_DEWARPA *dewa, l_int32 pageno );
+LEPT_DLL extern l_ok dewarpaInsertDewarp ( L_DEWARPA *dewa, L_DEWARP *dew );
+LEPT_DLL extern L_DEWARP * dewarpaGetDewarp ( L_DEWARPA *dewa, l_int32 index );
+LEPT_DLL extern l_ok dewarpaSetCurvatures ( L_DEWARPA *dewa, l_int32 max_linecurv, l_int32 min_diff_linecurv, l_int32 max_diff_linecurv, l_int32 max_edgecurv, l_int32 max_diff_edgecurv, l_int32 max_edgeslope );
+LEPT_DLL extern l_ok dewarpaUseBothArrays ( L_DEWARPA *dewa, l_int32 useboth );
+LEPT_DLL extern l_ok dewarpaSetCheckColumns ( L_DEWARPA *dewa, l_int32 check_columns );
+LEPT_DLL extern l_ok 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_DEWARP * dewarpReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok dewarpWrite ( const char *filename, L_DEWARP *dew );
+LEPT_DLL extern l_ok dewarpWriteStream ( FILE *fp, L_DEWARP *dew );
+LEPT_DLL extern l_ok dewarpWriteMem ( l_uint8 **pdata, size_t *psize, L_DEWARP *dew );
+LEPT_DLL extern L_DEWARPA * dewarpaRead ( const char *filename );
+LEPT_DLL extern L_DEWARPA * dewarpaReadStream ( FILE *fp );
+LEPT_DLL extern L_DEWARPA * dewarpaReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok dewarpaWrite ( const char *filename, L_DEWARPA *dewa );
+LEPT_DLL extern l_ok dewarpaWriteStream ( FILE *fp, L_DEWARPA *dewa );
+LEPT_DLL extern l_ok dewarpaWriteMem ( l_uint8 **pdata, size_t *psize, L_DEWARPA *dewa );
+LEPT_DLL extern l_ok dewarpBuildPageModel ( L_DEWARP *dew, const char *debugfile );
+LEPT_DLL extern l_ok dewarpFindVertDisparity ( L_DEWARP *dew, PTAA *ptaa, l_int32 rotflag );
+LEPT_DLL extern l_ok 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_ok dewarpFindHorizSlopeDisparity ( L_DEWARP *dew, PIX *pixb, l_float32 fractthresh, l_int32 parity );
+LEPT_DLL extern l_ok dewarpBuildLineModel ( L_DEWARP *dew, l_int32 opensize, const char *debugfile );
+LEPT_DLL extern l_ok dewarpaModelStatus ( L_DEWARPA *dewa, l_int32 pageno, l_int32 *pvsuccess, l_int32 *phsuccess );
+LEPT_DLL extern l_ok 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_ok 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_ok dewarpMinimize ( L_DEWARP *dew );
+LEPT_DLL extern l_ok dewarpPopulateFullRes ( L_DEWARP *dew, PIX *pix, l_int32 x, l_int32 y );
+LEPT_DLL extern l_ok dewarpSinglePage ( PIX *pixs, l_int32 thresh, l_int32 adaptive, l_int32 useboth, l_int32 check_columns, PIX **ppixd, L_DEWARPA **pdewa, l_int32 debug );
+LEPT_DLL extern l_ok dewarpSinglePageInit ( PIX *pixs, l_int32 thresh, l_int32 adaptive, l_int32 useboth, l_int32 check_columns, PIX **ppixb, L_DEWARPA **pdewa );
+LEPT_DLL extern l_ok dewarpSinglePageRun ( PIX *pixs, PIX *pixb, L_DEWARPA *dewa, PIX **ppixd, l_int32 debug );
+LEPT_DLL extern l_ok dewarpaListPages ( L_DEWARPA *dewa );
+LEPT_DLL extern l_ok dewarpaSetValidModels ( L_DEWARPA *dewa, l_int32 notests, l_int32 debug );
+LEPT_DLL extern l_ok dewarpaInsertRefModels ( L_DEWARPA *dewa, l_int32 notests, l_int32 debug );
+LEPT_DLL extern l_ok dewarpaStripRefModels ( L_DEWARPA *dewa );
+LEPT_DLL extern l_ok dewarpaRestoreModels ( L_DEWARPA *dewa );
+LEPT_DLL extern l_ok dewarpaInfo ( FILE *fp, L_DEWARPA *dewa );
+LEPT_DLL extern l_ok 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_ok dewarpaShowArrays ( L_DEWARPA *dewa, l_float32 scalefact, l_int32 first, l_int32 last );
+LEPT_DLL extern l_ok dewarpDebug ( L_DEWARP *dew, const char *subdirs, l_int32 index );
+LEPT_DLL extern l_ok 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_ok l_dnaEmpty ( L_DNA *da );
+LEPT_DLL extern l_ok l_dnaAddNumber ( L_DNA *da, l_float64 val );
+LEPT_DLL extern l_ok l_dnaInsertNumber ( L_DNA *da, l_int32 index, l_float64 val );
+LEPT_DLL extern l_ok l_dnaRemoveNumber ( L_DNA *da, l_int32 index );
+LEPT_DLL extern l_ok 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_ok l_dnaSetCount ( L_DNA *da, l_int32 newcount );
+LEPT_DLL extern l_ok l_dnaGetDValue ( L_DNA *da, l_int32 index, l_float64 *pval );
+LEPT_DLL extern l_ok l_dnaGetIValue ( L_DNA *da, l_int32 index, l_int32 *pival );
+LEPT_DLL extern l_ok l_dnaSetValue ( L_DNA *da, l_int32 index, l_float64 val );
+LEPT_DLL extern l_ok 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_ok l_dnaChangeRefcount ( L_DNA *da, l_int32 delta );
+LEPT_DLL extern l_ok l_dnaGetParameters ( L_DNA *da, l_float64 *pstartx, l_float64 *pdelx );
+LEPT_DLL extern l_ok l_dnaSetParameters ( L_DNA *da, l_float64 startx, l_float64 delx );
+LEPT_DLL extern l_ok 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_ok l_dnaWrite ( const char *filename, L_DNA *da );
+LEPT_DLL extern l_ok 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_ok l_dnaaTruncate ( L_DNAA *daa );
+LEPT_DLL extern void l_dnaaDestroy ( L_DNAA **pdaa );
+LEPT_DLL extern l_ok 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_ok l_dnaaReplaceDna ( L_DNAA *daa, l_int32 index, L_DNA *da );
+LEPT_DLL extern l_ok l_dnaaGetValue ( L_DNAA *daa, l_int32 i, l_int32 j, l_float64 *pval );
+LEPT_DLL extern l_ok 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_ok l_dnaaWrite ( const char *filename, L_DNAA *daa );
+LEPT_DLL extern l_ok l_dnaaWriteStream ( FILE *fp, L_DNAA *daa );
+LEPT_DLL extern l_ok l_dnaJoin ( L_DNA *dad, L_DNA *das, l_int32 istart, l_int32 iend );
+LEPT_DLL extern L_DNA * l_dnaaFlattenToDna ( L_DNAA *daa );
+LEPT_DLL extern NUMA * l_dnaConvertToNuma ( L_DNA *da );
+LEPT_DLL extern L_DNA * numaConvertToDna ( NUMA *na );
+LEPT_DLL extern L_DNA * pixConvertDataToDna ( PIX *pix );
+LEPT_DLL extern L_DNA * l_dnaUnionByAset ( L_DNA *da1, L_DNA *da2 );
+LEPT_DLL extern L_DNA * l_dnaRemoveDupsByAset ( L_DNA *das );
+LEPT_DLL extern L_DNA * l_dnaIntersectionByAset ( L_DNA *da1, L_DNA *da2 );
+LEPT_DLL extern L_ASET * l_asetCreateFromDna ( L_DNA *da );
+LEPT_DLL extern L_DNA * l_dnaDiffAdjValues ( L_DNA *das );
+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_ok 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_ok l_dnaRemoveDupsByHash ( L_DNA *das, L_DNA **pdad, L_DNAHASH **pdahash );
+LEPT_DLL extern l_ok 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_ok l_dnaFindValByHash ( L_DNA *da, L_DNAHASH *dahash, l_float64 val, l_int32 *pindex );
+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_ok 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_ok 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 ( const 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 ( const l_uint8 *inarray, size_t insize, size_t *poutsize );
+LEPT_DLL extern l_uint8 * decodeAscii85 ( const char *inarray, size_t insize, size_t *poutsize );
+LEPT_DLL extern char * encodeAscii85WithComp ( const l_uint8 *indata, size_t insize, size_t *poutsize );
+LEPT_DLL extern l_uint8 * decodeAscii85WithComp ( const char *instr, size_t insize, size_t *poutsize );
+LEPT_DLL extern char * reformatPacked64 ( const 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 l_int32 pixTRCMapGeneral ( PIX *pixs, PIX *pixm, NUMA *nar, NUMA *nag, NUMA *nab );
+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 * pixMosaicColorShiftRGB ( PIX *pixs, l_float32 roff, l_float32 goff, l_float32 boff, l_float32 delta, l_int32 nincr );
+LEPT_DLL extern PIX * pixColorShiftRGB ( PIX *pixs, l_float32 rfract, l_float32 gfract, l_float32 bfract );
+LEPT_DLL extern PIX * pixDarkenGray ( PIX *pixd, PIX *pixs, l_int32 thresh, l_int32 satlimit );
+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_ok fhmtautogen ( SELA *sela, l_int32 fileindex, const char *filename );
+LEPT_DLL extern l_ok fhmtautogen1 ( SELA *sela, l_int32 fileindex, const char *filename );
+LEPT_DLL extern l_ok 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_ok pixItalicWords ( PIX *pixs, BOXA *boxaw, PIX *pixw, BOXA **pboxa, l_int32 debugflag );
+LEPT_DLL extern PIX * pixOrientCorrect ( PIX *pixs, l_float32 minupconf, l_float32 minratio, l_float32 *pupconf, l_float32 *pleftconf, l_int32 *protation, l_int32 debug );
+LEPT_DLL extern l_ok pixOrientDetect ( PIX *pixs, l_float32 *pupconf, l_float32 *pleftconf, l_int32 mincount, l_int32 debug );
+LEPT_DLL extern l_ok makeOrientDecision ( l_float32 upconf, l_float32 leftconf, l_float32 minupconf, l_float32 minratio, l_int32 *porient, l_int32 debug );
+LEPT_DLL extern l_ok pixUpDownDetect ( PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 npixels, l_int32 debug );
+LEPT_DLL extern l_ok pixMirrorDetect ( PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 debug );
+LEPT_DLL extern l_ok fmorphautogen ( SELA *sela, l_int32 fileindex, const char *filename );
+LEPT_DLL extern l_ok 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 *fpixs );
+LEPT_DLL extern void fpixDestroy ( FPIX **pfpix );
+LEPT_DLL extern l_ok fpixGetDimensions ( FPIX *fpix, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_ok fpixSetDimensions ( FPIX *fpix, l_int32 w, l_int32 h );
+LEPT_DLL extern l_int32 fpixGetWpl ( FPIX *fpix );
+LEPT_DLL extern l_ok fpixSetWpl ( FPIX *fpix, l_int32 wpl );
+LEPT_DLL extern l_int32 fpixGetRefcount ( FPIX *fpix );
+LEPT_DLL extern l_ok fpixChangeRefcount ( FPIX *fpix, l_int32 delta );
+LEPT_DLL extern l_ok fpixGetResolution ( FPIX *fpix, l_int32 *pxres, l_int32 *pyres );
+LEPT_DLL extern l_ok fpixSetResolution ( FPIX *fpix, l_int32 xres, l_int32 yres );
+LEPT_DLL extern l_ok fpixCopyResolution ( FPIX *fpixd, FPIX *fpixs );
+LEPT_DLL extern l_float32 * fpixGetData ( FPIX *fpix );
+LEPT_DLL extern l_ok fpixSetData ( FPIX *fpix, l_float32 *data );
+LEPT_DLL extern l_ok fpixGetPixel ( FPIX *fpix, l_int32 x, l_int32 y, l_float32 *pval );
+LEPT_DLL extern l_ok 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_ok fpixaAddFPix ( FPIXA *fpixa, FPIX *fpix, l_int32 copyflag );
+LEPT_DLL extern l_int32 fpixaGetCount ( FPIXA *fpixa );
+LEPT_DLL extern l_ok fpixaChangeRefcount ( FPIXA *fpixa, l_int32 delta );
+LEPT_DLL extern FPIX * fpixaGetFPix ( FPIXA *fpixa, l_int32 index, l_int32 accesstype );
+LEPT_DLL extern l_ok 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_ok fpixaGetPixel ( FPIXA *fpixa, l_int32 index, l_int32 x, l_int32 y, l_float32 *pval );
+LEPT_DLL extern l_ok 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 *dpixs );
+LEPT_DLL extern void dpixDestroy ( DPIX **pdpix );
+LEPT_DLL extern l_ok dpixGetDimensions ( DPIX *dpix, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_ok dpixSetDimensions ( DPIX *dpix, l_int32 w, l_int32 h );
+LEPT_DLL extern l_int32 dpixGetWpl ( DPIX *dpix );
+LEPT_DLL extern l_ok dpixSetWpl ( DPIX *dpix, l_int32 wpl );
+LEPT_DLL extern l_int32 dpixGetRefcount ( DPIX *dpix );
+LEPT_DLL extern l_ok dpixChangeRefcount ( DPIX *dpix, l_int32 delta );
+LEPT_DLL extern l_ok dpixGetResolution ( DPIX *dpix, l_int32 *pxres, l_int32 *pyres );
+LEPT_DLL extern l_ok dpixSetResolution ( DPIX *dpix, l_int32 xres, l_int32 yres );
+LEPT_DLL extern l_ok dpixCopyResolution ( DPIX *dpixd, DPIX *dpixs );
+LEPT_DLL extern l_float64 * dpixGetData ( DPIX *dpix );
+LEPT_DLL extern l_ok dpixSetData ( DPIX *dpix, l_float64 *data );
+LEPT_DLL extern l_ok dpixGetPixel ( DPIX *dpix, l_int32 x, l_int32 y, l_float64 *pval );
+LEPT_DLL extern l_ok 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 FPIX * fpixReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok fpixWrite ( const char *filename, FPIX *fpix );
+LEPT_DLL extern l_ok fpixWriteStream ( FILE *fp, FPIX *fpix );
+LEPT_DLL extern l_ok fpixWriteMem ( l_uint8 **pdata, size_t *psize, 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 DPIX * dpixReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok dpixWrite ( const char *filename, DPIX *dpix );
+LEPT_DLL extern l_ok dpixWriteStream ( FILE *fp, DPIX *dpix );
+LEPT_DLL extern l_ok dpixWriteMem ( l_uint8 **pdata, size_t *psize, DPIX *dpix );
+LEPT_DLL extern DPIX * dpixEndianByteSwap ( DPIX *dpixd, DPIX *dpixs );
+LEPT_DLL extern l_ok 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_ok fpixGetMin ( FPIX *fpix, l_float32 *pminval, l_int32 *pxminloc, l_int32 *pyminloc );
+LEPT_DLL extern l_ok fpixGetMax ( FPIX *fpix, l_float32 *pmaxval, l_int32 *pxmaxloc, l_int32 *pymaxloc );
+LEPT_DLL extern l_ok dpixGetMin ( DPIX *dpix, l_float64 *pminval, l_int32 *pxminloc, l_int32 *pyminloc );
+LEPT_DLL extern l_ok 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_ok 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_ok dpixAddMultConstant ( DPIX *dpix, l_float64 addc, l_float64 multc );
+LEPT_DLL extern l_ok fpixSetAllArbitrary ( FPIX *fpix, l_float32 inval );
+LEPT_DLL extern l_ok 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_ok 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_ok 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 PIX * pixReadMemGif ( const l_uint8 *cdata, size_t size );
+LEPT_DLL extern l_ok pixWriteStreamGif ( FILE *fp, PIX *pix );
+LEPT_DLL extern l_ok 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_ok gplotAddPlot ( GPLOT *gplot, NUMA *nax, NUMA *nay, l_int32 plotstyle, const char *plotlabel );
+LEPT_DLL extern l_ok gplotSetScaling ( GPLOT *gplot, l_int32 scaling );
+LEPT_DLL extern PIX * gplotMakeOutputPix ( GPLOT *gplot );
+LEPT_DLL extern l_ok gplotMakeOutput ( GPLOT *gplot );
+LEPT_DLL extern l_ok gplotGenCommandFile ( GPLOT *gplot );
+LEPT_DLL extern l_ok gplotGenDataFiles ( GPLOT *gplot );
+LEPT_DLL extern l_ok gplotSimple1 ( NUMA *na, l_int32 outformat, const char *outroot, const char *title );
+LEPT_DLL extern l_ok gplotSimple2 ( NUMA *na1, NUMA *na2, l_int32 outformat, const char *outroot, const char *title );
+LEPT_DLL extern l_ok gplotSimpleN ( NUMAA *naa, l_int32 outformat, const char *outroot, const char *title );
+LEPT_DLL extern PIX * gplotSimplePix1 ( NUMA *na, const char *title );
+LEPT_DLL extern PIX * gplotSimplePix2 ( NUMA *na1, NUMA *na2, const char *title );
+LEPT_DLL extern PIX * gplotSimplePixN ( NUMAA *naa, const char *title );
+LEPT_DLL extern GPLOT * gplotSimpleXY1 ( NUMA *nax, NUMA *nay, l_int32 plotstyle, l_int32 outformat, const char *outroot, const char *title );
+LEPT_DLL extern GPLOT * gplotSimpleXY2 ( NUMA *nax, NUMA *nay1, NUMA *nay2, l_int32 plotstyle, l_int32 outformat, const char *outroot, const char *title );
+LEPT_DLL extern GPLOT * gplotSimpleXYN ( NUMA *nax, NUMAA *naay, l_int32 plotstyle, l_int32 outformat, const char *outroot, const char *title );
+LEPT_DLL extern PIX * gplotGeneralPix1 ( NUMA *na, l_int32 plotstyle, const char *rootname, const char *title, const char *xlabel, const char *ylabel );
+LEPT_DLL extern PIX * gplotGeneralPix2 ( NUMA *na1, NUMA *na2, l_int32 plotstyle, const char *rootname, const char *title, const char *xlabel, const char *ylabel );
+LEPT_DLL extern PIX * gplotGeneralPixN ( NUMA *nax, NUMAA *naay, l_int32 plotstyle, const char *rootname, const char *title, const char *xlabel, const char *ylabel );
+LEPT_DLL extern GPLOT * gplotRead ( const char *filename );
+LEPT_DLL extern l_ok 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_ok locatePtRadially ( l_int32 xr, l_int32 yr, l_float64 dist, l_float64 radang, l_float64 *px, l_float64 *py );
+LEPT_DLL extern l_ok 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_ok 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_ok pixRenderPta ( PIX *pix, PTA *pta, l_int32 op );
+LEPT_DLL extern l_ok pixRenderPtaArb ( PIX *pix, PTA *pta, l_uint8 rval, l_uint8 gval, l_uint8 bval );
+LEPT_DLL extern l_ok pixRenderPtaBlend ( PIX *pix, PTA *pta, l_uint8 rval, l_uint8 gval, l_uint8 bval, l_float32 fract );
+LEPT_DLL extern l_ok 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_ok 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_ok 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_ok pixRenderBox ( PIX *pix, BOX *box, l_int32 width, l_int32 op );
+LEPT_DLL extern l_ok pixRenderBoxArb ( PIX *pix, BOX *box, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval );
+LEPT_DLL extern l_ok pixRenderBoxBlend ( PIX *pix, BOX *box, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval, l_float32 fract );
+LEPT_DLL extern l_ok pixRenderBoxa ( PIX *pix, BOXA *boxa, l_int32 width, l_int32 op );
+LEPT_DLL extern l_ok pixRenderBoxaArb ( PIX *pix, BOXA *boxa, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval );
+LEPT_DLL extern l_ok 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_ok pixRenderHashBox ( PIX *pix, BOX *box, l_int32 spacing, l_int32 width, l_int32 orient, l_int32 outline, l_int32 op );
+LEPT_DLL extern l_ok 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_ok 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_ok pixRenderHashMaskArb ( PIX *pix, PIX *pixm, l_int32 x, l_int32 y, 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_ok pixRenderHashBoxa ( PIX *pix, BOXA *boxa, l_int32 spacing, l_int32 width, l_int32 orient, l_int32 outline, l_int32 op );
+LEPT_DLL extern l_ok 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_ok 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_ok pixRenderPolyline ( PIX *pix, PTA *ptas, l_int32 width, l_int32 op, l_int32 closeflag );
+LEPT_DLL extern l_ok pixRenderPolylineArb ( PIX *pix, PTA *ptas, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval, l_int32 closeflag );
+LEPT_DLL extern l_ok 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_ok 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 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 PIX * pixThresholdToBinary ( PIX *pixs, 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 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 * 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_ok makeGrayQuantTableArb ( NUMA *na, l_int32 outdepth, l_int32 **ptab, 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 L_HEAP * lheapCreate ( l_int32 n, l_int32 direction );
+LEPT_DLL extern void lheapDestroy ( L_HEAP **plh, l_int32 freeflag );
+LEPT_DLL extern l_ok 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 void * lheapGetElement ( L_HEAP *lh, l_int32 index );
+LEPT_DLL extern l_ok lheapSort ( L_HEAP *lh );
+LEPT_DLL extern l_ok lheapSortStrictOrder ( L_HEAP *lh );
+LEPT_DLL extern l_ok 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_ok jbAddPages ( JBCLASSER *classer, SARRAY *safiles );
+LEPT_DLL extern l_ok jbAddPage ( JBCLASSER *classer, PIX *pixs );
+LEPT_DLL extern l_ok jbAddPageComponents ( JBCLASSER *classer, PIX *pixs, BOXA *boxas, PIXA *pixas );
+LEPT_DLL extern l_ok 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_ok jbClassifyCorrelation ( JBCLASSER *classer, BOXA *boxa, PIXA *pixas );
+LEPT_DLL extern l_ok jbGetComponents ( PIX *pixs, l_int32 components, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxad, PIXA **ppixad );
+LEPT_DLL extern l_ok pixWordMaskByDilation ( PIX *pixs, PIX **ppixm, l_int32 *psize, PIXA *pixadb );
+LEPT_DLL extern l_ok pixWordBoxesByDilation ( PIX *pixs, l_int32 minwidth, l_int32 minheight, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxa, l_int32 *psize, PIXA *pixadb );
+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_ok 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_ok jbGetULCorners ( JBCLASSER *classer, PIX *pixs, BOXA *boxa );
+LEPT_DLL extern l_ok jbGetLLCorners ( JBCLASSER *classer );
+LEPT_DLL extern l_ok readHeaderJp2k ( const char *filename, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp );
+LEPT_DLL extern l_ok freadHeaderJp2k ( FILE *fp, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp );
+LEPT_DLL extern l_ok 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_ok pixWriteJp2k ( const char *filename, PIX *pix, l_int32 quality, l_int32 nlevels, l_int32 hint, l_int32 debug );
+LEPT_DLL extern l_ok 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_ok 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_ok readHeaderJpeg ( const char *filename, l_int32 *pw, l_int32 *ph, l_int32 *pspp, l_int32 *pycck, l_int32 *pcmyk );
+LEPT_DLL extern l_ok 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_ok pixWriteJpeg ( const char *filename, PIX *pix, l_int32 quality, l_int32 progressive );
+LEPT_DLL extern l_ok 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_ok 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_ok readResolutionMemJpeg ( const l_uint8 *data, size_t size, l_int32 *pxres, l_int32 *pyres );
+LEPT_DLL extern l_ok pixWriteMemJpeg ( l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 quality, l_int32 progressive );
+LEPT_DLL extern l_ok 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_ok kernelGetElement ( L_KERNEL *kel, l_int32 row, l_int32 col, l_float32 *pval );
+LEPT_DLL extern l_ok kernelSetElement ( L_KERNEL *kel, l_int32 row, l_int32 col, l_float32 val );
+LEPT_DLL extern l_ok kernelGetParameters ( L_KERNEL *kel, l_int32 *psy, l_int32 *psx, l_int32 *pcy, l_int32 *pcx );
+LEPT_DLL extern l_ok kernelSetOrigin ( L_KERNEL *kel, l_int32 cy, l_int32 cx );
+LEPT_DLL extern l_ok kernelGetSum ( L_KERNEL *kel, l_float32 *psum );
+LEPT_DLL extern l_ok 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_ok kernelWrite ( const char *fname, L_KERNEL *kel );
+LEPT_DLL extern l_ok 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 halfh, l_int32 halfw, l_float32 stdev, l_float32 max );
+LEPT_DLL extern l_ok makeGaussianKernelSep ( l_int32 halfh, l_int32 halfw, l_float32 stdev, l_float32 max, L_KERNEL **pkelx, L_KERNEL **pkely );
+LEPT_DLL extern L_KERNEL * makeDoGKernel ( l_int32 halfh, l_int32 halfw, l_float32 stdev, l_float32 ratio );
+LEPT_DLL extern char * getImagelibVersions ( void );
+LEPT_DLL extern void listDestroy ( DLLIST **phead );
+LEPT_DLL extern l_ok listAddToHead ( DLLIST **phead, void *data );
+LEPT_DLL extern l_ok listAddToTail ( DLLIST **phead, DLLIST **ptail, void *data );
+LEPT_DLL extern l_ok listInsertBefore ( DLLIST **phead, DLLIST *elem, void *data );
+LEPT_DLL extern l_ok 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_ok listReverse ( DLLIST **phead );
+LEPT_DLL extern l_ok 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 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_ok 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_ok 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 * pixaExtendByMorph ( PIXA *pixas, l_int32 type, l_int32 niters, SEL *sel, l_int32 include );
+LEPT_DLL extern PIXA * pixaExtendByScaling ( PIXA *pixas, NUMA *nasc, l_int32 type, 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_ok 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_ok 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_ok numaEmpty ( NUMA *na );
+LEPT_DLL extern l_ok numaAddNumber ( NUMA *na, l_float32 val );
+LEPT_DLL extern l_ok numaInsertNumber ( NUMA *na, l_int32 index, l_float32 val );
+LEPT_DLL extern l_ok numaRemoveNumber ( NUMA *na, l_int32 index );
+LEPT_DLL extern l_ok numaReplaceNumber ( NUMA *na, l_int32 index, l_float32 val );
+LEPT_DLL extern l_int32 numaGetCount ( NUMA *na );
+LEPT_DLL extern l_ok numaSetCount ( NUMA *na, l_int32 newcount );
+LEPT_DLL extern l_ok numaGetFValue ( NUMA *na, l_int32 index, l_float32 *pval );
+LEPT_DLL extern l_ok numaGetIValue ( NUMA *na, l_int32 index, l_int32 *pival );
+LEPT_DLL extern l_ok numaSetValue ( NUMA *na, l_int32 index, l_float32 val );
+LEPT_DLL extern l_ok 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_ok numaChangeRefcount ( NUMA *na, l_int32 delta );
+LEPT_DLL extern l_ok numaGetParameters ( NUMA *na, l_float32 *pstartx, l_float32 *pdelx );
+LEPT_DLL extern l_ok numaSetParameters ( NUMA *na, l_float32 startx, l_float32 delx );
+LEPT_DLL extern l_ok 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 NUMA * numaReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok numaWriteDebug ( const char *filename, NUMA *na );
+LEPT_DLL extern l_ok numaWrite ( const char *filename, NUMA *na );
+LEPT_DLL extern l_ok numaWriteStream ( FILE *fp, NUMA *na );
+LEPT_DLL extern l_ok numaWriteStderr ( NUMA *na );
+LEPT_DLL extern l_ok numaWriteMem ( l_uint8 **pdata, size_t *psize, 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_ok numaaTruncate ( NUMAA *naa );
+LEPT_DLL extern void numaaDestroy ( NUMAA **pnaa );
+LEPT_DLL extern l_ok 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_ok numaaReplaceNuma ( NUMAA *naa, l_int32 index, NUMA *na );
+LEPT_DLL extern l_ok numaaGetValue ( NUMAA *naa, l_int32 i, l_int32 j, l_float32 *pfval, l_int32 *pival );
+LEPT_DLL extern l_ok 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 NUMAA * numaaReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok numaaWrite ( const char *filename, NUMAA *naa );
+LEPT_DLL extern l_ok numaaWriteStream ( FILE *fp, NUMAA *naa );
+LEPT_DLL extern l_ok numaaWriteMem ( l_uint8 **pdata, size_t *psize, 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_ok numaAddToNumber ( NUMA *na, l_int32 index, l_float32 val );
+LEPT_DLL extern l_ok numaGetMin ( NUMA *na, l_float32 *pminval, l_int32 *piminloc );
+LEPT_DLL extern l_ok numaGetMax ( NUMA *na, l_float32 *pmaxval, l_int32 *pimaxloc );
+LEPT_DLL extern l_ok numaGetSum ( NUMA *na, l_float32 *psum );
+LEPT_DLL extern NUMA * numaGetPartialSums ( NUMA *na );
+LEPT_DLL extern l_ok numaGetSumOnInterval ( NUMA *na, l_int32 first, l_int32 last, l_float32 *psum );
+LEPT_DLL extern l_ok numaHasOnlyIntegers ( NUMA *na, 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_ok numaCountNonzeroRuns ( NUMA *na, l_int32 *pcount );
+LEPT_DLL extern l_ok numaGetNonzeroRange ( NUMA *na, l_float32 eps, l_int32 *pfirst, l_int32 *plast );
+LEPT_DLL extern l_ok 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_ok numaInterpolateEqxVal ( l_float32 startx, l_float32 deltax, NUMA *nay, l_int32 type, l_float32 xval, l_float32 *pyval );
+LEPT_DLL extern l_ok numaInterpolateArbxVal ( NUMA *nax, NUMA *nay, l_int32 type, l_float32 xval, l_float32 *pyval );
+LEPT_DLL extern l_ok 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_ok 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_ok numaFitMax ( NUMA *na, l_float32 *pmaxval, NUMA *naloc, l_float32 *pmaxloc );
+LEPT_DLL extern l_ok numaDifferentiateInterval ( NUMA *nax, NUMA *nay, l_float32 x0, l_float32 x1, l_int32 npts, NUMA **pnadx, NUMA **pnady );
+LEPT_DLL extern l_ok numaIntegrateInterval ( NUMA *nax, NUMA *nay, l_float32 x0, l_float32 x1, l_int32 npts, l_float32 *psum );
+LEPT_DLL extern l_ok 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_ok numaSortPair ( NUMA *nax, NUMA *nay, l_int32 sortorder, NUMA **pnasx, NUMA **pnasy );
+LEPT_DLL extern NUMA * numaInvertMap ( NUMA *nas );
+LEPT_DLL extern l_ok numaAddSorted ( NUMA *na, l_float32 val );
+LEPT_DLL extern l_ok numaFindSortedLoc ( NUMA *na, l_float32 val, l_int32 *pindex );
+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_ok numaGetRankValue ( NUMA *na, l_float32 fract, NUMA *nasort, l_int32 usebins, l_float32 *pval );
+LEPT_DLL extern l_ok numaGetMedian ( NUMA *na, l_float32 *pval );
+LEPT_DLL extern l_ok numaGetBinnedMedian ( NUMA *na, l_int32 *pval );
+LEPT_DLL extern l_ok numaGetMeanDevFromMedian ( NUMA *na, l_float32 med, l_float32 *pdev );
+LEPT_DLL extern l_ok numaGetMedianDevFromMedian ( NUMA *na, l_float32 *pmed, l_float32 *pdev );
+LEPT_DLL extern l_ok numaGetMode ( NUMA *na, l_float32 *pval, l_int32 *pcount );
+LEPT_DLL extern l_ok numaJoin ( NUMA *nad, NUMA *nas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern l_ok numaaJoin ( NUMAA *naad, NUMAA *naas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern NUMA * numaaFlattenToNuma ( NUMAA *naa );
+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_ok numaSimpleStats ( NUMA *na, l_int32 first, l_int32 last, l_float32 *pmean, l_float32 *pvar, l_float32 *prvar );
+LEPT_DLL extern l_ok 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_ok 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_ok 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_ok 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_ok 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_ok numaMakeRankFromHistogram ( l_float32 startx, l_float32 deltax, NUMA *nasy, l_int32 npts, NUMA **pnax, NUMA **pnay );
+LEPT_DLL extern l_ok numaHistogramGetRankFromVal ( NUMA *na, l_float32 rval, l_float32 *prank );
+LEPT_DLL extern l_ok numaHistogramGetValFromRank ( NUMA *na, l_float32 rank, l_float32 *prval );
+LEPT_DLL extern l_ok numaDiscretizeSortedInBins ( NUMA *na, l_int32 nbins, NUMA **pnabinval );
+LEPT_DLL extern l_ok numaDiscretizeHistoInBins ( NUMA *na, l_int32 nbins, NUMA **pnabinval, NUMA **pnarank );
+LEPT_DLL extern l_ok numaGetRankBinValues ( NUMA *na, l_int32 nbins, NUMA **pnam );
+LEPT_DLL extern NUMA * numaGetUniformBinSizes ( l_int32 ntotal, l_int32 nbins );
+LEPT_DLL extern l_ok 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_ok grayHistogramsToEMD ( NUMAA *naa1, NUMAA *naa2, NUMA **pnad );
+LEPT_DLL extern l_ok numaEarthMoverDistance ( NUMA *na1, NUMA *na2, l_float32 *pdist );
+LEPT_DLL extern l_ok 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, NUMA **pnav );
+LEPT_DLL extern l_ok numaFindLocForThreshold ( NUMA *na, l_int32 skip, l_int32 *pthresh, l_float32 *pfract );
+LEPT_DLL extern l_ok numaCountReversals ( NUMA *nas, l_float32 minreversal, l_int32 *pnr, l_float32 *prd );
+LEPT_DLL extern l_ok 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_ok 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_ok 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_ok pixGetRegionsBinary ( PIX *pixs, PIX **ppixhm, PIX **ppixtm, PIX **ppixtb, PIXA *pixadb );
+LEPT_DLL extern PIX * pixGenHalftoneMask ( PIX *pixs, PIX **ppixtext, l_int32 *phtfound, l_int32 debug );
+LEPT_DLL extern PIX * pixGenerateHalftoneMask ( PIX *pixs, PIX **ppixtext, l_int32 *phtfound, PIXA *pixadb );
+LEPT_DLL extern PIX * pixGenTextlineMask ( PIX *pixs, PIX **ppixvws, l_int32 *ptlfound, PIXA *pixadb );
+LEPT_DLL extern PIX * pixGenTextblockMask ( PIX *pixs, PIX *pixvws, PIXA *pixadb );
+LEPT_DLL extern BOX * pixFindPageForeground ( PIX *pixs, l_int32 threshold, l_int32 mindist, l_int32 erasedist, l_int32 showmorph, PIXAC *pixac );
+LEPT_DLL extern l_ok 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, l_int32 adjw, l_int32 adjh, PIXA *pixadb );
+LEPT_DLL extern PIXA * pixExtractRawTextlines ( PIX *pixs, l_int32 maxw, l_int32 maxh, l_int32 adjw, l_int32 adjh, PIXA *pixadb );
+LEPT_DLL extern l_ok pixCountTextColumns ( PIX *pixs, l_float32 deltafract, l_float32 peakfract, l_float32 clipfract, l_int32 *pncols, PIXA *pixadb );
+LEPT_DLL extern l_ok pixDecideIfText ( PIX *pixs, BOX *box, l_int32 *pistext, PIXA *pixadb );
+LEPT_DLL extern l_ok pixFindThreshFgExtent ( PIX *pixs, l_int32 thresh, l_int32 *ptop, l_int32 *pbot );
+LEPT_DLL extern l_ok pixDecideIfTable ( PIX *pixs, BOX *box, l_int32 orient, l_int32 *pscore, PIXA *pixadb );
+LEPT_DLL extern PIX * pixPrepare1bpp ( PIX *pixs, BOX *box, l_float32 cropfract, l_int32 outres );
+LEPT_DLL extern l_ok pixEstimateBackground ( PIX *pixs, l_int32 darkthresh, l_float32 edgecrop, l_int32 *pbg );
+LEPT_DLL extern l_ok pixFindLargeRectangles ( PIX *pixs, l_int32 polarity, l_int32 nrect, BOXA **pboxa, PIX **ppixdb );
+LEPT_DLL extern l_ok pixFindLargestRectangle ( PIX *pixs, l_int32 polarity, BOX **pbox, PIX **ppixdb );
+LEPT_DLL extern BOX * pixFindRectangleInCC ( PIX *pixs, BOX *boxs, l_float32 fract, l_int32 dir, l_int32 select, l_int32 debug );
+LEPT_DLL extern PIX * pixAutoPhotoinvert ( PIX *pixs, l_int32 thresh, PIX **ppixm, PIXA *pixadb );
+LEPT_DLL extern l_ok pixSetSelectCmap ( PIX *pixs, BOX *box, l_int32 sindex, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_ok pixColorGrayRegionsCmap ( PIX *pixs, BOXA *boxa, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_ok pixColorGrayCmap ( PIX *pixs, BOX *box, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_ok pixColorGrayMaskedCmap ( PIX *pixs, PIX *pixm, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_ok addColorizedGrayToCmap ( PIXCMAP *cmap, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval, NUMA **pna );
+LEPT_DLL extern l_ok 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_ok 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 l_ok partifyFiles ( const char *dirname, const char *substr, l_int32 nparts, const char *outroot, const char *debugfile );
+LEPT_DLL extern l_ok partifyPixac ( PIXAC *pixac, l_int32 nparts, const char *outroot, PIXA *pixadb );
+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_ok 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_ok 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_ok 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_ok selectDefaultPdfEncoding ( PIX *pix, l_int32 *ptype );
+LEPT_DLL extern l_ok convertUnscaledFilesToPdf ( const char *dirname, const char *substr, const char *title, const char *fileout );
+LEPT_DLL extern l_ok saConvertUnscaledFilesToPdf ( SARRAY *sa, const char *title, const char *fileout );
+LEPT_DLL extern l_ok saConvertUnscaledFilesToPdfData ( SARRAY *sa, const char *title, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_ok convertUnscaledToPdfData ( const char *fname, const char *title, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_ok 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_ok 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_ok 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_ok 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_ok 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_ok 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_ok 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_ok pixWriteStreamPdf ( FILE *fp, PIX *pix, l_int32 res, const char *title );
+LEPT_DLL extern l_ok pixWriteMemPdf ( l_uint8 **pdata, size_t *pnbytes, PIX *pix, l_int32 res, const char *title );
+LEPT_DLL extern l_ok 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_ok 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_ok 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_ok 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_ok 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_ok concatenatePdf ( const char *dirname, const char *substr, const char *fileout );
+LEPT_DLL extern l_ok saConcatenatePdf ( SARRAY *sa, const char *fileout );
+LEPT_DLL extern l_ok ptraConcatenatePdf ( L_PTRA *pa, const char *fileout );
+LEPT_DLL extern l_ok concatenatePdfToData ( const char *dirname, const char *substr, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_ok saConcatenatePdfToData ( SARRAY *sa, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_ok 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_ok ptraConcatenatePdfToData ( L_PTRA *pa_data, SARRAY *sa, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_ok convertTiffMultipageToPdf ( const char *filein, const char *fileout );
+LEPT_DLL extern l_ok 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_COMP_DATA * l_generateJpegDataMem ( l_uint8 *data, size_t nbytes, l_int32 ascii85flag );
+LEPT_DLL extern l_ok l_generateCIData ( const char *fname, l_int32 type, l_int32 quality, l_int32 ascii85, L_COMP_DATA **pcid );
+LEPT_DLL extern l_ok 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_ok 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 ( alloc_fn allocator, dealloc_fn deallocator );
+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 ( const PIX *pixs );
+LEPT_DLL extern PIX * pixCreateTemplateNoInit ( const PIX *pixs );
+LEPT_DLL extern PIX * pixCreateWithCmap ( l_int32 width, l_int32 height, l_int32 depth, l_int32 initcolor );
+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, const PIX *pixs );
+LEPT_DLL extern l_ok pixResizeImageData ( PIX *pixd, const PIX *pixs );
+LEPT_DLL extern l_ok pixCopyColormap ( PIX *pixd, const PIX *pixs );
+LEPT_DLL extern l_int32 pixSizesEqual ( const PIX *pix1, const PIX *pix2 );
+LEPT_DLL extern l_ok pixTransferAllData ( PIX *pixd, PIX **ppixs, l_int32 copytext, l_int32 copyformat );
+LEPT_DLL extern l_ok pixSwapAndDestroy ( PIX **ppixd, PIX **ppixs );
+LEPT_DLL extern l_int32 pixGetWidth ( const PIX *pix );
+LEPT_DLL extern l_int32 pixSetWidth ( PIX *pix, l_int32 width );
+LEPT_DLL extern l_int32 pixGetHeight ( const PIX *pix );
+LEPT_DLL extern l_int32 pixSetHeight ( PIX *pix, l_int32 height );
+LEPT_DLL extern l_int32 pixGetDepth ( const PIX *pix );
+LEPT_DLL extern l_int32 pixSetDepth ( PIX *pix, l_int32 depth );
+LEPT_DLL extern l_ok pixGetDimensions ( const PIX *pix, l_int32 *pw, l_int32 *ph, l_int32 *pd );
+LEPT_DLL extern l_ok pixSetDimensions ( PIX *pix, l_int32 w, l_int32 h, l_int32 d );
+LEPT_DLL extern l_ok pixCopyDimensions ( PIX *pixd, const PIX *pixs );
+LEPT_DLL extern l_int32 pixGetSpp ( const PIX *pix );
+LEPT_DLL extern l_int32 pixSetSpp ( PIX *pix, l_int32 spp );
+LEPT_DLL extern l_ok pixCopySpp ( PIX *pixd, const PIX *pixs );
+LEPT_DLL extern l_int32 pixGetWpl ( const PIX *pix );
+LEPT_DLL extern l_int32 pixSetWpl ( PIX *pix, l_int32 wpl );
+LEPT_DLL extern l_int32 pixGetRefcount ( const PIX *pix );
+LEPT_DLL extern l_int32 pixChangeRefcount ( PIX *pix, l_int32 delta );
+LEPT_DLL extern l_int32 pixGetXRes ( const PIX *pix );
+LEPT_DLL extern l_int32 pixSetXRes ( PIX *pix, l_int32 res );
+LEPT_DLL extern l_int32 pixGetYRes ( const PIX *pix );
+LEPT_DLL extern l_int32 pixSetYRes ( PIX *pix, l_int32 res );
+LEPT_DLL extern l_ok pixGetResolution ( const PIX *pix, l_int32 *pxres, l_int32 *pyres );
+LEPT_DLL extern l_ok pixSetResolution ( PIX *pix, l_int32 xres, l_int32 yres );
+LEPT_DLL extern l_int32 pixCopyResolution ( PIX *pixd, const PIX *pixs );
+LEPT_DLL extern l_int32 pixScaleResolution ( PIX *pix, l_float32 xscale, l_float32 yscale );
+LEPT_DLL extern l_int32 pixGetInputFormat ( const PIX *pix );
+LEPT_DLL extern l_int32 pixSetInputFormat ( PIX *pix, l_int32 informat );
+LEPT_DLL extern l_int32 pixCopyInputFormat ( PIX *pixd, const PIX *pixs );
+LEPT_DLL extern l_int32 pixSetSpecial ( PIX *pix, l_int32 special );
+LEPT_DLL extern char * pixGetText ( PIX *pix );
+LEPT_DLL extern l_ok pixSetText ( PIX *pix, const char *textstring );
+LEPT_DLL extern l_ok pixAddText ( PIX *pix, const char *textstring );
+LEPT_DLL extern l_int32 pixCopyText ( PIX *pixd, const PIX *pixs );
+LEPT_DLL extern l_uint8 * pixGetTextCompNew ( PIX *pix, size_t *psize );
+LEPT_DLL extern l_ok pixSetTextCompNew ( PIX *pix, const l_uint8 *data, size_t size );
+LEPT_DLL extern PIXCMAP * pixGetColormap ( PIX *pix );
+LEPT_DLL extern l_ok pixSetColormap ( PIX *pix, PIXCMAP *colormap );
+LEPT_DLL extern l_ok 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_ok pixPrintStreamInfo ( FILE *fp, const PIX *pix, const char *text );
+LEPT_DLL extern l_ok pixGetPixel ( PIX *pix, l_int32 x, l_int32 y, l_uint32 *pval );
+LEPT_DLL extern l_ok pixSetPixel ( PIX *pix, l_int32 x, l_int32 y, l_uint32 val );
+LEPT_DLL extern l_ok pixGetRGBPixel ( PIX *pix, l_int32 x, l_int32 y, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern l_ok pixSetRGBPixel ( PIX *pix, l_int32 x, l_int32 y, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_ok pixSetCmapPixel ( PIX *pix, l_int32 x, l_int32 y, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_ok pixGetRandomPixel ( PIX *pix, l_uint32 *pval, l_int32 *px, l_int32 *py );
+LEPT_DLL extern l_ok pixClearPixel ( PIX *pix, l_int32 x, l_int32 y );
+LEPT_DLL extern l_ok 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_ok pixGetBlackOrWhiteVal ( PIX *pixs, l_int32 op, l_uint32 *pval );
+LEPT_DLL extern l_ok pixClearAll ( PIX *pix );
+LEPT_DLL extern l_ok pixSetAll ( PIX *pix );
+LEPT_DLL extern l_ok pixSetAllGray ( PIX *pix, l_int32 grayval );
+LEPT_DLL extern l_ok pixSetAllArbitrary ( PIX *pix, l_uint32 val );
+LEPT_DLL extern l_ok pixSetBlackOrWhite ( PIX *pixs, l_int32 op );
+LEPT_DLL extern l_ok pixSetComponentArbitrary ( PIX *pix, l_int32 comp, l_int32 val );
+LEPT_DLL extern l_ok pixClearInRect ( PIX *pix, BOX *box );
+LEPT_DLL extern l_ok pixSetInRect ( PIX *pix, BOX *box );
+LEPT_DLL extern l_ok pixSetInRectArbitrary ( PIX *pix, BOX *box, l_uint32 val );
+LEPT_DLL extern l_ok pixBlendInRect ( PIX *pixs, BOX *box, l_uint32 val, l_float32 fract );
+LEPT_DLL extern l_ok pixSetPadBits ( PIX *pix, l_int32 val );
+LEPT_DLL extern l_ok pixSetPadBitsBand ( PIX *pix, l_int32 by, l_int32 bh, l_int32 val );
+LEPT_DLL extern l_ok pixSetOrClearBorder ( PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot, l_int32 op );
+LEPT_DLL extern l_ok pixSetBorderVal ( PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot, l_uint32 val );
+LEPT_DLL extern l_ok pixSetBorderRingVal ( PIX *pixs, l_int32 dist, l_uint32 val );
+LEPT_DLL extern l_ok 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_ok 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_ok pixSetRGBComponent ( PIX *pixd, PIX *pixs, l_int32 comp );
+LEPT_DLL extern PIX * pixGetRGBComponentCmap ( PIX *pixs, l_int32 comp );
+LEPT_DLL extern l_ok pixCopyRGBComponent ( PIX *pixd, PIX *pixs, l_int32 comp );
+LEPT_DLL extern l_ok composeRGBPixel ( l_int32 rval, l_int32 gval, l_int32 bval, l_uint32 *ppixel );
+LEPT_DLL extern l_ok 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_ok pixGetRGBLine ( PIX *pixs, l_int32 row, l_uint8 *bufr, l_uint8 *bufg, l_uint8 *bufb );
+LEPT_DLL extern l_ok setLineDataVal ( l_uint32 *line, l_int32 j, l_int32 d, l_uint32 val );
+LEPT_DLL extern PIX * pixEndianByteSwapNew ( PIX *pixs );
+LEPT_DLL extern l_ok 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_ok pixEndianTwoByteSwap ( PIX *pixs );
+LEPT_DLL extern l_ok pixGetRasterData ( PIX *pixs, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_ok pixInferResolution ( PIX *pix, l_float32 longside, l_int32 *pres );
+LEPT_DLL extern l_ok pixAlphaIsOpaque ( PIX *pix, l_int32 *popaque );
+LEPT_DLL extern l_uint8 ** pixSetupByteProcessing ( PIX *pix, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_ok pixCleanupByteProcessing ( PIX *pix, l_uint8 **lineptrs );
+LEPT_DLL extern void l_setAlphaMaskBorder ( l_float32 val1, l_float32 val2 );
+LEPT_DLL extern l_ok pixSetMasked ( PIX *pixd, PIX *pixm, l_uint32 val );
+LEPT_DLL extern l_ok pixSetMaskedGeneral ( PIX *pixd, PIX *pixm, l_uint32 val, l_int32 x, l_int32 y );
+LEPT_DLL extern l_ok pixCombineMasked ( PIX *pixd, PIX *pixs, PIX *pixm );
+LEPT_DLL extern l_ok pixCombineMaskedGeneral ( PIX *pixd, PIX *pixs, PIX *pixm, l_int32 x, l_int32 y );
+LEPT_DLL extern l_ok pixPaintThroughMask ( PIX *pixd, PIX *pixm, l_int32 x, l_int32 y, l_uint32 val );
+LEPT_DLL extern PIX * pixCopyWithBoxa ( PIX *pixs, BOXA *boxa, l_int32 background );
+LEPT_DLL extern l_ok 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 * pixMakeMaskFromVal ( PIX *pixs, l_int32 val );
+LEPT_DLL extern PIX * pixMakeMaskFromLUT ( PIX *pixs, l_int32 *tab );
+LEPT_DLL extern PIX * pixMakeArbMaskFromRGB ( PIX *pixs, l_float32 rc, l_float32 gc, l_float32 bc, l_float32 thresh );
+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_ok pixGetColorNearMaskBoundary ( PIX *pixs, PIX *pixm, BOX *box, l_int32 dist, l_uint32 *pval, l_int32 debug );
+LEPT_DLL extern PIX * pixDisplaySelectedPixels ( PIX *pixs, PIX *pixm, SEL *sel, l_uint32 val );
+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_ok pixZero ( PIX *pix, l_int32 *pempty );
+LEPT_DLL extern l_ok pixForegroundFraction ( PIX *pix, l_float32 *pfract );
+LEPT_DLL extern NUMA * pixaCountPixels ( PIXA *pixa );
+LEPT_DLL extern l_ok pixCountPixels ( PIX *pixs, l_int32 *pcount, l_int32 *tab8 );
+LEPT_DLL extern l_ok pixCountPixelsInRect ( PIX *pixs, BOX *box, 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_ok 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_ok 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_ok pixAverageInRect ( PIX *pixs, PIX *pixm, BOX *box, l_int32 minval, l_int32 maxval, l_int32 subsamp, l_float32 *pave );
+LEPT_DLL extern l_ok pixAverageInRectRGB ( PIX *pixs, PIX *pixm, BOX *box, l_int32 subsamp, l_uint32 *pave );
+LEPT_DLL extern NUMA * pixVarianceByRow ( PIX *pix, BOX *box );
+LEPT_DLL extern NUMA * pixVarianceByColumn ( PIX *pix, BOX *box );
+LEPT_DLL extern l_ok 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_ok pixAbsDiffInRect ( PIX *pix, BOX *box, l_int32 dir, l_float32 *pabsdiff );
+LEPT_DLL extern l_ok 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_ok 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_ok pixGetColorHistogram ( PIX *pixs, l_int32 factor, NUMA **pnar, NUMA **pnag, NUMA **pnab );
+LEPT_DLL extern l_ok 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_ok pixCountRGBColorsByHash ( PIX *pixs, l_int32 *pncolors );
+LEPT_DLL extern l_ok pixCountRGBColors ( PIX *pixs, l_int32 factor, l_int32 *pncolors );
+LEPT_DLL extern L_AMAP * pixGetColorAmapHistogram ( PIX *pixs, l_int32 factor );
+LEPT_DLL extern l_int32 amapGetCountForColor ( L_AMAP *amap, l_uint32 val );
+LEPT_DLL extern l_ok pixGetRankValue ( PIX *pixs, l_int32 factor, l_float32 rank, l_uint32 *pvalue );
+LEPT_DLL extern l_ok 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_ok 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_ok pixGetPixelAverage ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 factor, l_uint32 *pval );
+LEPT_DLL extern l_ok pixGetPixelStats ( PIX *pixs, l_int32 factor, l_int32 type, l_uint32 *pvalue );
+LEPT_DLL extern l_ok 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_ok pixGetAverageMasked ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 factor, l_int32 type, l_float32 *pval );
+LEPT_DLL extern l_ok 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_ok pixGetRangeValues ( PIX *pixs, l_int32 factor, l_int32 color, l_int32 *pminval, l_int32 *pmaxval );
+LEPT_DLL extern l_ok 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_ok pixGetMaxValueInRect ( PIX *pixs, BOX *box, l_uint32 *pmaxval, l_int32 *pxmax, l_int32 *pymax );
+LEPT_DLL extern l_ok pixGetMaxColorIndex ( PIX *pixs, l_int32 *pmaxindex );
+LEPT_DLL extern l_ok 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_ok pixGetRankColorArray ( PIX *pixs, l_int32 nbins, l_int32 type, l_int32 factor, l_uint32 **pcarray, PIXA *pixadb, l_int32 fontsize );
+LEPT_DLL extern l_ok pixGetBinnedColor ( PIX *pixs, PIX *pixg, l_int32 factor, l_int32 nbins, l_uint32 **pcarray, PIXA *pixadb );
+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_ok pixaExtractColumnFromEachPix ( PIXA *pixa, l_int32 col, PIX *pixd );
+LEPT_DLL extern l_ok pixGetRowStats ( PIX *pixs, l_int32 type, l_int32 nbins, l_int32 thresh, l_float32 *colvect );
+LEPT_DLL extern l_ok pixGetColumnStats ( PIX *pixs, l_int32 type, l_int32 nbins, l_int32 thresh, l_float32 *rowvect );
+LEPT_DLL extern l_ok pixSetPixelColumn ( PIX *pix, l_int32 col, l_float32 *colvect );
+LEPT_DLL extern l_ok pixThresholdForFgBg ( PIX *pixs, l_int32 factor, l_int32 thresh, l_int32 *pfgval, l_int32 *pbgval );
+LEPT_DLL extern l_ok pixSplitDistributionFgBg ( PIX *pixs, l_float32 scorefract, l_int32 factor, l_int32 *pthresh, l_int32 *pfgval, l_int32 *pbgval, PIX **ppixdb );
+LEPT_DLL extern l_ok pixaFindDimensions ( PIXA *pixa, NUMA **pnaw, NUMA **pnah );
+LEPT_DLL extern l_ok pixFindAreaPerimRatio ( PIX *pixs, l_int32 *tab, l_float32 *pfract );
+LEPT_DLL extern NUMA * pixaFindPerimToAreaRatio ( PIXA *pixa );
+LEPT_DLL extern l_ok pixFindPerimToAreaRatio ( PIX *pixs, l_int32 *tab, l_float32 *pfract );
+LEPT_DLL extern NUMA * pixaFindPerimSizeRatio ( PIXA *pixa );
+LEPT_DLL extern l_ok pixFindPerimSizeRatio ( PIX *pixs, l_int32 *tab, l_float32 *pratio );
+LEPT_DLL extern NUMA * pixaFindAreaFraction ( PIXA *pixa );
+LEPT_DLL extern l_ok 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_ok 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_ok 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_ok 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 * pixClipRectangleWithBorder ( PIX *pixs, BOX *box, l_int32 maxbord, BOX **pboxn );
+LEPT_DLL extern PIX * pixClipMasked ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_uint32 outval );
+LEPT_DLL extern l_ok 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 * pixSelectComponentBySize ( PIX *pixs, l_int32 rankorder, l_int32 type, l_int32 connectivity, BOX **pbox );
+LEPT_DLL extern PIX * pixFilterComponentBySize ( PIX *pixs, l_int32 rankorder, l_int32 type, l_int32 connectivity, BOX **pbox );
+LEPT_DLL extern PIX * pixMakeSymmetricMask ( l_int32 w, l_int32 h, l_float32 hf, l_float32 vf, l_int32 type );
+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 PIX * pixMakeCoveringOfRectangles ( PIX *pixs, l_int32 maxiters );
+LEPT_DLL extern l_ok pixFractionFgInMask ( PIX *pix1, PIX *pix2, l_float32 *pfract );
+LEPT_DLL extern l_ok pixClipToForeground ( PIX *pixs, PIX **ppixd, BOX **pbox );
+LEPT_DLL extern l_ok pixTestClipToForeground ( PIX *pixs, l_int32 *pcanclip );
+LEPT_DLL extern l_ok pixClipBoxToForeground ( PIX *pixs, BOX *boxs, PIX **ppixd, BOX **pboxd );
+LEPT_DLL extern l_ok pixScanForForeground ( PIX *pixs, BOX *box, l_int32 scanflag, l_int32 *ploc );
+LEPT_DLL extern l_ok 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_ok 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_ok pixWindowedVarianceOnLine ( PIX *pixs, l_int32 dir, l_int32 loc, l_int32 c1, l_int32 c2, l_int32 size, NUMA **pnad );
+LEPT_DLL extern l_ok 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 start, l_int32 num, 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_ok pixaAddPix ( PIXA *pixa, PIX *pix, l_int32 copyflag );
+LEPT_DLL extern l_ok pixaAddBox ( PIXA *pixa, BOX *box, l_int32 copyflag );
+LEPT_DLL extern l_ok pixaExtendArrayToSize ( PIXA *pixa, size_t size );
+LEPT_DLL extern l_int32 pixaGetCount ( PIXA *pixa );
+LEPT_DLL extern l_ok pixaChangeRefcount ( PIXA *pixa, l_int32 delta );
+LEPT_DLL extern PIX * pixaGetPix ( PIXA *pixa, l_int32 index, l_int32 accesstype );
+LEPT_DLL extern l_ok 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_ok pixaGetBoxGeometry ( PIXA *pixa, l_int32 index, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_ok pixaSetBoxa ( PIXA *pixa, BOXA *boxa, l_int32 accesstype );
+LEPT_DLL extern PIX ** pixaGetPixArray ( PIXA *pixa );
+LEPT_DLL extern l_ok pixaVerifyDepth ( PIXA *pixa, l_int32 *psame, l_int32 *pmaxd );
+LEPT_DLL extern l_ok pixaVerifyDimensions ( PIXA *pixa, l_int32 *psame, l_int32 *pmaxw, l_int32 *pmaxh );
+LEPT_DLL extern l_ok pixaIsFull ( PIXA *pixa, l_int32 *pfullpa, l_int32 *pfullba );
+LEPT_DLL extern l_ok pixaCountText ( PIXA *pixa, l_int32 *pntext );
+LEPT_DLL extern l_ok pixaSetText ( PIXA *pixa, const char *text, SARRAY *sa );
+LEPT_DLL extern void *** pixaGetLinePtrs ( PIXA *pixa, l_int32 *psize );
+LEPT_DLL extern l_ok pixaWriteStreamInfo ( FILE *fp, PIXA *pixa );
+LEPT_DLL extern l_ok pixaReplacePix ( PIXA *pixa, l_int32 index, PIX *pix, BOX *box );
+LEPT_DLL extern l_ok pixaInsertPix ( PIXA *pixa, l_int32 index, PIX *pixs, BOX *box );
+LEPT_DLL extern l_ok pixaRemovePix ( PIXA *pixa, l_int32 index );
+LEPT_DLL extern l_ok pixaRemovePixAndSave ( PIXA *pixa, l_int32 index, PIX **ppix, BOX **pbox );
+LEPT_DLL extern l_ok pixaRemoveSelected ( PIXA *pixa, NUMA *naindex );
+LEPT_DLL extern l_ok pixaInitFull ( PIXA *pixa, PIX *pix, BOX *box );
+LEPT_DLL extern l_ok pixaClear ( PIXA *pixa );
+LEPT_DLL extern l_ok pixaJoin ( PIXA *pixad, PIXA *pixas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern PIXA * pixaInterleave ( PIXA *pixa1, PIXA *pixa2, l_int32 copyflag );
+LEPT_DLL extern l_ok 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_ok pixaaAddPixa ( PIXAA *paa, PIXA *pixa, l_int32 copyflag );
+LEPT_DLL extern l_ok pixaaAddPix ( PIXAA *paa, l_int32 index, PIX *pix, BOX *box, l_int32 copyflag );
+LEPT_DLL extern l_ok 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_ok pixaaVerifyDepth ( PIXAA *paa, l_int32 *psame, l_int32 *pmaxd );
+LEPT_DLL extern l_ok pixaaVerifyDimensions ( PIXAA *paa, l_int32 *psame, l_int32 *pmaxw, l_int32 *pmaxh );
+LEPT_DLL extern l_int32 pixaaIsFull ( PIXAA *paa, l_int32 *pfull );
+LEPT_DLL extern l_ok pixaaInitFull ( PIXAA *paa, PIXA *pixa );
+LEPT_DLL extern l_ok pixaaReplacePixa ( PIXAA *paa, l_int32 index, PIXA *pixa );
+LEPT_DLL extern l_ok pixaaClear ( PIXAA *paa );
+LEPT_DLL extern l_ok pixaaTruncate ( PIXAA *paa );
+LEPT_DLL extern PIXA * pixaRead ( const char *filename );
+LEPT_DLL extern PIXA * pixaReadStream ( FILE *fp );
+LEPT_DLL extern PIXA * pixaReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok pixaWriteDebug ( const char *fname, PIXA *pixa );
+LEPT_DLL extern l_ok pixaWrite ( const char *filename, PIXA *pixa );
+LEPT_DLL extern l_ok pixaWriteStream ( FILE *fp, PIXA *pixa );
+LEPT_DLL extern l_ok pixaWriteMem ( l_uint8 **pdata, size_t *psize, PIXA *pixa );
+LEPT_DLL extern PIXA * pixaReadBoth ( const char *filename );
+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 PIXAA * pixaaReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok pixaaWrite ( const char *filename, PIXAA *paa );
+LEPT_DLL extern l_ok pixaaWriteStream ( FILE *fp, PIXAA *paa );
+LEPT_DLL extern l_ok pixaaWriteMem ( l_uint8 **pdata, size_t *psize, 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_ok pixaccAdd ( PIXACC *pixacc, PIX *pix );
+LEPT_DLL extern l_ok pixaccSubtract ( PIXACC *pixacc, PIX *pix );
+LEPT_DLL extern l_ok pixaccMultConst ( PIXACC *pixacc, l_float32 factor );
+LEPT_DLL extern l_ok 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 * pixSelectByArea ( PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged );
+LEPT_DLL extern PIXA * pixaSelectByArea ( 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 * pixaSelectByNumConnComp ( PIXA *pixas, l_int32 nmin, l_int32 nmax, l_int32 connectivity, l_int32 *pchanged );
+LEPT_DLL extern PIXA * pixaSelectWithIndicator ( PIXA *pixas, NUMA *na, l_int32 *pchanged );
+LEPT_DLL extern l_ok pixRemoveWithIndicator ( PIX *pixs, PIXA *pixa, NUMA *na );
+LEPT_DLL extern l_ok 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 * pixaScaleToSizeRel ( PIXA *pixas, l_int32 delw, l_int32 delh );
+LEPT_DLL extern PIXA * pixaScale ( PIXA *pixas, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PIXA * pixaScaleBySampling ( PIXA *pixas, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PIXA * pixaRotate ( PIXA *pixas, l_float32 angle, l_int32 type, l_int32 incolor, l_int32 width, l_int32 height );
+LEPT_DLL extern PIXA * pixaRotateOrth ( PIXA *pixas, l_int32 rotation );
+LEPT_DLL extern PIXA * pixaTranslate ( PIXA *pixas, l_int32 hshift, l_int32 vshift, l_int32 incolor );
+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_ok pixaaSizeRange ( PIXAA *paa, l_int32 *pminw, l_int32 *pminh, l_int32 *pmaxw, l_int32 *pmaxh );
+LEPT_DLL extern l_ok 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_ok pixaClipToForeground ( PIXA *pixas, PIXA **ppixad, BOXA **pboxa );
+LEPT_DLL extern l_ok pixaGetRenderingDepth ( PIXA *pixa, l_int32 *pdepth );
+LEPT_DLL extern l_ok pixaHasColor ( PIXA *pixa, l_int32 *phascolor );
+LEPT_DLL extern l_ok pixaAnyColormaps ( PIXA *pixa, l_int32 *phascmap );
+LEPT_DLL extern l_ok pixaGetDepthInfo ( PIXA *pixa, l_int32 *pmaxdepth, l_int32 *psame );
+LEPT_DLL extern PIXA * pixaConvertToSameDepth ( PIXA *pixas );
+LEPT_DLL extern l_ok pixaEqual ( PIXA *pixa1, PIXA *pixa2, l_int32 maxdist, NUMA **pnaindex, l_int32 *psame );
+LEPT_DLL extern l_ok pixaSetFullSizeBoxa ( PIXA *pixa );
+LEPT_DLL extern PIX * pixaDisplay ( PIXA *pixa, l_int32 w, l_int32 h );
+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 * pixaDisplayTiledInColumns ( PIXA *pixas, l_int32 nx, l_float32 scalefactor, 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 maxnx, l_float32 scalefactor, l_int32 hspacing, l_int32 vspacing, l_int32 border );
+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 * pixaConvertTo8Colormap ( PIXA *pixas, l_int32 dither );
+LEPT_DLL extern PIXA * pixaConvertTo32 ( PIXA *pixas );
+LEPT_DLL extern PIXA * pixaConstrainedSelect ( PIXA *pixas, l_int32 first, l_int32 last, l_int32 nmax, l_int32 use_pairs, l_int32 copyflag );
+LEPT_DLL extern l_ok pixaSelectToPdf ( PIXA *pixas, l_int32 first, l_int32 last, l_int32 res, l_float32 scalefactor, l_int32 type, l_int32 quality, l_uint32 color, l_int32 fontsize, const char *fileout );
+LEPT_DLL extern PIXA * pixaMakeFromTiledPixa ( PIXA *pixas, l_int32 w, l_int32 h, l_int32 nsamp );
+LEPT_DLL extern PIXA * pixaMakeFromTiledPix ( PIX *pixs, l_int32 w, l_int32 h, l_int32 start, l_int32 num, BOXA *boxa );
+LEPT_DLL extern l_ok pixGetTileCount ( PIX *pix, l_int32 *pn );
+LEPT_DLL extern PIXA * pixaDisplayMultiTiled ( PIXA *pixas, l_int32 nx, l_int32 ny, l_int32 maxw, l_int32 maxh, l_float32 scalefactor, l_int32 spacing, l_int32 border );
+LEPT_DLL extern l_ok pixaSplitIntoFiles ( PIXA *pixas, l_int32 nsplit, l_float32 scale, l_int32 outwidth, l_int32 write_pixa, l_int32 write_pix, l_int32 write_pdf );
+LEPT_DLL extern l_ok 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 PIXA * pixaConvertToNUpPixa ( PIXA *pixas, SARRAY *sa, l_int32 nx, l_int32 ny, l_int32 tw, l_int32 spacing, l_int32 border, l_int32 fontsize );
+LEPT_DLL extern l_ok pixaCompareInPdf ( PIXA *pixa1, PIXA *pixa2, l_int32 nx, l_int32 ny, l_int32 tw, l_int32 spacing, l_int32 border, l_int32 fontsize, const char *fileout );
+LEPT_DLL extern l_ok pmsCreate ( size_t minsize, size_t smallest, NUMA *numalloc, const char *logfile );
+LEPT_DLL extern void pmsDestroy ( void );
+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_ok pmsGetLevelForAlloc ( size_t nbytes, l_int32 *plevel );
+LEPT_DLL extern l_ok pmsGetLevelForDealloc ( void *data, l_int32 *plevel );
+LEPT_DLL extern void pmsLogInfo ( void );
+LEPT_DLL extern l_ok pixAddConstantGray ( PIX *pixs, l_int32 val );
+LEPT_DLL extern l_ok 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 * pixMultiplyGray ( PIX *pixs, PIX *pixg, l_float32 norm );
+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_ok pixAccumulate ( PIX *pixd, PIX *pixs, l_int32 op );
+LEPT_DLL extern l_ok 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 PIX * pixMaxDynamicRangeRGB ( PIX *pixs, l_int32 type );
+LEPT_DLL extern l_uint32 linearScaleRGBVal ( l_uint32 sval, l_float32 factor );
+LEPT_DLL extern l_uint32 logScaleRGBVal ( l_uint32 sval, l_float32 *tab, l_float32 factor );
+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 PIXC * pixcompCopy ( PIXC *pixcs );
+LEPT_DLL extern l_ok pixcompGetDimensions ( PIXC *pixc, l_int32 *pw, l_int32 *ph, l_int32 *pd );
+LEPT_DLL extern l_ok pixcompGetParameters ( PIXC *pixc, l_int32 *pxres, l_int32 *pyres, l_int32 *pcomptype, l_int32 *pcmapflag );
+LEPT_DLL extern l_ok 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_ok pixacompAddPix ( PIXAC *pixac, PIX *pix, l_int32 comptype );
+LEPT_DLL extern l_ok pixacompAddPixcomp ( PIXAC *pixac, PIXC *pixc, l_int32 copyflag );
+LEPT_DLL extern l_ok pixacompReplacePix ( PIXAC *pixac, l_int32 index, PIX *pix, l_int32 comptype );
+LEPT_DLL extern l_ok pixacompReplacePixcomp ( PIXAC *pixac, l_int32 index, PIXC *pixc );
+LEPT_DLL extern l_ok 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, l_int32 copyflag );
+LEPT_DLL extern PIX * pixacompGetPix ( PIXAC *pixac, l_int32 index );
+LEPT_DLL extern l_ok 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_ok 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_ok pixacompSetOffset ( PIXAC *pixac, l_int32 offset );
+LEPT_DLL extern PIXA * pixaCreateFromPixacomp ( PIXAC *pixac, l_int32 accesstype );
+LEPT_DLL extern l_ok pixacompJoin ( PIXAC *pixacd, PIXAC *pixacs, l_int32 istart, l_int32 iend );
+LEPT_DLL extern PIXAC * pixacompInterleave ( PIXAC *pixac1, PIXAC *pixac2 );
+LEPT_DLL extern PIXAC * pixacompRead ( const char *filename );
+LEPT_DLL extern PIXAC * pixacompReadStream ( FILE *fp );
+LEPT_DLL extern PIXAC * pixacompReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok pixacompWrite ( const char *filename, PIXAC *pixac );
+LEPT_DLL extern l_ok pixacompWriteStream ( FILE *fp, PIXAC *pixac );
+LEPT_DLL extern l_ok pixacompWriteMem ( l_uint8 **pdata, size_t *psize, PIXAC *pixac );
+LEPT_DLL extern l_ok 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_ok 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_ok pixacompFastConvertToPdfData ( PIXAC *pixac, const char *title, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_ok pixacompWriteStreamInfo ( FILE *fp, PIXAC *pixac, const char *text );
+LEPT_DLL extern l_ok 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 l_ok pixacompWriteFiles ( PIXAC *pixac, const char *subdir );
+LEPT_DLL extern l_ok pixcompWriteFile ( const char *rootname, PIXC *pixc );
+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_ok pixAddGrayColormap8 ( PIX *pixs );
+LEPT_DLL extern PIX * pixAddMinimalGrayColormap8 ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvertRGBToLuminance ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvertRGBToGrayGeneral ( PIX *pixs, l_int32 type, l_float32 rwt, l_float32 gwt, l_float32 bwt );
+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 * pixConvertRGBToGrayArb ( PIX *pixs, l_float32 rc, l_float32 gc, l_float32 bc );
+LEPT_DLL extern PIX * pixConvertRGBToBinaryArb ( PIX *pixs, l_float32 rc, l_float32 gc, l_float32 bc, l_int32 thresh, l_int32 relation );
+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 PIX * pixConvertCmapTo1 ( PIX *pixs );
+LEPT_DLL extern l_ok 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 * pixConvertTo2 ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvert8To2 ( PIX *pix );
+LEPT_DLL extern PIX * pixConvertTo4 ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvert8To4 ( PIX *pix );
+LEPT_DLL extern PIX * pixConvertTo1Adaptive ( PIX *pixs );
+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 * pixConvertTo8Colormap ( 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 void l_setNeutralBoostVal ( l_int32 val );
+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_ok 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_ok 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_ok pixTilingGetCount ( PIXTILING *pt, l_int32 *pnx, l_int32 *pny );
+LEPT_DLL extern l_ok 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_ok pixTilingNoStripOnPaint ( PIXTILING *pt );
+LEPT_DLL extern l_ok pixTilingPaintTile ( PIX *pixd, l_int32 i, l_int32 j, PIX *pixs, PIXTILING *pt );
+LEPT_DLL extern PIX * pixReadStreamPng ( FILE *fp );
+LEPT_DLL extern l_ok readHeaderPng ( const char *filename, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_ok freadHeaderPng ( FILE *fp, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_ok 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_ok isPngInterlaced ( const char *filename, l_int32 *pinterlaced );
+LEPT_DLL extern l_ok fgetPngColormapInfo ( FILE *fp, PIXCMAP **pcmap, l_int32 *ptransparency );
+LEPT_DLL extern l_ok pixWritePng ( const char *filename, PIX *pix, l_float32 gamma );
+LEPT_DLL extern l_ok pixWriteStreamPng ( FILE *fp, PIX *pix, l_float32 gamma );
+LEPT_DLL extern l_ok pixSetZlibCompression ( PIX *pix, l_int32 compval );
+LEPT_DLL extern void l_pngSetReadStrip16To8 ( l_int32 flag );
+LEPT_DLL extern PIX * pixReadMemPng ( const l_uint8 *filedata, size_t filesize );
+LEPT_DLL extern l_ok pixWriteMemPng ( l_uint8 **pfiledata, size_t *pfilesize, PIX *pix, l_float32 gamma );
+LEPT_DLL extern PIX * pixReadStreamPnm ( FILE *fp );
+LEPT_DLL extern l_ok 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_ok 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_ok pixWriteStreamPnm ( FILE *fp, PIX *pix );
+LEPT_DLL extern l_ok pixWriteStreamAsciiPnm ( FILE *fp, PIX *pix );
+LEPT_DLL extern l_ok pixWriteStreamPam ( FILE *fp, PIX *pix );
+LEPT_DLL extern PIX * pixReadMemPnm ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok readHeaderMemPnm ( const l_uint8 *data, 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_ok pixWriteMemPnm ( l_uint8 **pdata, size_t *psize, PIX *pix );
+LEPT_DLL extern l_ok pixWriteMemPam ( 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_ok getProjectiveXformCoeffs ( PTA *ptas, PTA *ptad, l_float32 **pvc );
+LEPT_DLL extern l_ok projectiveXformSampledPt ( l_float32 *vc, l_int32 x, l_int32 y, l_int32 *pxp, l_int32 *pyp );
+LEPT_DLL extern l_ok projectiveXformPt ( l_float32 *vc, l_int32 x, l_int32 y, l_float32 *pxp, l_float32 *pyp );
+LEPT_DLL extern l_ok convertFilesToPS ( const char *dirin, const char *substr, l_int32 res, const char *fileout );
+LEPT_DLL extern l_ok sarrayConvertFilesToPS ( SARRAY *sa, l_int32 res, const char *fileout );
+LEPT_DLL extern l_ok convertFilesFittedToPS ( const char *dirin, const char *substr, l_float32 xpts, l_float32 ypts, const char *fileout );
+LEPT_DLL extern l_ok sarrayConvertFilesFittedToPS ( SARRAY *sa, l_float32 xpts, l_float32 ypts, const char *fileout );
+LEPT_DLL extern l_ok writeImageCompressedToPSFile ( const char *filein, const char *fileout, l_int32 res, l_int32 *pindex );
+LEPT_DLL extern l_ok 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_ok pixWriteSegmentedPageToPS ( PIX *pixs, PIX *pixm, l_float32 textscale, l_float32 imagescale, l_int32 threshold, l_int32 pageno, const char *fileout );
+LEPT_DLL extern l_ok pixWriteMixedToPS ( PIX *pixb, PIX *pixc, l_float32 scale, l_int32 pageno, const char *fileout );
+LEPT_DLL extern l_ok convertToPSEmbed ( const char *filein, const char *fileout, l_int32 level );
+LEPT_DLL extern l_ok pixaWriteCompressedToPS ( PIXA *pixa, const char *fileout, l_int32 res, l_int32 level );
+LEPT_DLL extern l_ok pixWriteCompressedToPS ( PIX *pix, const char *fileout, l_int32 res, l_int32 level, l_int32 *pindex );
+LEPT_DLL extern l_ok pixWritePSEmbed ( const char *filein, const char *fileout );
+LEPT_DLL extern l_ok 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 l_ok convertJpegToPSEmbed ( const char *filein, const char *fileout );
+LEPT_DLL extern l_ok 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_ok convertG4ToPSEmbed ( const char *filein, const char *fileout );
+LEPT_DLL extern l_ok 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_ok convertTiffMultipageToPS ( const char *filein, const char *fileout, l_float32 fillfract );
+LEPT_DLL extern l_ok convertFlateToPSEmbed ( const char *filein, const char *fileout );
+LEPT_DLL extern l_ok 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_ok 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_ok ptaEmpty ( PTA *pta );
+LEPT_DLL extern l_ok ptaAddPt ( PTA *pta, l_float32 x, l_float32 y );
+LEPT_DLL extern l_ok ptaInsertPt ( PTA *pta, l_int32 index, l_int32 x, l_int32 y );
+LEPT_DLL extern l_ok 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_ok ptaGetPt ( PTA *pta, l_int32 index, l_float32 *px, l_float32 *py );
+LEPT_DLL extern l_ok ptaGetIPt ( PTA *pta, l_int32 index, l_int32 *px, l_int32 *py );
+LEPT_DLL extern l_ok ptaSetPt ( PTA *pta, l_int32 index, l_float32 x, l_float32 y );
+LEPT_DLL extern l_ok 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 PTA * ptaReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok ptaWriteDebug ( const char *filename, PTA *pta, l_int32 type );
+LEPT_DLL extern l_ok ptaWrite ( const char *filename, PTA *pta, l_int32 type );
+LEPT_DLL extern l_ok ptaWriteStream ( FILE *fp, PTA *pta, l_int32 type );
+LEPT_DLL extern l_ok ptaWriteMem ( l_uint8 **pdata, size_t *psize, PTA *pta, l_int32 type );
+LEPT_DLL extern PTAA * ptaaCreate ( l_int32 n );
+LEPT_DLL extern void ptaaDestroy ( PTAA **pptaa );
+LEPT_DLL extern l_ok 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_ok ptaaGetPt ( PTAA *ptaa, l_int32 ipta, l_int32 jpt, l_float32 *px, l_float32 *py );
+LEPT_DLL extern l_ok ptaaInitFull ( PTAA *ptaa, PTA *pta );
+LEPT_DLL extern l_ok ptaaReplacePta ( PTAA *ptaa, l_int32 index, PTA *pta );
+LEPT_DLL extern l_ok ptaaAddPt ( PTAA *ptaa, l_int32 ipta, l_float32 x, l_float32 y );
+LEPT_DLL extern l_ok ptaaTruncate ( PTAA *ptaa );
+LEPT_DLL extern PTAA * ptaaRead ( const char *filename );
+LEPT_DLL extern PTAA * ptaaReadStream ( FILE *fp );
+LEPT_DLL extern PTAA * ptaaReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok ptaaWriteDebug ( const char *filename, PTAA *ptaa, l_int32 type );
+LEPT_DLL extern l_ok ptaaWrite ( const char *filename, PTAA *ptaa, l_int32 type );
+LEPT_DLL extern l_ok ptaaWriteStream ( FILE *fp, PTAA *ptaa, l_int32 type );
+LEPT_DLL extern l_ok ptaaWriteMem ( l_uint8 **pdata, size_t *psize, PTAA *ptaa, l_int32 type );
+LEPT_DLL extern PTA * ptaSubsample ( PTA *ptas, l_int32 subfactor );
+LEPT_DLL extern l_ok ptaJoin ( PTA *ptad, PTA *ptas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern l_ok 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 * ptaSelectRange ( PTA *ptas, l_int32 first, l_int32 last );
+LEPT_DLL extern BOX * ptaGetBoundingRegion ( PTA *pta );
+LEPT_DLL extern l_ok 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_ok ptaGetMinMax ( PTA *pta, l_float32 *pxmin, l_float32 *pymin, l_float32 *pxmax, l_float32 *pymax );
+LEPT_DLL extern PTA * ptaSelectByValue ( PTA *ptas, l_float32 xth, l_float32 yth, l_int32 type, l_int32 relation );
+LEPT_DLL extern PTA * ptaCropToMask ( PTA *ptas, PIX *pixm );
+LEPT_DLL extern l_ok ptaGetLinearLSF ( PTA *pta, l_float32 *pa, l_float32 *pb, NUMA **pnafit );
+LEPT_DLL extern l_ok ptaGetQuadraticLSF ( PTA *pta, l_float32 *pa, l_float32 *pb, l_float32 *pc, NUMA **pnafit );
+LEPT_DLL extern l_ok ptaGetCubicLSF ( PTA *pta, l_float32 *pa, l_float32 *pb, l_float32 *pc, l_float32 *pd, NUMA **pnafit );
+LEPT_DLL extern l_ok ptaGetQuarticLSF ( PTA *pta, l_float32 *pa, l_float32 *pb, l_float32 *pc, l_float32 *pd, l_float32 *pe, NUMA **pnafit );
+LEPT_DLL extern l_ok ptaNoisyLinearLSF ( PTA *pta, l_float32 factor, PTA **pptad, l_float32 *pa, l_float32 *pb, l_float32 *pmederr, NUMA **pnafit );
+LEPT_DLL extern l_ok 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_ok applyLinearFit ( l_float32 a, l_float32 b, l_float32 x, l_float32 *py );
+LEPT_DLL extern l_ok applyQuadraticFit ( l_float32 a, l_float32 b, l_float32 c, l_float32 x, l_float32 *py );
+LEPT_DLL extern l_ok applyCubicFit ( l_float32 a, l_float32 b, l_float32 c, l_float32 d, l_float32 x, l_float32 *py );
+LEPT_DLL extern l_ok 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_ok 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 * ptaaIndexLabeledPixels ( PIX *pixs, l_int32 *pncc );
+LEPT_DLL extern PTA * ptaGetNeighborPixLocs ( PIX *pixs, l_int32 x, l_int32 y, l_int32 conn );
+LEPT_DLL extern PTA * numaConvertToPta1 ( NUMA *na );
+LEPT_DLL extern PTA * numaConvertToPta2 ( NUMA *nax, NUMA *nay );
+LEPT_DLL extern l_ok ptaConvertToNuma ( PTA *pta, NUMA **pnax, NUMA **pnay );
+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 PTA * ptaSort ( PTA *ptas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex );
+LEPT_DLL extern l_ok 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 l_ok ptaGetRankValue ( PTA *pta, l_float32 fract, PTA *ptasort, l_int32 sorttype, l_float32 *pval );
+LEPT_DLL extern PTA * ptaSort2d ( PTA *pta );
+LEPT_DLL extern l_ok ptaEqual ( PTA *pta1, PTA *pta2, l_int32 *psame );
+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_ok ptaRemoveDupsByHash ( PTA *ptas, PTA **pptad, L_DNAHASH **pdahash );
+LEPT_DLL extern PTA * ptaIntersectionByHash ( PTA *pta1, PTA *pta2 );
+LEPT_DLL extern l_ok 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 L_PTRA * ptraCreate ( l_int32 n );
+LEPT_DLL extern void ptraDestroy ( L_PTRA **ppa, l_int32 freeflag, l_int32 warnflag );
+LEPT_DLL extern l_ok ptraAdd ( L_PTRA *pa, void *item );
+LEPT_DLL extern l_ok 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_ok ptraSwap ( L_PTRA *pa, l_int32 index1, l_int32 index2 );
+LEPT_DLL extern l_ok ptraCompactArray ( L_PTRA *pa );
+LEPT_DLL extern l_ok ptraReverse ( L_PTRA *pa );
+LEPT_DLL extern l_ok ptraJoin ( L_PTRA *pa1, L_PTRA *pa2 );
+LEPT_DLL extern l_ok ptraGetMaxIndex ( L_PTRA *pa, l_int32 *pmaxindex );
+LEPT_DLL extern l_ok 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_ok ptraaGetSize ( L_PTRAA *paa, l_int32 *psize );
+LEPT_DLL extern l_ok 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_ok pixQuadtreeMean ( PIX *pixs, l_int32 nlevels, PIX *pix_ma, FPIXA **pfpixa );
+LEPT_DLL extern l_ok pixQuadtreeVariance ( PIX *pixs, l_int32 nlevels, PIX *pix_ma, DPIX *dpix_msa, FPIXA **pfpixa_v, FPIXA **pfpixa_rv );
+LEPT_DLL extern l_ok pixMeanInRectangle ( PIX *pixs, BOX *box, PIX *pixma, l_float32 *pval );
+LEPT_DLL extern l_ok 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_ok quadtreeGetParent ( FPIXA *fpixa, l_int32 level, l_int32 x, l_int32 y, l_float32 *pval );
+LEPT_DLL extern l_ok 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, l_int32 fontsize );
+LEPT_DLL extern L_QUEUE * lqueueCreate ( l_int32 nalloc );
+LEPT_DLL extern void lqueueDestroy ( L_QUEUE **plq, l_int32 freeflag );
+LEPT_DLL extern l_ok 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_ok 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 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_ok 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_ok findFileFormat ( const char *filename, l_int32 *pformat );
+LEPT_DLL extern l_ok findFileFormatStream ( FILE *fp, l_int32 *pformat );
+LEPT_DLL extern l_ok 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_ok 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_ok writeImageFileInfo ( const char *filename, FILE *fpout, l_int32 headeronly );
+LEPT_DLL extern l_ok ioFormatTest ( const char *filename );
+LEPT_DLL extern L_RECOG * recogCreateFromRecog ( L_RECOG *recs, l_int32 scalew, l_int32 scaleh, l_int32 linew, l_int32 threshold, l_int32 maxyshift );
+LEPT_DLL extern L_RECOG * recogCreateFromPixa ( PIXA *pixa, l_int32 scalew, l_int32 scaleh, l_int32 linew, l_int32 threshold, l_int32 maxyshift );
+LEPT_DLL extern L_RECOG * recogCreateFromPixaNoFinish ( PIXA *pixa, l_int32 scalew, l_int32 scaleh, l_int32 linew, l_int32 threshold, l_int32 maxyshift );
+LEPT_DLL extern L_RECOG * recogCreate ( l_int32 scalew, l_int32 scaleh, l_int32 linew, l_int32 threshold, l_int32 maxyshift );
+LEPT_DLL extern void recogDestroy ( L_RECOG **precog );
+LEPT_DLL extern l_int32 recogGetCount ( L_RECOG *recog );
+LEPT_DLL extern l_ok recogSetParams ( L_RECOG *recog, l_int32 type, l_int32 min_nopad, l_float32 max_wh_ratio, l_float32 max_ht_ratio );
+LEPT_DLL extern l_int32 recogGetClassIndex ( L_RECOG *recog, l_int32 val, char *text, l_int32 *pindex );
+LEPT_DLL extern l_ok 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_ok l_convertCharstrToInt ( const char *str, l_int32 *pval );
+LEPT_DLL extern L_RECOG * recogRead ( const char *filename );
+LEPT_DLL extern L_RECOG * recogReadStream ( FILE *fp );
+LEPT_DLL extern L_RECOG * recogReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok recogWrite ( const char *filename, L_RECOG *recog );
+LEPT_DLL extern l_ok recogWriteStream ( FILE *fp, L_RECOG *recog );
+LEPT_DLL extern l_ok recogWriteMem ( l_uint8 **pdata, size_t *psize, L_RECOG *recog );
+LEPT_DLL extern PIXA * recogExtractPixa ( L_RECOG *recog );
+LEPT_DLL extern BOXA * recogDecode ( L_RECOG *recog, PIX *pixs, l_int32 nlevels, PIX **ppixdb );
+LEPT_DLL extern l_ok recogCreateDid ( L_RECOG *recog, PIX *pixs );
+LEPT_DLL extern l_ok 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_ok recogSetChannelParams ( L_RECOG *recog, l_int32 nlevels );
+LEPT_DLL extern l_ok recogIdentifyMultiple ( L_RECOG *recog, PIX *pixs, l_int32 minh, l_int32 skipsplit, BOXA **pboxa, PIXA **ppixa, PIX **ppixdb, l_int32 debugsplit );
+LEPT_DLL extern l_ok recogSplitIntoCharacters ( L_RECOG *recog, PIX *pixs, l_int32 minh, l_int32 skipsplit, BOXA **pboxa, PIXA **ppixa, l_int32 debug );
+LEPT_DLL extern l_ok recogCorrelationBestRow ( L_RECOG *recog, PIX *pixs, BOXA **pboxa, NUMA **pnascore, NUMA **pnaindex, SARRAY **psachar, l_int32 debug );
+LEPT_DLL extern l_ok recogCorrelationBestChar ( L_RECOG *recog, PIX *pixs, BOX **pbox, l_float32 *pscore, l_int32 *pindex, char **pcharstr, PIX **ppixdb );
+LEPT_DLL extern l_ok recogIdentifyPixa ( L_RECOG *recog, PIXA *pixa, PIX **ppixdb );
+LEPT_DLL extern l_ok recogIdentifyPix ( L_RECOG *recog, PIX *pixs, PIX **ppixdb );
+LEPT_DLL extern l_ok recogSkipIdentify ( L_RECOG *recog );
+LEPT_DLL extern void rchaDestroy ( L_RCHA **prcha );
+LEPT_DLL extern void rchDestroy ( L_RCH **prch );
+LEPT_DLL extern l_ok rchaExtract ( L_RCHA *rcha, NUMA **pnaindex, NUMA **pnascore, SARRAY **psatext, NUMA **pnasample, NUMA **pnaxloc, NUMA **pnayloc, NUMA **pnawidth );
+LEPT_DLL extern l_ok 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 SARRAY * recogExtractNumbers ( L_RECOG *recog, BOXA *boxas, l_float32 scorethresh, l_int32 spacethresh, BOXAA **pbaa, NUMAA **pnaa );
+LEPT_DLL extern PIXA * showExtractNumbers ( PIX *pixs, SARRAY *sa, BOXAA *baa, NUMAA *naa, PIX **ppixdb );
+LEPT_DLL extern l_ok recogTrainLabeled ( L_RECOG *recog, PIX *pixs, BOX *box, char *text, l_int32 debug );
+LEPT_DLL extern l_ok recogProcessLabeled ( L_RECOG *recog, PIX *pixs, BOX *box, char *text, PIX **ppix );
+LEPT_DLL extern l_ok recogAddSample ( L_RECOG *recog, PIX *pix, l_int32 debug );
+LEPT_DLL extern PIX * recogModifyTemplate ( L_RECOG *recog, PIX *pixs );
+LEPT_DLL extern l_int32 recogAverageSamples ( L_RECOG **precog, 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_ok recogTrainingFinished ( L_RECOG **precog, l_int32 modifyflag, l_int32 minsize, l_float32 minfract );
+LEPT_DLL extern PIXA * recogFilterPixaBySize ( PIXA *pixas, l_int32 setsize, l_int32 maxkeep, l_float32 max_ht_ratio, NUMA **pna );
+LEPT_DLL extern PIXAA * recogSortPixaByClass ( PIXA *pixa, l_int32 setsize );
+LEPT_DLL extern l_ok recogRemoveOutliers1 ( L_RECOG **precog, l_float32 minscore, l_int32 mintarget, l_int32 minsize, PIX **ppixsave, PIX **ppixrem );
+LEPT_DLL extern PIXA * pixaRemoveOutliers1 ( PIXA *pixas, l_float32 minscore, l_int32 mintarget, l_int32 minsize, PIX **ppixsave, PIX **ppixrem );
+LEPT_DLL extern l_ok recogRemoveOutliers2 ( L_RECOG **precog, l_float32 minscore, l_int32 minsize, PIX **ppixsave, PIX **ppixrem );
+LEPT_DLL extern PIXA * pixaRemoveOutliers2 ( PIXA *pixas, l_float32 minscore, l_int32 minsize, PIX **ppixsave, PIX **ppixrem );
+LEPT_DLL extern PIXA * recogTrainFromBoot ( L_RECOG *recogboot, PIXA *pixas, l_float32 minscore, l_int32 threshold, l_int32 debug );
+LEPT_DLL extern l_ok recogPadDigitTrainingSet ( L_RECOG **precog, l_int32 scaleh, l_int32 linew );
+LEPT_DLL extern l_int32 recogIsPaddingNeeded ( L_RECOG *recog, SARRAY **psa );
+LEPT_DLL extern PIXA * recogAddDigitPadTemplates ( L_RECOG *recog, SARRAY *sa );
+LEPT_DLL extern L_RECOG * recogMakeBootDigitRecog ( l_int32 nsamp, l_int32 scaleh, l_int32 linew, l_int32 maxyshift, l_int32 debug );
+LEPT_DLL extern PIXA * recogMakeBootDigitTemplates ( l_int32 nsamp, l_int32 debug );
+LEPT_DLL extern l_ok recogShowContent ( FILE *fp, L_RECOG *recog, l_int32 index, l_int32 display );
+LEPT_DLL extern l_ok recogDebugAverages ( L_RECOG **precog, l_int32 debug );
+LEPT_DLL extern l_int32 recogShowAverageTemplates ( L_RECOG *recog );
+LEPT_DLL extern l_ok 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_ok regTestSetup ( l_int32 argc, char **argv, L_REGPARAMS **prp );
+LEPT_DLL extern l_ok regTestCleanup ( L_REGPARAMS *rp );
+LEPT_DLL extern l_ok regTestCompareValues ( L_REGPARAMS *rp, l_float32 val1, l_float32 val2, l_float32 delta );
+LEPT_DLL extern l_ok regTestCompareStrings ( L_REGPARAMS *rp, l_uint8 *string1, size_t bytes1, l_uint8 *string2, size_t bytes2 );
+LEPT_DLL extern l_ok regTestComparePix ( L_REGPARAMS *rp, PIX *pix1, PIX *pix2 );
+LEPT_DLL extern l_ok regTestCompareSimilarPix ( L_REGPARAMS *rp, PIX *pix1, PIX *pix2, l_int32 mindiff, l_float32 maxfract, l_int32 printstats );
+LEPT_DLL extern l_ok regTestCheckFile ( L_REGPARAMS *rp, const char *localname );
+LEPT_DLL extern l_ok regTestCompareFiles ( L_REGPARAMS *rp, l_int32 index1, l_int32 index2 );
+LEPT_DLL extern l_ok regTestWritePixAndCheck ( L_REGPARAMS *rp, PIX *pix, l_int32 format );
+LEPT_DLL extern l_ok regTestWriteDataAndCheck ( L_REGPARAMS *rp, void *data, size_t nbytes, const char *ext );
+LEPT_DLL extern char * regTestGenLocalFilename ( L_REGPARAMS *rp, l_int32 index, l_int32 format );
+LEPT_DLL extern l_ok 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_ok pixRasteropVip ( PIX *pixd, l_int32 bx, l_int32 bw, l_int32 vshift, l_int32 incolor );
+LEPT_DLL extern l_ok 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_ok pixRasteropIP ( PIX *pixd, l_int32 hshift, l_int32 vshift, l_int32 incolor );
+LEPT_DLL extern l_ok pixRasteropFullImage ( PIX *pixd, PIX *pixs, l_int32 op );
+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 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 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 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_ok 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_ok 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_ok pixFindHorizontalRuns ( PIX *pix, l_int32 y, l_int32 *xstart, l_int32 *xend, l_int32 *pn );
+LEPT_DLL extern l_ok 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_ok pixFindMaxHorizontalRunOnLine ( PIX *pix, l_int32 y, l_int32 *pxstart, l_int32 *psize );
+LEPT_DLL extern l_ok pixFindMaxVerticalRunOnLine ( PIX *pix, l_int32 x, l_int32 *pystart, l_int32 *psize );
+LEPT_DLL extern l_ok 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, const 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_ok sarrayAddString ( SARRAY *sa, const char *string, l_int32 copyflag );
+LEPT_DLL extern char * sarrayRemoveString ( SARRAY *sa, l_int32 index );
+LEPT_DLL extern l_ok sarrayReplaceString ( SARRAY *sa, l_int32 index, char *newstr, l_int32 copyflag );
+LEPT_DLL extern l_ok 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_ok 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 SARRAY * sarrayConcatUniformly ( SARRAY *sa, l_int32 n, l_int32 addnlflag );
+LEPT_DLL extern l_ok sarrayJoin ( SARRAY *sa1, SARRAY *sa2 );
+LEPT_DLL extern l_ok sarrayAppendRange ( SARRAY *sa1, SARRAY *sa2, l_int32 start, l_int32 end );
+LEPT_DLL extern l_ok sarrayPadToSameSize ( SARRAY *sa1, SARRAY *sa2, const 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 * sarrayRead ( const char *filename );
+LEPT_DLL extern SARRAY * sarrayReadStream ( FILE *fp );
+LEPT_DLL extern SARRAY * sarrayReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok sarrayWrite ( const char *filename, SARRAY *sa );
+LEPT_DLL extern l_ok sarrayWriteStream ( FILE *fp, SARRAY *sa );
+LEPT_DLL extern l_ok sarrayWriteMem ( l_uint8 **pdata, size_t *psize, SARRAY *sa );
+LEPT_DLL extern l_ok 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 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_ok sarrayRemoveDupsByHash ( SARRAY *sas, SARRAY **psad, L_DNAHASH **pdahash );
+LEPT_DLL extern SARRAY * sarrayIntersectionByHash ( SARRAY *sa1, SARRAY *sa2 );
+LEPT_DLL extern l_ok 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 * sarrayGenerateIntegers ( l_int32 n );
+LEPT_DLL extern l_ok sarrayLookupCSKV ( SARRAY *sa, const char *keystring, char **pvalstring );
+LEPT_DLL extern PIX * pixScale ( PIX *pixs, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PIX * pixScaleToSizeRel ( PIX *pixs, l_int32 delw, l_int32 delh );
+LEPT_DLL extern PIX * pixScaleToSize ( PIX *pixs, l_int32 wd, l_int32 hd );
+LEPT_DLL extern PIX * pixScaleToResolution ( PIX *pixs, l_float32 target, l_float32 assumed, l_float32 *pscalefact );
+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 * 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 * 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 * pixScaleSmoothToSize ( PIX *pixs, l_int32 wd, l_int32 hd );
+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 * pixScaleAreaMapToSize ( PIX *pixs, l_int32 wd, l_int32 hd );
+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 * 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_ok 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 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_ok pixSeedfillGray ( PIX *pixs, PIX *pixm, l_int32 connectivity );
+LEPT_DLL extern l_ok pixSeedfillGrayInv ( PIX *pixs, PIX *pixm, l_int32 connectivity );
+LEPT_DLL extern l_ok pixSeedfillGraySimple ( PIX *pixs, PIX *pixm, l_int32 connectivity );
+LEPT_DLL extern l_ok 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_ok pixLocalExtrema ( PIX *pixs, l_int32 maxmin, l_int32 minmax, PIX **ppixmin, PIX **ppixmax );
+LEPT_DLL extern l_ok pixSelectedLocalExtrema ( PIX *pixs, l_int32 mindist, PIX **ppixmin, PIX **ppixmax );
+LEPT_DLL extern PIX * pixFindEqualValues ( PIX *pixs1, PIX *pixs2 );
+LEPT_DLL extern l_ok 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 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_ok 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_ok selSetName ( SEL *sel, const char *name );
+LEPT_DLL extern l_ok selaFindSelByName ( SELA *sela, const char *name, l_int32 *pindex, SEL **psel );
+LEPT_DLL extern l_ok selGetElement ( SEL *sel, l_int32 row, l_int32 col, l_int32 *ptype );
+LEPT_DLL extern l_ok selSetElement ( SEL *sel, l_int32 row, l_int32 col, l_int32 type );
+LEPT_DLL extern l_ok selGetParameters ( SEL *sel, l_int32 *psy, l_int32 *psx, l_int32 *pcy, l_int32 *pcx );
+LEPT_DLL extern l_ok selSetOrigin ( SEL *sel, l_int32 cy, l_int32 cx );
+LEPT_DLL extern l_ok 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_ok 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_ok 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_ok selaWrite ( const char *fname, SELA *sela );
+LEPT_DLL extern l_ok selaWriteStream ( FILE *fp, SELA *sela );
+LEPT_DLL extern l_ok selWrite ( const char *fname, SEL *sel );
+LEPT_DLL extern l_ok 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, const char *selname );
+LEPT_DLL extern SELA * selaCreateFromColorPixa ( PIXA *pixa, SARRAY *sa );
+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 SELA * sela4ccThin ( SELA *sela );
+LEPT_DLL extern SELA * sela8ccThin ( SELA *sela );
+LEPT_DLL extern SELA * sela4and8ccThin ( SELA *sela );
+LEPT_DLL extern SEL * selMakePlusSign ( l_int32 size, l_int32 linewidth );
+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_ok pixHShearIP ( PIX *pixs, l_int32 yloc, l_float32 radang, l_int32 incolor );
+LEPT_DLL extern l_ok 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 * pixDeskewBoth ( PIX *pixs, l_int32 redsearch );
+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_ok pixFindSkew ( PIX *pixs, l_float32 *pangle, l_float32 *pconf );
+LEPT_DLL extern l_ok pixFindSkewSweep ( PIX *pixs, l_float32 *pangle, l_int32 reduction, l_float32 sweeprange, l_float32 sweepdelta );
+LEPT_DLL extern l_ok 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_ok 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_ok 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_ok pixFindDifferentialSquareSum ( PIX *pixs, l_float32 *psum );
+LEPT_DLL extern l_ok pixFindNormalizedSquareSum ( PIX *pixs, l_float32 *phratio, l_float32 *pvratio, l_float32 *pfract );
+LEPT_DLL extern PIX * pixReadStreamSpix ( FILE *fp );
+LEPT_DLL extern l_ok readHeaderSpix ( const char *filename, l_int32 *pwidth, l_int32 *pheight, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_ok freadHeaderSpix ( FILE *fp, l_int32 *pwidth, l_int32 *pheight, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_ok sreadHeaderSpix ( const l_uint32 *data, size_t size, l_int32 *pwidth, l_int32 *pheight, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_ok pixWriteStreamSpix ( FILE *fp, PIX *pix );
+LEPT_DLL extern PIX * pixReadMemSpix ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok pixWriteMemSpix ( l_uint8 **pdata, size_t *psize, PIX *pix );
+LEPT_DLL extern l_ok 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 n );
+LEPT_DLL extern void lstackDestroy ( L_STACK **plstack, l_int32 freeflag );
+LEPT_DLL extern l_ok 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_ok lstackPrint ( FILE *fp, L_STACK *lstack );
+LEPT_DLL extern L_STRCODE * strcodeCreate ( l_int32 fileno );
+LEPT_DLL extern l_ok strcodeCreateFromFile ( const char *filein, l_int32 fileno, const char *outdir );
+LEPT_DLL extern l_ok 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_getStructStrFromFile ( const char *filename, l_int32 field, char **pstr );
+LEPT_DLL extern l_ok pixFindStrokeLength ( PIX *pixs, l_int32 *tab8, l_int32 *plength );
+LEPT_DLL extern l_ok pixFindStrokeWidth ( PIX *pixs, l_float32 thresh, l_int32 *tab8, l_float32 *pwidth, NUMA **pnahisto );
+LEPT_DLL extern NUMA * pixaFindStrokeWidth ( PIXA *pixa, l_float32 thresh, l_int32 *tab8, l_int32 debug );
+LEPT_DLL extern PIXA * pixaModifyStrokeWidth ( PIXA *pixas, l_float32 targetw );
+LEPT_DLL extern PIX * pixModifyStrokeWidth ( PIX *pixs, l_float32 width, l_float32 targetw );
+LEPT_DLL extern PIXA * pixaSetStrokeWidth ( PIXA *pixas, l_int32 width, l_int32 thinfirst, l_int32 connectivity );
+LEPT_DLL extern PIX * pixSetStrokeWidth ( PIX *pixs, l_int32 width, l_int32 thinfirst, l_int32 connectivity );
+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_ok 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_ok 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_ok 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_ok 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_ok 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_ok pixWriteTiff ( const char *filename, PIX *pix, l_int32 comptype, const char *modestr );
+LEPT_DLL extern l_ok pixWriteTiffCustom ( const char *filename, PIX *pix, l_int32 comptype, const char *modestr, NUMA *natags, SARRAY *savals, SARRAY *satypes, NUMA *nasizes );
+LEPT_DLL extern l_ok pixWriteStreamTiff ( FILE *fp, PIX *pix, l_int32 comptype );
+LEPT_DLL extern l_ok pixWriteStreamTiffWA ( FILE *fp, PIX *pix, l_int32 comptype, const char *modestr );
+LEPT_DLL extern PIX * pixReadFromMultipageTiff ( const char *fname, size_t *poffset );
+LEPT_DLL extern PIXA * pixaReadMultipageTiff ( const char *filename );
+LEPT_DLL extern l_ok pixaWriteMultipageTiff ( const char *fname, PIXA *pixa );
+LEPT_DLL extern l_ok writeMultipageTiff ( const char *dirin, const char *substr, const char *fileout );
+LEPT_DLL extern l_ok writeMultipageTiffSA ( SARRAY *sa, const char *fileout );
+LEPT_DLL extern l_ok fprintTiffInfo ( FILE *fpout, const char *tiffile );
+LEPT_DLL extern l_ok tiffGetCount ( FILE *fp, l_int32 *pn );
+LEPT_DLL extern l_ok getTiffResolution ( FILE *fp, l_int32 *pxres, l_int32 *pyres );
+LEPT_DLL extern l_ok readHeaderTiff ( const char *filename, l_int32 n, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp, l_int32 *pres, l_int32 *pcmap, l_int32 *pformat );
+LEPT_DLL extern l_ok freadHeaderTiff ( FILE *fp, l_int32 n, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp, l_int32 *pres, l_int32 *pcmap, l_int32 *pformat );
+LEPT_DLL extern l_ok readHeaderMemTiff ( const l_uint8 *cdata, size_t size, l_int32 n, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp, l_int32 *pres, l_int32 *pcmap, l_int32 *pformat );
+LEPT_DLL extern l_ok findTiffCompression ( FILE *fp, l_int32 *pcomptype );
+LEPT_DLL extern l_ok 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 PIX * pixReadMemFromMultipageTiff ( const l_uint8 *cdata, size_t size, size_t *poffset );
+LEPT_DLL extern PIXA * pixaReadMemMultipageTiff ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_ok pixaWriteMemMultipageTiff ( l_uint8 **pdata, size_t *psize, PIXA *pixa );
+LEPT_DLL extern l_ok pixWriteMemTiff ( l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 comptype );
+LEPT_DLL extern l_ok 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 void leptSetStderrHandler ( void ( *handler ) ( const char * ) );
+LEPT_DLL extern void lept_stderr ( const char *fmt, ... );
+LEPT_DLL extern l_ok 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 l_ok fileCorruptByDeletion ( const char *filein, l_float32 loc, l_float32 size, const char *fileout );
+LEPT_DLL extern l_ok fileCorruptByMutation ( const char *filein, l_float32 loc, l_float32 size, const char *fileout );
+LEPT_DLL extern l_ok fileReplaceBytes ( const char *filein, l_int32 start, l_int32 nbytes, l_uint8 *newdata, size_t newsize, const char *fileout );
+LEPT_DLL extern l_ok genRandomIntOnInterval ( l_int32 start, l_int32 end, l_int32 seed, l_int32 *pval );
+LEPT_DLL extern l_int32 lept_roundftoi ( l_float32 fval );
+LEPT_DLL extern l_ok l_hashStringToUint64 ( const char *str, l_uint64 *phash );
+LEPT_DLL extern l_ok l_hashPtToUint64 ( l_int32 x, l_int32 y, l_uint64 *phash );
+LEPT_DLL extern l_ok l_hashFloat64ToUint64 ( l_int32 nbuckets, l_float64 val, l_uint64 *phash );
+LEPT_DLL extern l_ok findNextLargerPrime ( l_int32 start, l_uint32 *pprime );
+LEPT_DLL extern l_ok lept_isPrime ( l_uint64 n, l_int32 *pis_prime, l_uint32 *pfactor );
+LEPT_DLL extern l_uint32 convertIntToGrayCode ( l_uint32 val );
+LEPT_DLL extern l_uint32 convertGrayCodeToInt ( l_uint32 val );
+LEPT_DLL extern char * getLeptonicaVersion ( void );
+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 ( void );
+LEPT_DLL extern char * stringNew ( const char *src );
+LEPT_DLL extern l_ok stringCopy ( char *dest, const char *src, l_int32 n );
+LEPT_DLL extern char * stringCopySegment ( const char *src, l_int32 start, l_int32 nbytes );
+LEPT_DLL extern l_ok 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_ok 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_ok stringSplitOnToken ( char *cstr, const char *seps, char **phead, char **ptail );
+LEPT_DLL extern l_ok stringCheckForChars ( const char *src, const char *chars, l_int32 *pfound );
+LEPT_DLL extern char * stringRemoveChars ( const char *src, const char *remchars );
+LEPT_DLL extern char * stringReplaceEachSubstr ( const char *src, const char *sub1, const char *sub2, l_int32 *pcount );
+LEPT_DLL extern char * stringReplaceSubstr ( const char *src, const char *sub1, const char *sub2, l_int32 *ploc, l_int32 *pfound );
+LEPT_DLL extern L_DNA * stringFindEachSubstr ( const char *src, const char *sub );
+LEPT_DLL extern l_int32 stringFindSubstr ( const char *src, const char *sub, l_int32 *ploc );
+LEPT_DLL extern l_uint8 * arrayReplaceEachSequence ( const l_uint8 *datas, size_t dataslen, const l_uint8 *seq, size_t seqlen, const l_uint8 *newseq, size_t newseqlen, size_t *pdatadlen, 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_ok 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, size_t oldsize, size_t 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_ok l_binaryWrite ( const char *filename, const char *operation, const 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 ( const l_uint8 *datas, size_t size );
+LEPT_DLL extern l_ok l_binaryCompare ( const l_uint8 *data1, size_t size1, const l_uint8 *data2, size_t size2, l_int32 *psame );
+LEPT_DLL extern l_ok fileCopy ( const char *srcfile, const char *newfile );
+LEPT_DLL extern l_ok fileConcatenate ( const char *srcfile, const char *destfile );
+LEPT_DLL extern l_ok fileAppendString ( const char *filename, const char *str );
+LEPT_DLL extern l_ok fileSplitLinesUniform ( const char *filename, l_int32 n, l_int32 save_empty, const char *rootpath, const char *ext );
+LEPT_DLL extern FILE * fopenReadStream ( const char *filename );
+LEPT_DLL extern FILE * fopenWriteStream ( const char *filename, const char *modestring );
+LEPT_DLL extern FILE * fopenReadFromMemory ( const l_uint8 *data, size_t size );
+LEPT_DLL extern FILE * fopenWriteWinTempfile ( void );
+LEPT_DLL extern FILE * lept_fopen ( const char *filename, const char *mode );
+LEPT_DLL extern l_ok 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 void callSystemDebug ( const char *cmd );
+LEPT_DLL extern l_ok splitPathAtDirectory ( const char *pathname, char **pdir, char **ptail );
+LEPT_DLL extern l_ok 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_ok convertSepCharsInPath ( char *path, l_int32 type );
+LEPT_DLL extern char * genPathname ( const char *dir, const char *fname );
+LEPT_DLL extern l_ok makeTempDirname ( char *result, size_t nbytes, const char *subdir );
+LEPT_DLL extern l_ok modifyTrailingSlash ( char *path, size_t nbytes, l_int32 flag );
+LEPT_DLL extern char * l_makeTempFilename ( void );
+LEPT_DLL extern l_int32 extractNumberFromFilename ( const char *fname, l_int32 numpre, l_int32 numpost );
+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_ok wshedApply ( L_WSHED *wshed );
+LEPT_DLL extern l_ok 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 l_ok pixaWriteWebPAnim ( const char *filename, PIXA *pixa, l_int32 loopcount, l_int32 duration, l_int32 quality, l_int32 lossless );
+LEPT_DLL extern l_ok pixaWriteStreamWebPAnim ( FILE *fp, PIXA *pixa, l_int32 loopcount, l_int32 duration, l_int32 quality, l_int32 lossless );
+LEPT_DLL extern l_ok pixaWriteMemWebPAnim ( l_uint8 **pencdata, size_t *pencsize, PIXA *pixa, l_int32 loopcount, l_int32 duration, l_int32 quality, l_int32 lossless );
+LEPT_DLL extern PIX * pixReadStreamWebP ( FILE *fp );
+LEPT_DLL extern PIX * pixReadMemWebP ( const l_uint8 *filedata, size_t filesize );
+LEPT_DLL extern l_ok readHeaderWebP ( const char *filename, l_int32 *pw, l_int32 *ph, l_int32 *pspp );
+LEPT_DLL extern l_ok readHeaderMemWebP ( const l_uint8 *data, size_t size, l_int32 *pw, l_int32 *ph, l_int32 *pspp );
+LEPT_DLL extern l_ok pixWriteWebP ( const char *filename, PIX *pixs, l_int32 quality, l_int32 lossless );
+LEPT_DLL extern l_ok pixWriteStreamWebP ( FILE *fp, PIX *pixs, l_int32 quality, l_int32 lossless );
+LEPT_DLL extern l_ok pixWriteMemWebP ( l_uint8 **pencdata, size_t *pencsize, PIX *pixs, l_int32 quality, l_int32 lossless );
+LEPT_DLL extern l_int32 l_jpegSetQuality ( l_int32 new_quality );
+LEPT_DLL extern void setLeptDebugOK ( l_int32 allow );
+LEPT_DLL extern l_ok pixaWriteFiles ( const char *rootname, PIXA *pixa, l_int32 format );
+LEPT_DLL extern l_ok pixWriteDebug ( const char *fname, PIX *pix, l_int32 format );
+LEPT_DLL extern l_ok pixWrite ( const char *fname, PIX *pix, l_int32 format );
+LEPT_DLL extern l_ok pixWriteAutoFormat ( const char *filename, PIX *pix );
+LEPT_DLL extern l_ok pixWriteStream ( FILE *fp, PIX *pix, l_int32 format );
+LEPT_DLL extern l_ok pixWriteImpliedFormat ( const char *filename, PIX *pix, l_int32 quality, l_int32 progressive );
+LEPT_DLL extern l_int32 pixChooseOutputFormat ( PIX *pix );
+LEPT_DLL extern l_int32 getImpliedFileFormat ( const char *filename );
+LEPT_DLL extern l_ok pixGetAutoFormat ( PIX *pix, l_int32 *pformat );
+LEPT_DLL extern const char * getFormatExtension ( l_int32 format );
+LEPT_DLL extern l_ok pixWriteMem ( l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 format );
+LEPT_DLL extern l_ok l_fileDisplay ( const char *fname, l_int32 x, l_int32 y, l_float32 scale );
+LEPT_DLL extern l_ok pixDisplay ( PIX *pixs, l_int32 x, l_int32 y );
+LEPT_DLL extern l_ok pixDisplayWithTitle ( PIX *pixs, l_int32 x, l_int32 y, const char *title, l_int32 dispflag );
+LEPT_DLL extern PIX * pixMakeColorSquare ( l_uint32 color, l_int32 size, l_int32 addlabel, l_int32 location, l_uint32 textcolor );
+LEPT_DLL extern void l_chooseDisplayProg ( l_int32 selection );
+LEPT_DLL extern void changeFormatForMissingLib ( l_int32 *pformat );
+LEPT_DLL extern l_ok pixDisplayWrite ( PIX *pixs, l_int32 reduction );
+LEPT_DLL extern l_uint8 * zlibCompress ( const l_uint8 *datain, size_t nin, size_t *pnout );
+LEPT_DLL extern l_uint8 * zlibUncompress ( const l_uint8 *datain, size_t nin, size_t *pnout );
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* NO_PROTOS */
+
+
+#endif /* LEPTONICA_ALLHEADERS_H */
+
diff --git a/leptonica/src/allheaders_bot.txt b/leptonica/src/allheaders_bot.txt
new file mode 100644
index 00000000..a04c6818
--- /dev/null
+++ b/leptonica/src/allheaders_bot.txt
@@ -0,0 +1,5 @@
+#endif /* NO_PROTOS */
+
+
+#endif /* LEPTONICA_ALLHEADERS_H */
+
diff --git a/leptonica/src/allheaders_top.txt b/leptonica/src/allheaders_top.txt
new file mode 100644
index 00000000..a77c74c9
--- /dev/null
+++ b/leptonica/src/allheaders_top.txt
@@ -0,0 +1,37 @@
+/*====================================================================*
+ - 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 81
+#define LIBLEPT_PATCH_VERSION 0
+
+#include "alltypes.h"
+
+#ifndef NO_PROTOS
diff --git a/leptonica/src/alltypes.h b/leptonica/src/alltypes.h
new file mode 100644
index 00000000..9fc4dfba
--- /dev/null
+++ b/leptonica/src/alltypes.h
@@ -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.
+ *====================================================================*/
+
+#ifndef LEPTONICA_ALLTYPES_H
+#define LEPTONICA_ALLTYPES_H
+
+ /* Standard */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+ /* General and configuration defs */
+#include "endianness.h"
+#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 "colorfill.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/leptonica/src/array.h b/leptonica/src/array.h
new file mode 100644
index 00000000..5c13977a
--- /dev/null
+++ b/leptonica/src/array.h
@@ -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.
+ *====================================================================*/
+
+#ifndef LEPTONICA_ARRAY_H
+#define LEPTONICA_ARRAY_H
+
+/*!
+ * \file array.h
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+
+
+/*------------------------------------------------------------------------*
+ * Array Structs *
+ *------------------------------------------------------------------------*/
+
+/*! Numa version for serialization */
+#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;
+
+/*! Dna version for serialization */
+#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; /*!< array of L_Dna */
+};
+typedef struct L_DnaHash L_DNAHASH;
+
+/*! Sarray version for serialization */
+#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 *
+ *------------------------------------------------------------------------*/
+/*! Numa Interpolation */
+enum {
+ L_LINEAR_INTERP = 1, /*!< linear */
+ L_QUADRATIC_INTERP = 2 /*!< quadratic */
+};
+
+/*! Border Adding */
+enum {
+ L_CONTINUED_BORDER = 1, /*!< extended with same value */
+ L_SLOPE_BORDER = 2, /*!< extended with constant normal derivative */
+ L_MIRRORED_BORDER = 3 /*!< mirrored */
+};
+
+/*! Numa Data Conversion */
+enum {
+ L_INTEGER_VALUE = 1, /*!< convert to integer */
+ L_FLOAT_VALUE = 2 /*!< convert to float */
+};
+
+#endif /* LEPTONICA_ARRAY_H */
diff --git a/leptonica/src/arrayaccess.c b/leptonica/src/arrayaccess.c
new file mode 100644
index 00000000..1dd337da
--- /dev/null
+++ b/leptonica/src/arrayaccess.c
@@ -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.
+ *====================================================================*/
+
+/*!
+ * \file arrayaccess.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*----------------------------------------------------------------------*
+ * Access within an array of 32-bit words *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief l_getDataBit()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] n pixel index
+ * \return val of the nth 1-bit pixel.
+ */
+l_int32
+l_getDataBit(const void *line,
+ l_int32 n)
+{
+ return (*((const l_uint32 *)line + (n >> 5)) >> (31 - (n & 31))) & 1;
+}
+
+
+/*!
+ * \brief l_setDataBit()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] 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));
+}
+
+
+/*!
+ * \brief l_clearDataBit()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] 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));
+}
+
+
+/*!
+ * \brief l_setDataBitVal()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] n pixel index
+ * \param[in] val val to be inserted: 0 or 1
+ * \return void
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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 |= (l_uint32)val << (31 - (n & 31)); /* set */
+}
+
+
+/*!
+ * \brief l_getDataDibit()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] n pixel index
+ * \return val of the nth 2-bit pixel.
+ */
+l_int32
+l_getDataDibit(const void *line,
+ l_int32 n)
+{
+ return (*((const l_uint32 *)line + (n >> 4)) >> (2 * (15 - (n & 15)))) & 3;
+}
+
+
+/*!
+ * \brief l_setDataDibit()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] n pixel index
+ * \param[in] 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 |= (l_uint32)(val & 3) << (30 - 2 * (n & 15)); /* set */
+}
+
+
+/*!
+ * \brief l_clearDataDibit()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] 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)));
+}
+
+
+/*!
+ * \brief l_getDataQbit()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] n pixel index
+ * \return val of the nth 4-bit pixel.
+ */
+l_int32
+l_getDataQbit(const void *line,
+ l_int32 n)
+{
+ return (*((const l_uint32 *)line + (n >> 3)) >> (4 * (7 - (n & 7)))) & 0xf;
+}
+
+
+/*!
+ * \brief l_setDataQbit()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] n pixel index
+ * \param[in] 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 |= (l_uint32)(val & 15) << (28 - 4 * (n & 7)); /* set */
+}
+
+
+/*!
+ * \brief l_clearDataQbit()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] 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)));
+}
+
+
+/*!
+ * \brief l_getDataByte()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] n pixel index
+ * \return value of the n-th byte pixel
+ */
+l_int32
+l_getDataByte(const void *line,
+ l_int32 n)
+{
+#ifdef L_BIG_ENDIAN
+ return *((const l_uint8 *)line + n);
+#else /* L_LITTLE_ENDIAN */
+ return *(l_uint8 *)((l_uintptr_t)((const l_uint8 *)line + n) ^ 3);
+#endif /* L_BIG_ENDIAN */
+}
+
+
+/*!
+ * \brief l_setDataByte()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] n pixel index
+ * \param[in] 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 */
+}
+
+
+/*!
+ * \brief l_getDataTwoBytes()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] n pixel index
+ * \return value of the n-th 2-byte pixel
+ */
+l_int32
+l_getDataTwoBytes(const void *line,
+ l_int32 n)
+{
+#ifdef L_BIG_ENDIAN
+ return *((const l_uint16 *)line + n);
+#else /* L_LITTLE_ENDIAN */
+ return *(l_uint16 *)((l_uintptr_t)((const l_uint16 *)line + n) ^ 2);
+#endif /* L_BIG_ENDIAN */
+}
+
+
+/*!
+ * \brief l_setDataTwoBytes()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] n pixel index
+ * \param[in] 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 */
+}
+
+
+/*!
+ * \brief l_getDataFourBytes()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] n pixel index
+ * \return value of the n-th 4-byte pixel
+ */
+l_int32
+l_getDataFourBytes(const void *line,
+ l_int32 n)
+{
+ return *((const l_uint32 *)line + n);
+}
+
+
+/*!
+ * \brief l_setDataFourBytes()
+ *
+ * \param[in] line ptr to beginning of data line
+ * \param[in] n pixel index
+ * \param[in] 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/leptonica/src/arrayaccess.h b/leptonica/src/arrayaccess.h
new file mode 100644
index 00000000..1a831bcc
--- /dev/null
+++ b/leptonica/src/arrayaccess.h
@@ -0,0 +1,270 @@
+/*====================================================================*
+ - 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
+
+/*!
+ * \file arrayaccess.h
+ *
+ * <pre>
+ * 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);
+ *
+ * Some compilers complain when the SET macros are surrounded by
+ * parentheses, because parens require an evaluation and it is not
+ * defined for SET macros. If SET_DATA_QBIT were defined as a
+ * compound macro, in analogy to l_setDataQbit(), it requires
+ * surrounding braces:
+ * \code
+ * #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)));}
+ * \endcode
+ * but if used in an if/else
+ * \code
+ * if (x)
+ * SET_DATA_QBIT(...);
+ * else
+ * ...
+ * \endcode
+ * the compiler sees
+ * \code
+ * if (x)
+ * {......};
+ * else
+ * ...
+ * \endcode
+ * The semicolon comes after the brace and will not compile.
+ * This can be fixed in the call by either omitting the semicolon
+ * or requiring another set of braces around SET_DATA_QBIT(), but
+ * both these options break compatibility with current code, and
+ * require special attention by anyone using the macros.
+ *
+ * There are (at least) two ways to fix this in the macro definitions,
+ * suggested by Dave Bryan.
+ * (1) Surround the braces in the macro above with
+ * do {....} while(0)
+ * Then the semicolon just terminates the expression.
+ * (2) Reduce the blocks to a single expression; e.g,
+ * *((l_uint32 *)(pdata) + ((n) >> 3)) = \
+ * *((l_uint32 *)(pdata) + ((n) >> 3)) \
+ * & ~(0xf0000000 >> (4 * ((n) & 7))) \
+ * | (((val) & 15) << (28 - 4 * ((n) & 7)))
+ * This appears to cause redundant computation, but the compiler
+ * should evaluate the common subexpression only once.
+ * All these methods have the same performance, giving about 300M
+ * SET_DATA_QBIT operations per second on a fast 64 bit system.
+ * Using the function calls instead of the macros results in about 250M
+ * SET_DATA_QBIT operations per second, a performance hit of nearly 20%.
+ * </pre>
+ */
+
+#define USE_INLINE_ACCESSORS 1
+
+#if USE_INLINE_ACCESSORS
+
+ /*=============================================================*/
+ /* Faster: use in line accessors */
+ /*=============================================================*/
+
+ /*--------------------------------------------------*
+ * 1 bit access *
+ *--------------------------------------------------*/
+/*! 1 bit access - get */
+#define GET_DATA_BIT(pdata, n) \
+ ((*((const l_uint32 *)(pdata) + ((n) >> 5)) >> (31 - ((n) & 31))) & 1)
+
+/*! 1 bit access - set */
+#define SET_DATA_BIT(pdata, n) \
+ *((l_uint32 *)(pdata) + ((n) >> 5)) |= (0x80000000 >> ((n) & 31))
+
+/*! 1 bit access - clear */
+#define CLEAR_DATA_BIT(pdata, n) \
+ *((l_uint32 *)(pdata) + ((n) >> 5)) &= ~(0x80000000 >> ((n) & 31))
+
+/*! 1 bit access - set value (0 or 1) */
+#define SET_DATA_BIT_VAL(pdata, n, val) \
+ *((l_uint32 *)(pdata) + ((n) >> 5)) = \
+ ((*((l_uint32 *)(pdata) + ((n) >> 5)) \
+ & (~(0x80000000 >> ((n) & 31)))) \
+ | ((l_uint32)(val) << (31 - ((n) & 31))))
+
+ /*--------------------------------------------------*
+ * 2 bit access *
+ *--------------------------------------------------*/
+/*! 2 bit access - get */
+#define GET_DATA_DIBIT(pdata, n) \
+ ((*((const l_uint32 *)(pdata) + ((n) >> 4)) >> (2 * (15 - ((n) & 15)))) & 3)
+
+/*! 2 bit access - set value (0 ... 3) */
+#define SET_DATA_DIBIT(pdata, n, val) \
+ *((l_uint32 *)(pdata) + ((n) >> 4)) = \
+ ((*((l_uint32 *)(pdata) + ((n) >> 4)) \
+ & (~(0xc0000000 >> (2 * ((n) & 15))))) \
+ | ((l_uint32)((val) & 3) << (30 - 2 * ((n) & 15))))
+
+/*! 2 bit access - clear */
+#define CLEAR_DATA_DIBIT(pdata, n) \
+ *((l_uint32 *)(pdata) + ((n) >> 4)) &= ~(0xc0000000 >> (2 * ((n) & 15)))
+
+
+ /*--------------------------------------------------*
+ * 4 bit access *
+ *--------------------------------------------------*/
+/*! 4 bit access - get */
+#define GET_DATA_QBIT(pdata, n) \
+ ((*((const l_uint32 *)(pdata) + ((n) >> 3)) >> (4 * (7 - ((n) & 7)))) & 0xf)
+
+/*! 4 bit access - set value (0 ... 15) */
+#define SET_DATA_QBIT(pdata, n, val) \
+ *((l_uint32 *)(pdata) + ((n) >> 3)) = \
+ ((*((l_uint32 *)(pdata) + ((n) >> 3)) \
+ & (~(0xf0000000 >> (4 * ((n) & 7))))) \
+ | ((l_uint32)((val) & 15) << (28 - 4 * ((n) & 7))))
+
+/*! 4 bit access - clear */
+#define CLEAR_DATA_QBIT(pdata, n) \
+ *((l_uint32 *)(pdata) + ((n) >> 3)) &= ~(0xf0000000 >> (4 * ((n) & 7)))
+
+
+ /*--------------------------------------------------*
+ * 8 bit access *
+ *--------------------------------------------------*/
+#ifdef L_BIG_ENDIAN
+/*! 8 bit access - get */
+#define GET_DATA_BYTE(pdata, n) \
+ (*((const l_uint8 *)(pdata) + (n)))
+#else /* L_LITTLE_ENDIAN */
+/*! 8 bit access - get */
+#define GET_DATA_BYTE(pdata, n) \
+ (*(l_uint8 *)((l_uintptr_t)((const l_uint8 *)(pdata) + (n)) ^ 3))
+#endif /* L_BIG_ENDIAN */
+
+#ifdef L_BIG_ENDIAN
+/*! 8 bit access - set value (0 ... 255) */
+#define SET_DATA_BYTE(pdata, n, val) \
+ *((l_uint8 *)(pdata) + (n)) = (val)
+#else /* L_LITTLE_ENDIAN */
+/*! 8 bit access - set value (0 ... 255) */
+#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
+/*! 16 bit access - get */
+#define GET_DATA_TWO_BYTES(pdata, n) \
+ (*((const l_uint16 *)(pdata) + (n)))
+#else /* L_LITTLE_ENDIAN */
+/*! 16 bit access - get */
+#define GET_DATA_TWO_BYTES(pdata, n) \
+ (*(l_uint16 *)((l_uintptr_t)((const l_uint16 *)(pdata) + (n)) ^ 2))
+#endif /* L_BIG_ENDIAN */
+
+#ifdef L_BIG_ENDIAN
+/*! 16 bit access - set value (0 ... 65535) */
+#define SET_DATA_TWO_BYTES(pdata, n, val) \
+ *((l_uint16 *)(pdata) + (n)) = (val)
+#else /* L_LITTLE_ENDIAN */
+/*! 16 bit access - set value (0 ... 65535) */
+#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 *
+ *--------------------------------------------------*/
+/*! 32 bit access - get */
+#define GET_DATA_FOUR_BYTES(pdata, n) \
+ (*((const l_uint32 *)(pdata) + (n)))
+
+/*! 32 bit access - set (0 ... 4294967295) */
+#define SET_DATA_FOUR_BYTES(pdata, n, val) \
+ *((l_uint32 *)(pdata) + (n)) = (val)
+
+
+#else
+
+ /*=============================================================*/
+ /* Slower: use function calls for all accessors */
+ /*=============================================================*/
+
+#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 */
+
+
+#endif /* LEPTONICA_ARRAY_ACCESS_H */
diff --git a/leptonica/src/bardecode.c b/leptonica/src/bardecode.c
new file mode 100644
index 00000000..3424bc78
--- /dev/null
+++ b/leptonica/src/bardecode.c
@@ -0,0 +1,1047 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file bardecode.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief barcodeDispatchDecoder()
+ *
+ * \param[in] barstr string of integers in set {1,2,3,4} of bar widths
+ * \param[in] format L_BF_ANY, L_BF_CODEI2OF5, L_BF_CODE93, ...
+ * \param[in] 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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief barcodeFindFormat()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief barcodeFormatIsSupported()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief barcodeVerifyFormat()
+ *
+ * \param[in] barstr of barcode widths, in set {1,2,3,4}
+ * \param[in] format L_BF_CODEI2OF5, L_BF_CODE93, ...
+ * \param[out] pvalid 0 if not valid, 1 and 2 if valid
+ * \param[out] preverse [optional] 1 if reversed; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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) Require at least 12 data bits, in addition to format identifiers.
+ * (TODO) If the barcode has a fixed length, this should be used
+ * explicitly, as is done for L_BF_UPCA and L_BF_EAN13.
+ * (4) (TODO) Add to this as more formats are supported.
+ * </pre>
+ */
+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);
+ if (len < 20)
+ return ERROR_INT("barstr too short for CODE2OF5", procName, 1);
+ 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);
+ if (len < 20)
+ return ERROR_INT("barstr too short for CODEI2OF5", procName, 1);
+ 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);
+ if (len < 28)
+ return ERROR_INT("barstr too short for CODE93", procName, 1);
+ 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);
+ if (len < 30)
+ return ERROR_INT("barstr too short for CODE39", procName, 1);
+ 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);
+ if (len < 26)
+ return ERROR_INT("barstr too short for CODABAR", procName, 1);
+ 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)
+ return ERROR_INT("invalid length for UPCA or EAN13", procName, 1);
+ 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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief barcodeDecode2of5()
+ *
+ * \param[in] barstr of widths, in set {1, 2}
+ * \param[in] debugflag
+ * \return data string of digits, or NULL if none found or on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ LEPT_FREE(vbarstr);
+ 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)
+ lept_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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief barcodeDecodeI2of5()
+ *
+ * \param[in] barstr of widths, in set {1, 2}
+ * \param[in] debugflag
+ * \return data string of digits, or NULL if none found or on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ LEPT_FREE(vbarstr);
+ 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)
+ lept_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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief barcodeDecode93()
+ *
+ * \param[in] barstr of widths, in set {1, 2, 3, 4}
+ * \param[in] debugflag
+ * \return data string of digits, or NULL if none found or on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ LEPT_FREE(vbarstr);
+ 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)
+ lept_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];
+ lept_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];
+ lept_stderr("checkk = %s\n", checkk);
+ }
+
+ /* Remove the two check codes from the output */
+ data[nsymb - 2] = '\0';
+
+ LEPT_FREE(index);
+ return data;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Code 39 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief barcodeDecode39()
+ *
+ * \param[in] barstr of widths, in set {1, 2}
+ * \param[in] debugflag
+ * \return data string of digits, or NULL if none found or on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ LEPT_FREE(vbarstr);
+ 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)
+ lept_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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief barcodeDecodeCodabar()
+ *
+ * \param[in] barstr of widths, in set {1, 2}
+ * \param[in] debugflag
+ * \return data string of digits, or NULL if none found or on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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) {
+ LEPT_FREE(vbarstr);
+ 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)
+ lept_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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief barcodeDecodeUpca()
+ *
+ * \param[in] barstr of widths, in set {1, 2, 3, 4}
+ * \param[in] debugflag
+ * \return data string of digits, or NULL if none found or on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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)
+ lept_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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief barcodeDecodeEan13()
+ *
+ * \param[in] barstr of widths, in set {1, 2, 3, 4}
+ * \param[in] first first digit: 0 - 9
+ * \param[in] debugflag
+ * \return data string of digits, or NULL if none found or on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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)
+ lept_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/leptonica/src/baseline.c b/leptonica/src/baseline.c
new file mode 100644
index 00000000..bda8df78
--- /dev/null
+++ b/leptonica/src/baseline.c
@@ -0,0 +1,600 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file baseline.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+ /* Min to travel after finding max before abandoning peak */
+static const l_int32 MinDistInPeak = 35;
+
+ /* Thresholds for peaks and zeros, relative to the max peak */
+static const l_int32 PeakThresholdRatio = 20;
+static const l_int32 ZeroThresholdRatio = 100;
+
+ /* Default values for determining local skew */
+static const l_int32 DefaultSlices = 10;
+static const l_int32 DefaultSweepReduction = 2;
+static const l_int32 DefaultBsReduction = 1;
+static const l_float32 DefaultSweepRange = 5.; /* degrees */
+static const l_float32 DefaultSweepDelta = 1.; /* degrees */
+static const l_float32 DefaultMinbsDelta = 0.01f; /* degrees */
+
+ /* Overlap slice fraction added to top and bottom of each slice */
+static const l_float32 OverlapFraction = 0.5;
+
+ /* Minimum allowed confidence (ratio) for accepting a value */
+static const l_float32 MinAllowedConfidence = 3.0;
+
+
+/*---------------------------------------------------------------------*
+ * Locate text baselines in an image *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixFindBaselines()
+ *
+ * \param[in] pixs 1 bpp, 300 ppi
+ * \param[out] ppta [optional] pairs of pts corresponding to
+ * approx. ends of each text line
+ * \param[in] pixadb for debug output; use NULL to skip
+ * \return na of baseline y values, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+NUMA *
+pixFindBaselines(PIX *pixs,
+ PTA **ppta,
+ PIXA *pixadb)
+{
+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 *pix1, *pix2;
+PTA *pta;
+
+ PROCNAME("pixFindBaselines");
+
+ if (ppta) *ppta = NULL;
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+
+ /* Close up the text characters, removing noise */
+ pix1 = pixMorphSequence(pixs, "c25.1 + e15.1", 0);
+
+ /* Estimate the resolution */
+ if (pixadb) pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT);
+
+ /* Save the difference of adjacent row sums.
+ * The high positive-going peaks are the baselines */
+ if ((nasum = pixCountPixelsByRow(pix1, NULL)) == NULL) {
+ pixDestroy(&pix1);
+ 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);
+ }
+ numaDestroy(&nasum);
+
+ if (pixadb) { /* show the difference signal */
+ lept_mkdir("lept/baseline");
+ gplotSimple1(nadiff, GPLOT_PNG, "/tmp/lept/baseline/diff", "Diff Sig");
+ pix2 = pixRead("/tmp/lept/baseline/diff.png");
+ pixaAddPix(pixadb, pix2, L_INSERT);
+ }
+
+ /* Use the zeroes of the profile to locate each baseline. */
+ array = numaGetIArray(nadiff);
+ ndiff = numaGetCount(nadiff);
+ numaGetMax(nadiff, &maxval, &imaxloc);
+ numaDestroy(&nadiff);
+
+ /* Use this to begin locating a new peak: */
+ peakthresh = (l_int32)maxval / PeakThresholdRatio;
+ /* Use this to begin a region between peaks: */
+ zerothresh = (l_int32)maxval / ZeroThresholdRatio;
+
+ 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 + MinDistInPeak; /* 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 + MinDistInPeak;
+ } else if (i > mintosearch && array[i] <= zerothresh) { /* leave */
+ inpeak = FALSE;
+ numaAddNumber(naval, max);
+ numaAddNumber(naloc, maxloc);
+ }
+ }
+ }
+ LEPT_FREE(array);
+
+ /* If array[ndiff-1] is max, eg. no descenders, baseline at bottom */
+ if (inpeak) {
+ numaAddNumber(naval, max);
+ numaAddNumber(naloc, maxloc);
+ }
+
+ if (pixadb) { /* show the raster locations for the peaks */
+ gplot = gplotCreate("/tmp/lept/baseline/loc", GPLOT_PNG, "Peak locs",
+ "rasterline", "height");
+ gplotAddPlot(gplot, naloc, naval, GPLOT_POINTS, "locs");
+ gplotMakeOutput(gplot);
+ gplotDestroy(&gplot);
+ pix2 = pixRead("/tmp/lept/baseline/loc.png");
+ pixaAddPix(pixadb, pix2, L_INSERT);
+ }
+ numaDestroy(&naval);
+
+ /* 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. */
+ pix2 = pixMorphSequence(pix1, "r11 + c20.1 + o30.1 +c1.3", 0);
+ if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
+ boxa1 = pixConnComp(pix2, NULL, 4);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ if (boxaGetCount(boxa1) == 0) {
+ numaDestroy(&naloc);
+ boxaDestroy(&boxa1);
+ L_INFO("no compnents after filtering\n", procName);
+ return NULL;
+ }
+ boxa2 = boxaTransform(boxa1, 0, 0, 4., 4.);
+ boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+
+ /* Optionally, find the baseline segments */
+ pta = NULL;
+ if (ppta) {
+ pta = ptaCreate(0);
+ *ppta = pta;
+ }
+ 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;
+ }
+ }
+ }
+ boxaDestroy(&boxa3);
+
+ if (pixadb && pta) { /* display baselines */
+ l_int32 npts, x1, y1, x2, y2;
+ pix1 = pixConvertTo32(pixs);
+ npts = ptaGetCount(pta);
+ for (i = 0; i < npts; i += 2) {
+ ptaGetIPt(pta, i, &x1, &y1);
+ ptaGetIPt(pta, i + 1, &x2, &y2);
+ pixRenderLineArb(pix1, x1, y1, x2, y2, 2, 255, 0, 0);
+ }
+ pixWriteDebug("/tmp/lept/baseline/baselines.png", pix1, IFF_PNG);
+ pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ return naloc;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Projective transform to remove local skew *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixDeskewLocal()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] nslices the number of horizontal overlapping slices;
+ * must be larger than 1 and not exceed 20;
+ * use 0 for default
+ * \param[in] redsweep sweep reduction factor: 1, 2, 4 or 8;
+ * use 0 for default value
+ * \param[in] redsearch search reduction factor: 1, 2, 4 or 8, and
+ * not larger than redsweep; use 0 for default value
+ * \param[in] sweeprange half the full range, assumed about 0; in degrees;
+ * use 0.0 for default value
+ * \param[in] sweepdelta angle increment of sweep; in degrees;
+ * use 0.0 for default value
+ * \param[in] minbsdelta min binary search increment angle; in degrees;
+ * use 0.0 for default value
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 || pixGetDepth(pixs) != 1)
+ return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixGetLocalSkewTransform()
+ *
+ * \param[in] pixs
+ * \param[in] nslices the number of horizontal overlapping slices;
+ * must be larger than 1 and not exceed 20;
+ * use 0 for default
+ * \param[in] redsweep sweep reduction factor: 1, 2, 4 or 8;
+ * use 0 for default value
+ * \param[in] redsearch search reduction factor: 1, 2, 4 or 8, and not
+ * larger than redsweep; use 0 for default value
+ * \param[in] sweeprange half the full range, assumed about 0;
+ * in degrees; use 0.0 for default value
+ * \param[in] sweepdelta angle increment of sweep; in degrees;
+ * use 0.0 for default value
+ * \param[in] minbsdelta min binary search increment angle; in degrees;
+ * use 0.0 for default value
+ * \param[out] pptas 4 points in the source
+ * \param[out] pptad the corresponding 4 pts in the dest
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 || pixGetDepth(pixs) != 1)
+ return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+ if (nslices < 2 || nslices > 20)
+ nslices = DefaultSlices;
+ if (redsweep < 1 || redsweep > 8)
+ redsweep = DefaultSweepReduction;
+ if (redsearch < 1 || redsearch > redsweep)
+ redsearch = DefaultBsReduction;
+ if (sweeprange == 0.0)
+ sweeprange = DefaultSweepRange;
+ if (sweepdelta == 0.0)
+ sweepdelta = DefaultSweepDelta;
+ if (minbsdelta == 0.0)
+ minbsdelta = DefaultMinbsDelta;
+
+ naskew = pixGetLocalSkewAngles(pixs, nslices, redsweep, redsearch,
+ sweeprange, sweepdelta, minbsdelta,
+ NULL, NULL, 0);
+ if (!naskew)
+ return ERROR_INT("naskew not made", procName, 1);
+
+ deg2rad = 3.14159265f / 180.f;
+ 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;
+}
+
+
+/*!
+ * \brief pixGetLocalSkewAngles()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] nslices the number of horizontal overlapping slices;
+ * must be larger than 1 and not exceed 20;
+ * use 0 for default
+ * \param[in] redsweep sweep reduction factor: 1, 2, 4 or 8;
+ * use 0 for default value
+ * \param[in] redsearch search reduction factor: 1, 2, 4 or 8, and not
+ * larger than redsweep; use 0 for default value
+ * \param[in] sweeprange half the full range, assumed about 0;
+ * in degrees; use 0.0 for default value
+ * \param[in] sweepdelta angle increment of sweep; in degrees;
+ * use 0.0 for default value
+ * \param[in] minbsdelta min binary search increment angle; in degrees;
+ * use 0.0 for default value
+ * \param[out] pa [optional] slope of skew as fctn of y
+ * \param[out] pb [optional] intercept at y = 0 of skew,
+ 8 as a function of y
+ * \param[in] debug 1 for generating plot of skew angle vs. y;
+ * 0 otherwise
+ * \return naskew, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 debug)
+{
+l_int32 w, h, hs, i, ystart, yend, ovlap, npts;
+l_float32 angle, conf, ycenter, a, b;
+BOX *box;
+GPLOT *gplot;
+NUMA *naskew, *nax, *nay;
+PIX *pix;
+PTA *pta;
+
+ PROCNAME("pixGetLocalSkewAngles");
+
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+ if (nslices < 2 || nslices > 20)
+ nslices = DefaultSlices;
+ if (redsweep < 1 || redsweep > 8)
+ redsweep = DefaultSweepReduction;
+ if (redsearch < 1 || redsearch > redsweep)
+ redsearch = DefaultBsReduction;
+ if (sweeprange == 0.0)
+ sweeprange = DefaultSweepRange;
+ if (sweepdelta == 0.0)
+ sweepdelta = DefaultSweepDelta;
+ if (minbsdelta == 0.0)
+ minbsdelta = DefaultMinbsDelta;
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ hs = h / nslices;
+ ovlap = (l_int32)(OverlapFraction * 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 = (l_float32)(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 > MinAllowedConfidence)
+ ptaAddPt(pta, ycenter, angle);
+ pixDestroy(&pix);
+ boxDestroy(&box);
+ }
+
+ /* 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) {
+ lept_mkdir("lept/baseline");
+ ptaGetArrays(pta, &nax, &nay);
+ gplot = gplotCreate("/tmp/lept/baseline/skew", GPLOT_PNG,
+ "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);
+ }
+
+ ptaDestroy(&pta);
+ return naskew;
+}
diff --git a/leptonica/src/bbuffer.c b/leptonica/src/bbuffer.c
new file mode 100644
index 00000000..e478afbf
--- /dev/null
+++ b/leptonica/src/bbuffer.c
@@ -0,0 +1,485 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file bbuffer.c
+ * <pre>
+ *
+ * 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:
+ *
+ * \code
+ * 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
+ * \endcode
+ * 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().
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Bounds on array size */
+static const l_uint32 MaxArraySize = 1000000000; /* 10^9 bytes */
+static const l_int32 InitialArraySize = 1024; /*!< n'importe quoi */
+
+/*--------------------------------------------------------------------------*
+ * BBuffer create/destroy *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief bbufferCreate()
+ *
+ * \param[in] indata address in memory [optional]
+ * \param[in] nalloc size of byte array to be alloc'd 0 for default
+ * \return bbuffer, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+L_BBUFFER *
+bbufferCreate(const l_uint8 *indata,
+ l_int32 nalloc)
+{
+L_BBUFFER *bb;
+
+ PROCNAME("bbufferCreate");
+
+ if (nalloc <= 0 || nalloc > MaxArraySize)
+ nalloc = InitialArraySize;
+
+ bb = (L_BBUFFER *)LEPT_CALLOC(1, sizeof(L_BBUFFER));
+ if ((bb->array = (l_uint8 *)LEPT_CALLOC(nalloc, sizeof(l_uint8))) == NULL) {
+ LEPT_FREE(bb);
+ return (L_BBUFFER *)ERROR_PTR("byte array not made", procName, NULL);
+ }
+ bb->nalloc = nalloc;
+ bb->nwritten = 0;
+
+ if (indata) {
+ memcpy(bb->array, indata, nalloc);
+ bb->n = nalloc;
+ } else {
+ bb->n = 0;
+ }
+
+ return bb;
+}
+
+
+/*!
+ * \brief bbufferDestroy()
+ *
+ * \param[in,out] pbb will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Destroys the byte array in the bbuffer and then the bbuffer;
+ * then nulls the contents of the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief bbufferDestroyAndSaveData()
+ *
+ * \param[in,out] pbb input data buffer; will be nulled
+ * \param[out] pnbytes number of bytes saved in array
+ * \return barray newly allocated array of data
+ *
+ * <pre>
+ * Notes:
+ * (1) Copies data to newly allocated array; then destroys the bbuffer.
+ * </pre>
+ */
+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(array, bb->array + bb->nwritten, nbytes);
+
+ bbufferDestroy(pbb);
+ return array;
+}
+
+
+/*--------------------------------------------------------------------------*
+ * Operations to read data INTO a BBuffer *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief bbufferRead()
+ *
+ * \param[in] bb bbuffer
+ * \param[in] src source memory buffer from which bytes are read
+ * \param[in] nbytes bytes to be read
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+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(bb->array, 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);
+ if (bbufferExtendArray(bb, nadd))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+
+ /* Read in the new bytes */
+ memcpy(bb->array + bb->n, src, nbytes);
+ bb->n += nbytes;
+ return 0;
+}
+
+
+/*!
+ * \brief bbufferReadStream()
+ *
+ * \param[in] bb bbuffer
+ * \param[in] fp source stream from which bytes are read
+ * \param[in] nbytes bytes to be read
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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(bb->array, 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);
+ if (bbufferExtendArray(bb, nadd))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+
+ /* Read in the new bytes */
+ nread = fread(bb->array + bb->n, 1, nbytes, fp);
+ bb->n += nread;
+
+ return 0;
+}
+
+
+/*!
+ * \brief bbufferExtendArray()
+ *
+ * \param[in] bb bbuffer
+ * \param[in] nbytes number of bytes to extend array size
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) reallocNew() copies all bb->nalloc bytes, even though
+ * only bb->n are data.
+ * </pre>
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief bbufferWrite()
+ *
+ * \param[in] bb bbuffer
+ * \param[in] dest dest memory buffer to which bytes are written
+ * \param[in] nbytes bytes requested to be written
+ * \param[out] pnout bytes actually written
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+bbufferWrite(L_BBUFFER *bb,
+ l_uint8 *dest,
+ size_t nbytes,
+ size_t *pnout)
+{
+size_t 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, 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;
+}
+
+
+/*!
+ * \brief bbufferWriteStream()
+ *
+ * \param[in] bb bbuffer
+ * \param[in] fp dest stream to which bytes are written
+ * \param[in] nbytes bytes requested to be written
+ * \param[out] pnout bytes actually written
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+bbufferWriteStream(L_BBUFFER *bb,
+ FILE *fp,
+ size_t nbytes,
+ size_t *pnout)
+{
+size_t 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(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/leptonica/src/bbuffer.h b/leptonica/src/bbuffer.h
new file mode 100644
index 00000000..945cbb0f
--- /dev/null
+++ b/leptonica/src/bbuffer.h
@@ -0,0 +1,60 @@
+/*====================================================================*
+ - 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
+
+/*!
+ * \file bbuffer.h
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+
+/*! Expandable byte buffer for memory read/write operations */
+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/leptonica/src/bilateral.c b/leptonica/src/bilateral.c
new file mode 100644
index 00000000..179a2fde
--- /dev/null
+++ b/leptonica/src/bilateral.c
@@ -0,0 +1,829 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file bilateral.c
+ * <pre>
+ *
+ * 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).
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief pixBilateral()
+ *
+ * \param[in] pixs 8 bpp gray or 32 bpp rgb, no colormap
+ * \param[in] spatial_stdev of gaussian kernel; in pixels, > 0.5
+ * \param[in] range_stdev of gaussian range kernel; > 5.0; typ. 50.0
+ * \param[in] ncomps number of intermediate sums J(k,x);
+ * in [4 ... 30]
+ * \param[in] reduction 1, 2 or 4
+ * \return pixd bilateral filtered image, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * (8) The image must be sufficiently big to get reasonable results.
+ * This requires the dimensions to be at least twice the filter size.
+ * Otherwise, return a copy of the input with warning.
+ * </pre>
+ */
+PIX *
+pixBilateral(PIX *pixs,
+ l_float32 spatial_stdev,
+ l_float32 range_stdev,
+ l_int32 ncomps,
+ l_int32 reduction)
+{
+l_int32 w, h, d, filtersize;
+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);
+ pixGetDimensions(pixs, &w, &h, &d);
+ 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);
+ filtersize = (l_int32)(2.0 * spatial_stdev + 1.0 + 0.5);
+ if (w < 2 * filtersize || h < 2 * filtersize) {
+ L_WARNING("w = %d, h = %d; w or h < 2 * filtersize = %d; "
+ "returning copy\n", procName, w, h, 2 * filtersize);
+ return pixCopy(NULL, pixs);
+ }
+ 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;
+}
+
+
+/*!
+ * \brief pixBilateralGray()
+ *
+ * \param[in] pixs 8 bpp gray
+ * \param[in] spatial_stdev of gaussian kernel; in pixels, > 0.5
+ * \param[in] range_stdev of gaussian range kernel; > 5.0; typ. 50.0
+ * \param[in] ncomps number of intermediate sums J(k,x);
+ * in [4 ... 30]
+ * \param[in] reduction 1, 2 or 4
+ * \return pixd 8 bpp bilateral filtered image, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixBilateral() for constraints on the input parameters.
+ * (2) See pixBilateral() for algorithm details.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief bilateralCreate()
+ *
+ * \param[in] pixs 8 bpp gray, no colormap
+ * \param[in] spatial_stdev of gaussian kernel; in pixels, > 0.5
+ * \param[in] range_stdev of gaussian range kernel; > 5.0; typ. 50.0
+ * \param[in] ncomps number of intermediate sums J(k,x);
+ * in [4 ... 30]
+ * \param[in] reduction 1, 2 or 4
+ * \return bil, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *pix1, *pix2, *pixt, *pixsc, *pixd;
+PIXA *pixac;
+
+ PROCNAME("bilateralCreate");
+
+ if (reduction == 1) {
+ pix1 = pixClone(pixs);
+ } else if (reduction == 2) {
+ pix1 = pixScaleAreaMap2(pixs);
+ } else { /* reduction == 4) */
+ pix2 = pixScaleAreaMap2(pixs);
+ pix1 = pixScaleAreaMap2(pix2);
+ pixDestroy(&pix2);
+ }
+ if (!pix1)
+ return (L_BILATERAL *)ERROR_PTR("pix1 not made", procName, NULL);
+
+ sstdev = spatial_stdev / (l_float32)reduction; /* reduced spat. stdev */
+ border = (l_int32)(2 * sstdev + 1);
+ pixsc = pixAddMirroredBorder(pix1, border, border, border, border);
+ pixGetExtremeValue(pix1, 1, L_SELECT_MIN, NULL, NULL, NULL, &minval);
+ pixGetExtremeValue(pix1, 1, L_SELECT_MAX, NULL, NULL, NULL, &maxval);
+ pixDestroy(&pix1);
+ if (!pixsc)
+ return (L_BILATERAL *)ERROR_PTR("pixsc not made", procName, NULL);
+
+ bil = (L_BILATERAL *)LEPT_CALLOC(1, sizeof(L_BILATERAL));
+ bil->spatial_stdev = sstdev;
+ bil->range_stdev = range_stdev;
+ bil->reduction = reduction;
+ bil->ncomps = ncomps;
+ bil->minval = minval;
+ bil->maxval = maxval;
+ bil->pixsc = pixsc;
+ 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++)
+ lept_stderr("kindex[%d] = %d; kfract[%d] = %5.3f\n",
+ i, kindex[i], i, kfract[i]);
+ for (i = 0; i < ncomps; i++)
+ lept_stderr("nc[%d] = %d\n", i, nc[i]);
+#endif /* DEBUG_BILATERAL */
+
+ /* -------------------------------------------------------------------- *
+ * Generate 1-D kernel arrays (spatial and range) *
+ * -------------------------------------------------------------------- */
+ spatial_size = 2 * sstdev + 1; /* same as the added border */
+ 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;
+ }
+ if (norm > 0.0) {
+ 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;
+ }
+ if (norm > 0.0)
+ dval = (l_int32)((sum / norm) + 0.5);
+ else
+ dval = GET_DATA_BYTE(linet, border + j);
+ 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;
+}
+
+
+/*!
+ * \brief bilateralApply()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief bilateralDestroy()
+ *
+ * \param[in,out] pbil will be set to null before returning
+ */
+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;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Exact implementation of grayscale or color bilateral filtering *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixBilateralExact()
+ *
+ * \param[in] pixs 8 bpp gray or 32 bpp rgb
+ * \param[in] spatial_kel gaussian kernel
+ * \param[in] range_kel [optional] 256 x 1, monotonically decreasing
+ * \return pixd 8 bpp bilateral filtered image
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+ }
+}
+
+
+/*!
+ * \brief pixBilateralGrayExact()
+ *
+ * \param[in] pixs 8 bpp gray
+ * \param[in] spatial_kel gaussian kernel
+ * \param[in] range_kel [optional] 256 x 1, monotonically decreasing
+ * \return pixd 8 bpp bilateral filtered image
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixBilateralExact().
+ * </pre>
+ */
+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);
+ kernelGetParameters(spatial_kel, &sy, &sx, NULL, NULL);
+ if (w < 2 * sx + 1 || h < 2 * sy + 1) {
+ L_WARNING("w = %d < 2 * sx + 1 = %d, or h = %d < 2 * sy + 1 = %d; "
+ "returning copy\n", procName, w, 2 * sx + 1, h, 2 * sy + 1);
+ return pixCopy(NULL, pixs);
+ }
+ 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) {
+ kernelDestroy(&keli);
+ 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;
+}
+
+
+/*!
+ * \brief pixBlockBilateralExact()
+ *
+ * \param[in] pixs 8 bpp gray or 32 bpp rgb
+ * \param[in] spatial_stdev must be > 0.0
+ * \param[in] range_stdev must be > 0.0
+ * \return pixd 8 bpp or 32 bpp bilateral filtered image
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief makeRangeKernel()
+ *
+ * \param[in] range_stdev must be > 0.0
+ * \return kel, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/bilateral.h b/leptonica/src/bilateral.h
new file mode 100644
index 00000000..e5b5bbdd
--- /dev/null
+++ b/leptonica/src/bilateral.h
@@ -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_BILATERAL_H
+#define LEPTONICA_BILATERAL_H
+
+/*!
+ * \file bilateral.h
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+
+
+/*------------------------------------------------------------------------*
+ * Bilateral filter *
+ *------------------------------------------------------------------------*/
+
+/*! 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/leptonica/src/bilinear.c b/leptonica/src/bilinear.c
new file mode 100644
index 00000000..7336e91b
--- /dev/null
+++ b/leptonica/src/bilinear.c
@@ -0,0 +1,912 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file bilinear.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+extern l_float32 AlphaMaskBorderVals[2];
+
+/*-------------------------------------------------------------*
+ * Sampled bilinear image transformation *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixBilinearSampledPta()
+ *
+ * \param[in] pixs all depths
+ * \param[in] ptad 4 pts of final coordinate space
+ * \param[in] ptas 4 pts of initial coordinate space
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixBilinearSampled()
+ *
+ * \param[in] pixs all depths
+ * \param[in] vc vector of 8 coefficients for bilinear transformation
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixBilinearPta()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] ptad 4 pts of final coordinate space
+ * \param[in] ptas 4 pts of initial coordinate space
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Brings in either black or white pixels from the boundary
+ * (2) Removes any existing colormap, if necessary, before transforming
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixBilinear()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] vc vector of 8 coefficients for bilinear transformation
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Brings in either black or white pixels from the boundary
+ * (2) Removes any existing colormap, if necessary, before transforming
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixBilinearPtaColor()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] ptad 4 pts of final coordinate space
+ * \param[in] ptas 4 pts of initial coordinate space
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixBilinearColor()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] vc vector of 8 coefficients for bilinear transformation
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixBilinearPtaGray()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] ptad 4 pts of final coordinate space
+ * \param[in] ptas 4 pts of initial coordinate space
+ * \param[in] grayval e.g., 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;
+}
+
+
+/*!
+ * \brief pixBilinearGray()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] vc vector of 8 coefficients for bilinear transformation
+ * \param[in] grayval e.g., 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 *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief pixBilinearPtaWithAlpha()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] ptad 4 pts of final coordinate space
+ * \param[in] ptas 4 pts of initial coordinate space
+ * \param[in] pixg [optional] 8 bpp, can be null
+ * \param[in] fract between 0.0 and 1.0, with 0.0 fully transparent
+ * and 1.0 fully opaque
+ * \param[in] border of pixels added to capture transformed source pixels
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief getBilinearXformCoeffs()
+ *
+ * \param[in] ptas source 4 points; unprimed
+ * \param[in] ptad transformed 4 points; primed
+ * \param[out] pvc vector of coefficients of transform
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+
+ b = (l_float32 *)LEPT_CALLOC(8, sizeof(l_float32));
+ *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++)
+ a[i] = (l_float32 *)LEPT_CALLOC(8, sizeof(l_float32));
+ 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;
+}
+
+
+/*!
+ * \brief bilinearXformSampledPt()
+ *
+ * \param[in] vc vector of 8 coefficients
+ * \param[in] x, y initial point
+ * \param[out] pxp, pyp transformed point
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds the nearest pixel coordinates of the transformed point.
+ * (2) It does not check ptrs for returned data!
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief bilinearXformPt()
+ *
+ * \param[in] vc vector of 8 coefficients
+ * \param[in] x, y initial point
+ * \param[out] pxp, pyp transformed point
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This computes the floating point location of the transformed point.
+ * (2) It does not check ptrs for returned data!
+ * </pre>
+ */
+l_ok
+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/leptonica/src/binarize.c b/leptonica/src/binarize.c
new file mode 100644
index 00000000..09d50c98
--- /dev/null
+++ b/leptonica/src/binarize.c
@@ -0,0 +1,1101 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file binarize.c
+ * <pre>
+ *
+ * ===================================================================
+ * 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()
+ * static PIX *pixSauvolaGetThreshold()
+ * static PIX *pixApplyLocalThreshold();
+ *
+ * Global thresholding using connected components
+ * PIX *pixThresholdByConnComp()
+ *
+ * Global thresholding by histogram
+ * PIX *pixThresholdByHisto()
+ *
+ * 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 measurement at each pixel and a
+ * parameter that determines the amount of normalized local
+ * standard deviation to subtract from the local average value.
+ * (4) pixThresholdByConnComp() 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+static PIX *pixSauvolaGetThreshold(PIX *pixm, PIX *pixms, l_float32 factor,
+ PIX **ppixsd);
+static PIX *pixApplyLocalThreshold(PIX *pixs, PIX *pixth);
+
+/*------------------------------------------------------------------*
+ * Adaptive Otsu-based thresholding *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixOtsuAdaptiveThreshold()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] sx, sy desired tile dimensions; actual size may vary
+ * \param[in] smoothx, smoothy half-width of convolution kernel applied to
+ * threshold array: use 0 for no smoothing
+ * \param[in] scorefract fraction of the max Otsu score; typ. 0.1;
+ * use 0.0 for standard Otsu
+ * \param[out] ppixth [optional] array of threshold values
+ * found for each tile
+ * \param[out] ppixd [optional] thresholded input pixs,
+ * based on the threshold array
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, NULL);
+ 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixOtsuThreshOnBackgroundNorm()
+ *
+ * \param[in] pixs 8 bpp grayscale; not colormapped
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
+ * \param[in] sx, sy tile size in pixels
+ * \param[in] thresh threshold for determining foreground
+ * \param[in] mincount min threshold on counts in a tile
+ * \param[in] bgval target bg val; typ. > 128
+ * \param[in] smoothx half-width of block convolution kernel width
+ * \param[in] smoothy half-width of block convolution kernel height
+ * \param[in] scorefract fraction of the max Otsu score; typ. 0.1
+ * \param[out] pthresh [optional] threshold value that was
+ * used on the normalized image
+ * \return pixd 1 bpp thresholded image, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixMaskedThreshOnBackgroundNorm()
+ *
+ * \param[in] pixs 8 bpp grayscale; not colormapped
+ * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
+ * \param[in] sx, sy tile size in pixels
+ * \param[in] thresh threshold for determining foreground
+ * \param[in] mincount min threshold on counts in a tile
+ * \param[in] smoothx half-width of block convolution kernel width
+ * \param[in] smoothy half-width of block convolution kernel height
+ * \param[in] scorefract fraction of the max Otsu score; typ. ~ 0.1
+ * \param[out] pthresh [optional] threshold value that was
+ * used on the normalized image
+ * \return pixd 1 bpp thresholded image, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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, highthresh;
+l_uint32 val;
+PIX *pixn, *pixm, *pixd, *pix1, *pix2, *pix3, *pix4;
+
+ 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. */
+ pix1 = pixBackgroundNormFlex(pixs, 7, 7, 1, 1, 20);
+ pix2 = pixThresholdToBinary(pix1, 240);
+ pixInvert(pix2, pix2);
+ pixm = pixMorphSequence(pix2, "d21.21", 0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Use Otsu to get a global threshold estimate for the image,
+ * which is stored as a single pixel in pix3. */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixOtsuAdaptiveThreshold(pixs, w, h, 0, 0, scorefract, &pix3, NULL);
+ pixGetPixel(pix3, 0, 0, &val);
+ if (pthresh) *pthresh = val;
+ pixDestroy(&pix3);
+
+ /* 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. */
+ highthresh = L_MIN(256, val + 30);
+ pixd = pixThresholdToBinary(pixn, highthresh); /* for bg and light fg */
+ pix4 = pixThresholdToBinary(pixn, 190); /* for heavier fg */
+ pixCombineMasked(pixd, pix4, pixm);
+ pixDestroy(&pix4);
+ pixDestroy(&pixm);
+ pixDestroy(&pixn);
+
+ if (!pixd)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ else
+ return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Sauvola binarization *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixSauvolaBinarizeTiled()
+ *
+ * \param[in] pixs 8 bpp grayscale, not colormapped
+ * \param[in] whsize window half-width for measuring local statistics
+ * \param[in] factor factor for reducing threshold due to variance; >= 0
+ * \param[in] nx, ny subdivision into tiles; >= 1
+ * \param[out] ppixth [optional] Sauvola threshold values
+ * \param[out] ppixd [optional] thresholded image
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSauvolaBinarize()
+ *
+ * \param[in] pixs 8 bpp grayscale; not colormapped
+ * \param[in] whsize window half-width for measuring local statistics
+ * \param[in] factor factor for reducing threshold due to variance; >= 0
+ * \param[in] addborder 1 to add border of width (%whsize + 1) on all sides
+ * \param[out] ppixm [optional] local mean values
+ * \param[out] ppixsd [optional] local standard deviation values
+ * \param[out] ppixth [optional] threshold values
+ * \param[out] ppixd [optional] thresholded image
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+ pixCopyResolution(pixd, pixs);
+ }
+
+ if (ppixm)
+ *ppixm = pixm;
+ else
+ pixDestroy(&pixm);
+ pixDestroy(&pixms);
+ if (ppixth)
+ *ppixth = pixth;
+ else
+ pixDestroy(&pixth);
+ if (ppixd)
+ *ppixd = pixd;
+ pixDestroy(&pixg);
+ pixDestroy(&pixsc);
+ return 0;
+}
+
+
+/*!
+ * \brief pixSauvolaGetThreshold()
+ *
+ * \param[in] pixm 8 bpp grayscale; not colormapped
+ * \param[in] pixms 32 bpp
+ * \param[in] factor factor for reducing threshold due to variance; >= 0
+ * \param[out] ppixsd [optional] local standard deviation
+ * \return pixd 8 bpp, sauvola threshold values, or NULL on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+static 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;
+}
+
+
+/*!
+ * \brief pixApplyLocalThreshold()
+ *
+ * \param[in] pixs 8 bpp grayscale; not colormapped
+ * \param[in] pixth 8 bpp array of local thresholds
+ * \return pixd 1 bpp, thresholded image, or NULL on error
+ */
+static PIX *
+pixApplyLocalThreshold(PIX *pixs,
+ PIX *pixth)
+{
+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;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Global thresholding using connected components *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixThresholdByConnComp()
+ *
+ * \param[in] pixs depth > 1, colormap OK
+ * \param[in] pixm [optional] 1 bpp mask giving region to ignore
+ * by setting pixels to white; use NULL if no mask
+ * \param[in] start, end, incr binarization threshold levels to test
+ * \param[in] thresh48 threshold on normalized difference between the
+ * numbers of 4 and 8 connected components
+ * \param[in] threshdiff threshold on normalized difference between the
+ * number of 4 cc at successive iterations
+ * \param[out] pglobthresh [optional] best global threshold; 0
+ * if no threshold is found
+ * \param[out] ppixd [optional] image thresholded to binary, or
+ * null if no threshold is found
+ * \param[in] debugflag 1 for plotted results
+ * \return 0 if OK, 1 on error or if no threshold is found
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) {
+ lept_mkdir("lept/binarize");
+ gplot = gplotCreate("/tmp/lept/binarize", 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) {
+ lept_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) lept_stderr("global threshold = %d\n", globthresh);
+ pixDestroy(&pix2);
+ return 0;
+ }
+
+ if (debugflag) lept_stderr("no global threshold found\n");
+ pixDestroy(&pix2);
+ return 1;
+}
+
+/*----------------------------------------------------------------------*
+ * Global thresholding by histogram *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixThresholdByHisto()
+ *
+ * \param[in] pixs gray 8 bpp, no colormap
+ * \param[in] factor subsampling factor >= 1
+ * \param[in] halfw half of window width for smoothing;
+ * use 0 for default
+ * \param[in] delta relative amount to resolve peaks and valleys;
+ * in (0 ... 1], use 0 for default
+ * \param[out] pthresh best global threshold; 0 if no threshold is found
+ * \param[out] ppixd [optional] thresholded 1 bpp pix
+ * \param[out] ppixhisto [optional] rescaled histogram of gray values
+ * \return 0 if OK, 1 on error or if no threshold is found
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds a global threshold. It is best for an image that
+ * has a fairly well-defined fg and bg.
+ * (2) If it finds a good threshold and %ppixd is defined, the binarized
+ * image is returned in &pixd; otherwise it return null.
+ * (3) Suggest using default values for %half and %delta.
+ * (4) Returns 0 in %pthresh if it can't find a good threshold.
+ * </pre>
+ */
+l_ok
+pixThresholdByHisto(PIX *pixs,
+ l_int32 factor,
+ l_int32 halfw,
+ l_float32 delta,
+ l_int32 *pthresh,
+ PIX **ppixd,
+ PIX **ppixhisto)
+{
+l_float32 maxval, fract;
+NUMA *na1, *na2, *na3;
+
+ PROCNAME("pixThresholdByHisto");
+
+ if (ppixhisto) *ppixhisto = NULL;
+ if (ppixd) *ppixd = NULL;
+ if (!pthresh)
+ return ERROR_INT("&thresh not defined", procName, 1);
+ *pthresh = 0;
+ if (!pixs || pixGetDepth(pixs) != 8)
+ return ERROR_INT("pixs undefined or not 8 bpp", procName, 1);
+ if (pixGetColormap(pixs))
+ return ERROR_INT("pixs has colormap", procName, 1);
+ if (factor < 1)
+ return ERROR_INT("sampling must be >= 1", procName, 1);
+ if (halfw <= 0) halfw = 20;
+ if (delta <= 0.0) delta = 0.1;
+
+ /* Make a histogram of pixel values where the largest peak
+ * is normalized to a value of 1.0. */
+ na1 = pixGetGrayHistogram(pixs, factor);
+ na2 = numaWindowedMean(na1, halfw); /* smoothing */
+ numaGetMax(na2, &maxval, NULL);
+ na3 = numaTransform(na2, 0.0, 1.0 / maxval); /* rescale to max of 1.0 */
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+
+ numaFindLocForThreshold(na3, 0, pthresh, &fract);
+ L_INFO("fractional area under first peak: %5.3f\n", procName, fract);
+
+ if (ppixhisto) {
+ lept_mkdir("lept/histo");
+ gplotSimple1(na3, GPLOT_PNG, "/tmp/lept/histo/histo", NULL);
+ *ppixhisto = pixRead("/tmp/lept/histo/histo.png");
+ }
+ numaDestroy(&na3);
+
+ if (*pthresh > 0 && ppixd)
+ *ppixd = pixThresholdToBinary(pixs, *pthresh);
+ return 0;
+}
+
diff --git a/leptonica/src/binexpand.c b/leptonica/src/binexpand.c
new file mode 100644
index 00000000..3c67297a
--- /dev/null
+++ b/leptonica/src/binexpand.c
@@ -0,0 +1,304 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file binexpand.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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) *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixExpandBinaryReplicate()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] xfact integer scale factor for horiz. replicative expansion
+ * \param[in] yfact integer scale factor for vertical replicative expansion
+ * \return pixd scaled up, or NULL on error
+ */
+PIX *
+pixExpandBinaryReplicate(PIX *pixs,
+ l_int32 xfact,
+ l_int32 yfact)
+{
+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 (xfact <= 0 || yfact <= 0)
+ return (PIX *)ERROR_PTR("invalid scale factor: <= 0", procName, NULL);
+
+ if (xfact == yfact) {
+ if (xfact == 1)
+ return pixCopy(NULL, pixs);
+ if (xfact == 2 || xfact == 4 || xfact == 8 || xfact == 16)
+ return pixExpandBinaryPower2(pixs, xfact);
+ }
+
+ wpls = pixGetWpl(pixs);
+ datas = pixGetData(pixs);
+ wd = xfact * w;
+ hd = yfact * h;
+ if ((pixd = pixCreate(wd, hd, 1)) == NULL)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ pixCopyResolution(pixd, pixs);
+ pixScaleResolution(pixd, (l_float32)xfact, (l_float32)yfact);
+ wpld = pixGetWpl(pixd);
+ datad = pixGetData(pixd);
+
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ lined = datad + yfact * i * wpld;
+ for (j = 0; j < w; j++) { /* replicate pixels on a single line */
+ if (GET_DATA_BIT(lines, j)) {
+ start = xfact * j;
+ for (k = 0; k < xfact; k++)
+ SET_DATA_BIT(lined, start + k);
+ }
+ }
+ for (k = 1; k < yfact; k++) /* replicate the line */
+ memcpy(lined + k * wpld, lined, 4 * wpld);
+ }
+
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Power of 2 expansion *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixExpandBinaryPower2()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] 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) {
+ tab2 = makeExpandTab2x();
+ 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(lined + wpld, lined, 4 * wpld);
+ }
+ LEPT_FREE(tab2);
+ } else if (factor == 4) {
+ tab4 = makeExpandTab4x();
+ 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(lined + k * wpld, lined, 4 * wpld);
+ }
+ LEPT_FREE(tab4);
+ } else if (factor == 8) {
+ tab8 = makeExpandTab8x();
+ 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);
+ lined[j] = tab8[sval];
+ }
+ for (k = 1; k < 8; k++)
+ memcpy(lined + k * wpld, 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(lined + k * wpld, lined, 4 * wpld);
+ }
+ }
+
+ return pixd;
+}
+
+
+/*-------------------------------------------------------------------*
+ * Expansion tables for 2x, 4x and 8x expansion *
+ *-------------------------------------------------------------------*/
+static l_uint16 *
+makeExpandTab2x(void)
+{
+l_uint16 *tab;
+l_int32 i;
+
+ tab = (l_uint16 *) LEPT_CALLOC(256, sizeof(l_uint16));
+ 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;
+
+ tab = (l_uint32 *) LEPT_CALLOC(256, sizeof(l_uint32));
+ 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;
+
+ tab = (l_uint32 *) LEPT_CALLOC(16, sizeof(l_uint32));
+ 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/leptonica/src/binreduce.c b/leptonica/src/binreduce.c
new file mode 100644
index 00000000..7b690808
--- /dev/null
+++ b/leptonica/src/binreduce.c
@@ -0,0 +1,412 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file binreduce.c
+ * <pre>
+ *
+ * Subsampled 2x reduction
+ * PIX *pixReduceBinary2()
+ *
+ * Rank filtered 2x reductions
+ * PIX *pixReduceRankBinaryCascade()
+ * PIX *pixReduceRankBinary2()
+ *
+ * Permutation table for 2x rank binary reduction
+ * l_uint8 *makeSubsampleTab2x(void)
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*------------------------------------------------------------------*
+ * Subsampled reduction *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixReduceBinary2()
+ *
+ * \param[in] pixs
+ * \param[in] intab [optional]; if null, a table is made here
+ * and destroyed before exit
+ * \return pixd 2x subsampled, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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 || pixGetDepth(pixs) != 1)
+ return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+
+ pixGetDimensions(pixs, &ws, &hs, NULL);
+ if (hs <= 1)
+ return (PIX *)ERROR_PTR("hs must be at least 2", procName, NULL);
+ wpls = pixGetWpl(pixs);
+ datas = pixGetData(pixs);
+ pixSetPadBits(pixs, 0);
+
+ 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);
+
+ tab = (intab) ? intab : makeSubsampleTab2x();
+ if (!tab) {
+ pixDestroy(&pixd);
+ return (PIX *)ERROR_PTR("tab not made", procName, NULL);
+ }
+
+ /* 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) LEPT_FREE(tab);
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Rank filtered binary reductions *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixReduceRankBinaryCascade()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] level1 threshold, in the set {0, 1, 2, 3, 4}
+ * \param[in] level2 threshold, in the set {0, 1, 2, 3, 4}
+ * \param[in] level3 threshold, in the set {0, 1, 2, 3, 4}
+ * \param[in] level4 threshold, in the set {0, 1, 2, 3, 4}
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This performs up to four cascaded 2x rank reductions.
+ * (2) Use level = 0 to truncate the cascade.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixReduceRankBinary2()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] level rank threshold: 1, 2, 3, 4
+ * \param[in] 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
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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);
+
+ pixGetDimensions(pixs, &ws, &hs, NULL);
+ if (hs <= 1)
+ return (PIX *)ERROR_PTR("hs must be at least 2", procName, NULL);
+ wpls = pixGetWpl(pixs);
+ datas = pixGetData(pixs);
+ pixSetPadBits(pixs, 0);
+
+ 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);
+
+ tab = (intab) ? intab : makeSubsampleTab2x();
+ if (!tab) {
+ pixDestroy(&pixd);
+ return (PIX *)ERROR_PTR("tab not made", procName, NULL);
+ }
+
+ /* 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;
+}
+
+
+/*!
+ * \brief makeSubsampleTab2x()
+ *
+ * \return tab table of 256 permutations, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * Permutation table for 2x rank binary reduction
+ * 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
+ * </pre>
+ */
+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/leptonica/src/blend.c b/leptonica/src/blend.c
new file mode 100644
index 00000000..1cb79c61
--- /dev/null
+++ b/leptonica/src/blend.c
@@ -0,0 +1,2295 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file blend.c
+ * <pre>
+ *
+ * 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()
+ *
+ * Fading from the edge
+ * l_int32 pixLinearEdgeFade()
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixBlend()
+ *
+ * \param[in] pixs1 blendee
+ * \param[in] pixs2 blender; typ. smaller
+ * \param[in] x,y origin [UL corner] of pixs2 relative to
+ * the origin of pixs1; can be < 0
+ * \param[in] fract blending fraction
+ * \return pixd blended image, or null on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a simple top-level interface. For more flexibility,
+ * call directly into pixBlendMask(), etc.
+ * </pre>
+ */
+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);
+ pixDestroy(&pixt);
+ 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;
+}
+
+
+/*!
+ * \brief pixBlendMask()
+ *
+ * \param[in] pixd [optional]; either NULL or equal to pixs1 for in-place
+ * \param[in] pixs1 blendee, depth > 1
+ * \param[in] pixs2 blender, 1 bpp; typ. smaller in size than pixs1
+ * \param[in] x,y origin [UL corner] of pixs2 relative to
+ * the origin of pixs1; can be < 0
+ * \param[in] fract blending fraction
+ * \param[in] type L_BLEND_WITH_INVERSE, L_BLEND_TO_WHITE,
+ * L_BLEND_TO_BLACK
+ * \return pixd if OK; null on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixBlendGray()
+ *
+ * \param[in] pixd [optional] either equal to pixs1 for in-place,
+ * or NULL
+ * \param[in] pixs1 blendee, depth > 1
+ * \param[in] pixs2 blender, any depth; typically, the area of
+ * pixs2 is smaller than pixs1
+ * \param[in] x,y origin [UL corner] of pixs2 relative to
+ * the origin of pixs1; can be < 0
+ * \param[in] fract blending fraction
+ * \param[in] type L_BLEND_GRAY, L_BLEND_GRAY_WITH_INVERSE
+ * \param[in] transparent 1 to use transparency; 0 otherwise
+ * \param[in] transpix pixel grayval in pixs2 that is to be transparent
+ * \return pixd if OK; pixs1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 || 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 || 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 || 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 || 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;
+}
+
+
+/*!
+ * \brief pixBlendGrayInverse()
+ *
+ * \param[in] pixd [optional] either equal to pixs1 for in-place, or NULL
+ * \param[in] pixd [optional] either NULL or equal to pixs1 for in-place
+ * \param[in] pixs1 blendee, depth > 1
+ * \param[in] pixs2 blender, any depth; typ. smaller in size than pixs1
+ * \param[in] x,y origin [UL corner] of pixs2 relative to
+ * the origin of pixs1; can be < 0
+ * \param[in] fract blending fraction
+ * \return pixd if OK; pixs1 on error
+ *
+ * <pre>
+ * 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]
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixBlendColor()
+ *
+ * \param[in] pixd [optional] either equal to pixs1 for in-place,
+ * or NULL
+ * \param[in] pixs1 blendee; depth > 1
+ * \param[in] pixs2 blender, any depth; typically, the area of
+ * pixs2 is smaller than pixs1
+ * \param[in] x,y origin [UL corner] of pixs2 relative to
+ * the origin of pixs1
+ * \param[in] fract blending fraction
+ * \param[in] transparent 1 to use transparency; 0 otherwise
+ * \param[in] transpix pixel color in pixs2 that is to be transparent
+ * \return pixd, or null on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 ||
+ ((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;
+}
+
+
+/*
+ * \brief pixBlendColorByChannel()
+ *
+ * \param[in] pixd [optional] either equal to pixs1 for in-place,
+ * or NULL
+ * \param[in] pixs1 blendee; depth > 1
+ * \param[in] pixs2 blender, any depth; typically, the area of
+ * pixs2 is smaller than pixs1
+ * \param[in] x,y origin [UL corner] of pixs2 relative to
+ * the origin of pixs1
+ * \param[in] rfract blending fraction in red channel
+ * \param[in] gfract blending fraction in green channel
+ * \param[in] bfract blending fraction in blue channel
+ * \param[in] transparent 1 to use transparency; 0 otherwise
+ * \param[in] transpix pixel color in pixs2 that is to be transparent
+ * \return pixd if OK; pixd on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 ||
+ ((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);
+}
+
+
+/*!
+ * \brief pixBlendGrayAdapt()
+ *
+ * \param[in] pixd [optional] either equal to pixs1 for in-place, or NULL
+ * \param[in] pixs1 blendee; depth > 1
+ * \param[in] pixs2 blender, any depth; typically, the area of
+ * pixs2 is smaller than pixs1
+ * \param[in] x,y origin [UL corner] of pixs2 relative to
+ * the origin of pixs1; can be < 0
+ * \param[in] fract blending fraction
+ * \param[in] shift >= 0 but <= 128: shift of zero blend value from
+ * median source; use -1 for default value;
+ * \return pixd if OK; pixs1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFadeWithGray()
+ *
+ * \param[in] pixs colormapped or 8 bpp or 32 bpp
+ * \param[in] pixb 8 bpp blender
+ * \param[in] factor multiplicative factor to apply to blender value
+ * \param[in] type L_BLEND_TO_WHITE, L_BLEND_TO_BLACK
+ * \return pixd, or null on error
+ *
+ * <pre>
+ * 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 applied to pixs. Fade either to white (L_BLEND_TO_WHITE)
+ * or to black (L_BLEND_TO_BLACK).
+ * </pre>
+ */
+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;
+}
+
+
+/*
+ * \brief pixBlendHardLight()
+ *
+ * \param[in] pixd either NULL or equal to pixs1 for in-place
+ * \param[in] pixs1 blendee; depth > 1, may be cmapped
+ * \param[in] pixs2 blender, 8 or 32 bpp; may be colormapped;
+ * typ. smaller in size than pixs1
+ * \param[in] x,y origin [UL corner] of pixs2 relative to
+ * the origin of pixs1
+ * \param[in] fract blending fraction, or 'opacity factor'
+ * \return pixd if OK; pixs1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*
+ * \brief blendHardLightComponents()
+ *
+ * \param[in] a 8 bpp blendee component
+ * \param[in] b 8 bpp blender component
+ * \param[in] fract fraction of blending; use 1.0 for usual definition
+ * \return blended 8 bpp component
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixBlendCmap()
+ *
+ * \param[in] pixs 2, 4 or 8 bpp, with colormap
+ * \param[in] pixb colormapped blender
+ * \param[in] x, y UL corner of blender relative to pixs
+ * \param[in] sindex colormap index of pixels in pixs to be changed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (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.
+ * </pre>
+ */
+l_ok
+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);
+
+ pixGetDimensions(pixs, &w, &h, &d);
+ if (d != 2 && d != 4 && d != 8)
+ return ERROR_INT("depth not in {2,4,8}", procName, 1);
+
+ /* 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);
+
+ /* 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixBlendWithGrayMask()
+ *
+ * \param[in] pixs1 8 bpp gray, rgb, rgba or colormapped
+ * \param[in] pixs2 8 bpp gray, rgb, rgba or colormapped
+ * \param[in] pixg [optional] 8 bpp gray, for transparency of pixs2;
+ * can be null
+ * \param[in] x, y UL corner of pixs2 and pixg with respect to pixs1
+ * \return pixd blended image, or null on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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: both either 8 or 32 bpp */
+ d1 = pixGetDepth(pix1);
+ d2 = pixGetDepth(pix2);
+ if (d1 != d2 || (d1 != 8 && d1 != 32)) {
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixg2);
+ 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.;
+ if (d1 == 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);
+ } else { /* 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;
+ }
+ }
+ }
+
+ pixDestroy(&pixg2);
+ pixDestroy(&pix2);
+ return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Blending background to a specific color *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixBlendBackgroundToColor()
+ *
+ * \param[in] pixd can be NULL or pixs
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] box region for blending; can be NULL)
+ * \param[in] color 32 bit color in 0xrrggbb00 format
+ * \param[in] gamma, minval, maxval args for grayscale TRC mapping
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixMultiplyByColor()
+ *
+ * \param[in] pixd can be NULL or pixs
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] box region for filtering; can be NULL)
+ * \param[in] color 32 bit color in 0xrrggbb00 format
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixAlphaBlendUniform()
+ *
+ * \param[in] pixs 32 bpp rgba, with alpha
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixAddAlphaToBlend()
+ *
+ * \param[in] pixs any depth
+ * \param[in] fract fade fraction in the alpha component
+ * \param[in] invert 1 to photometrically invert pixs
+ * \return pixd 32 bpp with alpha, or null on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixSetAlphaOverWhite()
+ *
+ * \param[in] pixs colormapped or 32 bpp rgb; no alpha
+ * \return pixd new pix with meaningful alpha component,
+ * or null on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Fading from the edge *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixLinearEdgeFade()
+ *
+ * \param[in] pixs 8 or 32 bpp; no colormap
+ * \param[in] dir L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT
+ * \param[in] fadeto L_BLEND_TO_WHITE, L_BLEND_TO_BLACK
+ * \param[in] distfract fraction of width or height over which fading occurs
+ * \param[in] maxfade fraction of fading at the edge, <= 1.0
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) In-place operation.
+ * (2) Maximum fading fraction %maxfade occurs at the edge of the image,
+ * and the fraction goes to 0 at the fractional distance %distfract
+ * from the edge. %maxfade must be in [0, 1].
+ * (3) %distrfact must be in [0, 1], and typically it would be <= 0.5.
+ * </pre>
+ */
+l_ok
+pixLinearEdgeFade(PIX *pixs,
+ l_int32 dir,
+ l_int32 fadeto,
+ l_float32 distfract,
+ l_float32 maxfade)
+{
+l_int32 i, j, w, h, d, wpl, xmin, ymin, range, val, rval, gval, bval;
+l_float32 slope, limit, del;
+l_uint32 *data, *line;
+
+ PROCNAME("pixLinearEdgeFade");
+
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+ if (pixGetColormap(pixs) != NULL)
+ return ERROR_INT("pixs has a colormap", procName, 1);
+ pixGetDimensions(pixs, &w, &h, &d);
+ if (d != 8 && d != 32)
+ return ERROR_INT("pixs not 8 or 32 bpp", procName, 1);
+ if (dir != L_FROM_LEFT && dir != L_FROM_RIGHT &&
+ dir != L_FROM_TOP && dir != L_FROM_BOT)
+ return ERROR_INT("invalid fade direction from edge", procName, 1);
+ if (fadeto != L_BLEND_TO_WHITE && fadeto != L_BLEND_TO_BLACK)
+ return ERROR_INT("invalid fadeto photometry", procName, 1);
+ if (maxfade <= 0) return 0;
+ if (maxfade > 1.0)
+ return ERROR_INT("invalid maxfade", procName, 1);
+ if (distfract <= 0 || distfract * L_MIN(w, h) < 1.0) {
+ L_INFO("distfract is too small\n", procName);
+ return 0;
+ }
+ if (distfract > 1.0)
+ return ERROR_INT("invalid distfract", procName, 1);
+
+ /* Set up parameters */
+ if (dir == L_FROM_LEFT) {
+ range = (l_int32)(distfract * w);
+ xmin = 0;
+ slope = maxfade / (l_float32)range;
+ } else if (dir == L_FROM_RIGHT) {
+ range = (l_int32)(distfract * w);
+ xmin = w - range;
+ slope = maxfade / (l_float32)range;
+ } else if (dir == L_FROM_TOP) {
+ range = (l_int32)(distfract * h);
+ ymin = 0;
+ slope = maxfade / (l_float32)range;
+ } else if (dir == L_FROM_BOT) {
+ range = (l_int32)(distfract * h);
+ ymin = h - range;
+ slope = maxfade / (l_float32)range;
+ }
+
+ limit = (fadeto == L_BLEND_TO_WHITE) ? 255.0 : 0.0;
+ data = pixGetData(pixs);
+ wpl = pixGetWpl(pixs);
+ if (dir == L_FROM_LEFT || dir == L_FROM_RIGHT) {
+ for (j = 0; j < range; j++) {
+ del = (dir == L_FROM_LEFT) ? maxfade - slope * j
+ : maxfade - slope * (range - j);
+ for (i = 0; i < h; i++) {
+ line = data + i * wpl;
+ if (d == 8) {
+ val = GET_DATA_BYTE(line, xmin + j);
+ val += (limit - val) * del + 0.5;
+ SET_DATA_BYTE(line, xmin + j, val);
+ } else { /* rgb */
+ extractRGBValues(*(line + xmin + j), &rval, &gval, &bval);
+ rval += (limit - rval) * del + 0.5;
+ gval += (limit - gval) * del + 0.5;
+ bval += (limit - bval) * del + 0.5;
+ composeRGBPixel(rval, gval, bval, line + xmin + j);
+ }
+ }
+ }
+ } else { /* dir == L_FROM_TOP || L_FROM_BOT */
+ for (i = 0; i < range; i++) {
+ del = (dir == L_FROM_TOP) ? maxfade - slope * i
+ : maxfade - slope * (range - i);
+ line = data + (ymin + i) * wpl;
+ for (j = 0; j < w; j++) {
+ if (d == 8) {
+ val = GET_DATA_BYTE(line, j);
+ val += (limit - val) * del + 0.5;
+ SET_DATA_BYTE(line, j, val);
+ } else { /* rgb */
+ extractRGBValues(*(line + j), &rval, &gval, &bval);
+ rval += (limit - rval) * del + 0.5;
+ gval += (limit - gval) * del + 0.5;
+ bval += (limit - bval) * del + 0.5;
+ composeRGBPixel(rval, gval, bval, line + j);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/leptonica/src/bmf.c b/leptonica/src/bmf.c
new file mode 100644
index 00000000..c597e480
--- /dev/null
+++ b/leptonica/src/bmf.c
@@ -0,0 +1,876 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file bmf.c
+ * <pre>
+ *
+ * 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()
+ * static PIXA *pixaGenerateFontFromFile()
+ * static PIXA *pixaGenerateFontFromString()
+ * static 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 '!'.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+#include "bmfdata.h"
+
+static const l_float32 VertFractSep = 0.3f;
+
+#ifndef NO_CONSOLE_IO
+#define DEBUG_BASELINE 0
+#define DEBUG_CHARS 0
+#define DEBUG_FONT_GEN 0
+#endif /* ~NO_CONSOLE_IO */
+
+static PIXA *pixaGenerateFontFromFile(const char *dir, l_int32 fontsize,
+ l_int32 *pbl0, l_int32 *pbl1,
+ l_int32 *pbl2);
+static PIXA *pixaGenerateFontFromString(l_int32 fontsize, l_int32 *pbl0,
+ l_int32 *pbl1, l_int32 *pbl2);
+static PIXA *pixaGenerateFont(PIX *pixs, l_int32 fontsize, l_int32 *pbl0,
+ l_int32 *pbl1, l_int32 *pbl2);
+static l_int32 pixGetTextBaseline(PIX *pixs, l_int32 *tab8, l_int32 *py);
+static l_int32 bmfMakeAsciiTables(L_BMF *bmf);
+
+/*---------------------------------------------------------------------*/
+/* Bmf create/destroy */
+/*---------------------------------------------------------------------*/
+/*!
+ * \brief bmfCreate()
+ *
+ * \param[in] dir [optional] directory holding pixa of character set
+ * \param[in] fontsize 4, 6, 8, ... , 20
+ * \return bmf holding the bitmap font and associated information
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ bmf = (L_BMF *)LEPT_CALLOC(1, sizeof(L_BMF));
+
+ if (!dir) { /* Generate from a string */
+ pixa = pixaGenerateFontFromString(fontsize, &bmf->baseline1,
+ &bmf->baseline2, &bmf->baseline3);
+ } else { /* Look for the pixa in a directory */
+ 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;
+}
+
+
+/*!
+ * \brief bmfDestroy()
+ *
+ * \param[in,out] pbmf will be set to null before returning
+ * \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;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Bmf accessors */
+/*---------------------------------------------------------------------*/
+/*!
+ * \brief bmfGetPix()
+ *
+ * \param[in] bmf
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief bmfGetWidth()
+ *
+ * \param[in] bmf
+ * \param[in] chr should be one of the 95 supported bitmaps
+ * \param[out] pw character width; -1 if not printable
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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);
+}
+
+
+/*!
+ * \brief bmfGetBaseline()
+ *
+ * \param[in] bmf
+ * \param[in] chr should be one of the 95 supported bitmaps
+ * \param[out] pbaseline distance below UL corner of bitmap char
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 */
+/*---------------------------------------------------------------------*/
+/*!
+ * \brief pixaGetFont()
+ *
+ * \param[in] dir directory holding pixa of character set
+ * \param[in] fontsize 4, 6, 8, ... , 20
+ * \param[out] pbl0 baseline of row 1
+ * \param[out] pbl1 baseline of row 2
+ * \param[out] pbl2 baseline of row 3
+ * \return pixa of font bitmaps for 95 characters, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This reads a pre-computed pixa file with the 95 ascii chars.
+ * </pre>
+ */
+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 = pathJoin(dir, outputfonts[fileno]);
+ pixa = pixaRead(pathname);
+ LEPT_FREE(pathname);
+
+ if (!pixa)
+ L_WARNING("pixa of char bitmaps not found\n", procName);
+ return pixa;
+}
+
+
+/*!
+ * \brief pixaSaveFont()
+ *
+ * \param[in] indir [optional] directory holding image of character set
+ * \param[in] outdir directory into which the output pixa file
+ * will be written
+ * \param[in] fontsize in pts, at 300 ppi
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This saves a font of a particular size.
+ * (2) If %indir == 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.
+ * </pre>
+ */
+l_ok
+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 */
+ pixa = pixaGenerateFontFromString(fontsize, &bl1, &bl2, &bl3);
+ else /* Generate from an image file */
+ pixa = pixaGenerateFontFromFile(indir, fontsize, &bl1, &bl2, &bl3);
+ if (!pixa)
+ return ERROR_INT("pixa not made", procName, 1);
+
+ pathname = pathJoin(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;
+}
+
+
+/*!
+ * \brief pixaGenerateFontFromFile()
+ *
+ * \param[in] dir directory holding image of character set
+ * \param[in] fontsize 4, 6, 8, ... , 20, in pts at 300 ppi
+ * \param[out] pbl0 baseline of row 1
+ * \param[out] pbl1 baseline of row 2
+ * \param[out] pbl2 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.
+ */
+static 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 = pathJoin(dir, inputfonts[fileno]);
+ pix = pixRead(pathname);
+ LEPT_FREE(pathname);
+ if (!pix) {
+ L_ERROR("pix not found for font size %d\n", procName, fontsize);
+ return NULL;
+ }
+
+ pixa = pixaGenerateFont(pix, fontsize, pbl0, pbl1, pbl2);
+ pixDestroy(&pix);
+ return pixa;
+}
+
+
+/*!
+ * \brief pixaGenerateFontFromString()
+ *
+ * \param[in] fontsize 4, 6, 8, ... , 20, in pts at 300 ppi
+ * \param[out] pbl0 baseline of row 1
+ * \param[out] pbl1 baseline of row 2
+ * \param[out] pbl2 baseline of row 3
+ * \return pixa of font bitmaps for 95 characters, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixaGenerateFontFromFile() for details.
+ * </pre>
+ */
+static 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;
+}
+
+
+/*!
+ * \brief pixaGenerateFont()
+ *
+ * \param[in] pixs of 95 characters in 3 rows
+ * \param[in] fontsize 4, 6, 8, ... , 20, in pts at 300 ppi
+ * \param[out] pbl0 baseline of row 1
+ * \param[out] pbl1 baseline of row 2
+ * \param[out] pbl2 baseline of row 3
+ * \return pixa of font bitmaps for 95 characters, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static 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);
+ boxaDestroy(&boxar);
+ 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 )
+ pixWriteDebug("/tmp/baseline/row0.png", pix1, IFF_PNG);
+ else if (i == 1)
+ pixWriteDebug("/tmp/baseline/row1.png", pix1, IFF_PNG);
+ else
+ pixWriteDebug("/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;
+}
+
+
+/*!
+ * \brief pixGetTextBaseline()
+ *
+ * \param[in] pixs 1 bpp, one textline character set
+ * \param[in] tab8 [optional] pixel sum table
+ * \param[out] py baseline value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief bmfMakeAsciiTables
+ *
+ * \param[in] bmf
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+ fonttab = (l_int32 *)LEPT_CALLOC(128, sizeof(l_int32));
+ bmf->fonttab = fonttab;
+ for (i = 0; i < 128; i++)
+ fonttab[i] = UNDEF;
+ for (i = 32; i < 127; i++)
+ fonttab[i] = i - 32;
+
+ baselinetab = (l_int32 *)LEPT_CALLOC(128, sizeof(l_int32));
+ 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 */
+ widthtab = (l_int32 *)LEPT_CALLOC(128, sizeof(l_int32));
+ 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)(VertFractSep * bmf->lineheight + 0.5);
+
+ return 0;
+}
diff --git a/leptonica/src/bmf.h b/leptonica/src/bmf.h
new file mode 100644
index 00000000..328e2c0d
--- /dev/null
+++ b/leptonica/src/bmf.h
@@ -0,0 +1,64 @@
+/*====================================================================*
+ - 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
+
+/*!
+ * \file bmf.h
+ *
+ * Simple data structure to hold bitmap fonts and related data
+ */
+
+ /*! Constants for deciding when text block is divided into paragraphs */
+/*! Split Text */
+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 */
+};
+
+
+/*! Data structure to hold bitmap fonts and related data */
+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/leptonica/src/bmfdata.h b/leptonica/src/bmfdata.h
new file mode 100644
index 00000000..30e2b5ad
--- /dev/null
+++ b/leptonica/src/bmfdata.h
@@ -0,0 +1,636 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file bmfdata.h
+ *
+ * <pre>
+ * 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 image 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);
+ * </pre>
+ */
+
+#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/leptonica/src/bmp.h b/leptonica/src/bmp.h
new file mode 100644
index 00000000..568c9901
--- /dev/null
+++ b/leptonica/src/bmp.h
@@ -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.
+ *====================================================================*/
+
+#ifndef LEPTONICA_BMP_H
+#define LEPTONICA_BMP_H
+
+/*!
+ * \file bmp.h
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+
+/*-------------------------------------------------------------*
+ * BMP file header *
+ *-------------------------------------------------------------*/
+
+/*! BMP file header
+ *
+ * Notes:
+ * (1) The bfSize field is stored as a 32 bit integer and includes
+ * the size of the BMP_FileHeader, BMP_InfoHeader, the color
+ * table (if any), and the size of the DIB bits.
+ * (2) The bfOffBits field is also stored as a 32 bit integer and
+ * contains the absolute offset in bytes of the image data
+ * in this file. Some bmp files have additional data after the
+ * BMP_InfoHeader and before the color table (if it exists).
+ * However, enabling reading of these files makes the reader
+ * vulnerable to various malware attacks. Therefore we do not
+ * read bmp files with extra data, and require that the size
+ * of the color table in bytes is
+ * offset - sizeof(BMP_FileHeader) - sizeof(BMP_InfoHeader)
+ * (3) Use arrays of l_uint8[] to make an endianness agnostic
+ * access to the BMP_FileHeader easier.
+ */
+struct BMP_FileHeader
+{
+ l_uint8 bfType[2]; /*!< file type; must be "BM" */
+ l_uint8 bfSize[4]; /*!< length of the file;
+ sizeof(BMP_FileHeader) +
+ sizeof(BMP_InfoHeader) +
+ size of optional extra data +
+ size of color table +
+ size of DIB bits */
+ l_uint8 bfReserved1[2]; /*!< don't care (set to 0) */
+ l_uint8 bfReserved2[2]; /*!< don't care (set to 0) */
+ l_uint8 bfOffBits[4]; /*!< offset from beginning of file */
+};
+typedef struct BMP_FileHeader BMP_FH;
+
+/*! Number of bytes in a BMP file header */
+#define BMP_FHBYTES sizeof(BMP_FH)
+
+
+/*-------------------------------------------------------------*
+ * BMP info header *
+ *-------------------------------------------------------------*/
+
+/*! 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; /*!< compress 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;
+
+/*! Number of bytes in a BMP info header */
+#define BMP_IHBYTES sizeof(BMP_IH)
+
+
+/*-------------------------------------------------------------*
+ * Align BMP headers on 4 byte boundaries *
+ *-------------------------------------------------------------*/
+
+/*! BMP_IH is misaligned, causing crashes on some big-endians.
+ * A packed struct forces alignment. */
+#if defined(__GNUC__)
+typedef struct __attribute__((__packed__)) {
+ BMP_FH bmpfh;
+ BMP_IH bmpih;
+} BMP_HEADER;
+#endif
+
+#endif /* LEPTONICA_BMP_H */
diff --git a/leptonica/src/bmpio.c b/leptonica/src/bmpio.c
new file mode 100644
index 00000000..d0c1e76e
--- /dev/null
+++ b/leptonica/src/bmpio.c
@@ -0,0 +1,646 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file bmpio.c
+ * <pre>
+ *
+ * Read bmp
+ * PIX *pixReadStreamBmp()
+ * PIX *pixReadMemBmp()
+ *
+ * Write bmp
+ * l_int32 pixWriteStreamBmp()
+ * l_int32 pixWriteMemBmp()
+ *
+ * </pre>
+ */
+
+#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} };
+
+ /* 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_PIXELS = 400000000LL;
+static const l_int32 L_MAX_ALLOWED_RES = 10000000; /* pixels/meter */
+
+#ifndef NO_CONSOLE_IO
+#define DEBUG 0
+#endif /* ~NO_CONSOLE_IO */
+
+/*--------------------------------------------------------------*
+ * Read bmp *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief pixReadStreamBmp()
+ *
+ * \param[in] fp file stream opened for read
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+PIX *
+pixReadStreamBmp(FILE *fp)
+{
+l_uint8 *data;
+size_t size;
+PIX *pix;
+
+ PROCNAME("pixReadStreamBmp");
+
+ 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 ((data = l_binaryReadStream(fp, &size)) == NULL)
+ return (PIX *)ERROR_PTR("data not read", procName, NULL);
+
+ pix = pixReadMemBmp(data, size);
+ LEPT_FREE(data);
+ return pix;
+}
+
+
+/*!
+ * \brief pixReadMemBmp()
+ *
+ * \param[in] cdata bmp data
+ * \param[in] size number of bytes of bmp-formatted data
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The BMP file is organized as follows:
+ * * 14 byte fileheader
+ * * Variable size infoheader: 40, 108 or 124 bytes.
+ * We only use data in he first 40 bytes.
+ * * Optional colormap, with size 4 * ncolors (in bytes)
+ * * Image data
+ * (2) 2 bpp bmp files are not valid in the original spec, but they
+ * are valid in later versions.
+ * </pre>
+ */
+PIX *
+pixReadMemBmp(const l_uint8 *cdata,
+ size_t size)
+{
+l_uint8 pel[4];
+l_uint8 *cmapBuf, *fdata, *data;
+l_int16 bftype, depth, d;
+l_int32 offset, ihbytes, width, height, height_neg, xres, yres;
+l_int32 compression, imagebytes, fdatabytes, cmapbytes, ncolors, maxcolors;
+l_int32 fdatabpl, extrabytes, pixWpl, pixBpl, i, j, k;
+l_uint32 *line, *pixdata, *pword;
+l_int64 npixels;
+BMP_FH *bmpfh;
+#if defined(__GNUC__)
+BMP_HEADER *bmph;
+#define bmpih (&bmph->bmpih)
+#else
+BMP_IH *bmpih;
+#endif
+PIX *pix, *pix1;
+PIXCMAP *cmap;
+
+ PROCNAME("pixReadMemBmp");
+
+ if (!cdata)
+ return (PIX *)ERROR_PTR("cdata not defined", procName, NULL);
+ if (size < sizeof(BMP_FH) + sizeof(BMP_IH))
+ return (PIX *)ERROR_PTR("bmf size error", procName, NULL);
+
+ /* Verify this is an uncompressed bmp */
+ bmpfh = (BMP_FH *)cdata;
+ bftype = bmpfh->bfType[0] + ((l_int32)bmpfh->bfType[1] << 8);
+ if (bftype != BMP_ID)
+ return (PIX *)ERROR_PTR("not bmf format", procName, NULL);
+#if defined(__GNUC__)
+ bmph = (BMP_HEADER *)bmpfh;
+#else
+ bmpih = (BMP_IH *)(cdata + BMP_FHBYTES);
+#endif
+ compression = convertOnBigEnd32(bmpih->biCompression);
+ if (compression != 0)
+ return (PIX *)ERROR_PTR("cannot read compressed BMP files",
+ procName, NULL);
+
+ /* Find the offset from the beginning of the file to the image data */
+ offset = bmpfh->bfOffBits[0];
+ offset += (l_int32)bmpfh->bfOffBits[1] << 8;
+ offset += (l_int32)bmpfh->bfOffBits[2] << 16;
+ offset += (l_uint32)bmpfh->bfOffBits[3] << 24;
+
+ /* Read the remaining useful data in the infoheader.
+ * Note that the first 4 bytes give the infoheader size. */
+ ihbytes = convertOnBigEnd32(*(l_uint32 *)(bmpih));
+ width = convertOnBigEnd32(bmpih->biWidth);
+ height = convertOnBigEnd32(bmpih->biHeight);
+ depth = convertOnBigEnd16(bmpih->biBitCount);
+ imagebytes = convertOnBigEnd32(bmpih->biSizeImage);
+ xres = convertOnBigEnd32(bmpih->biXPelsPerMeter);
+ yres = convertOnBigEnd32(bmpih->biYPelsPerMeter);
+
+ /* Some sanity checking. We impose limits on the image
+ * dimensions, resolution and number of pixels. We make sure the
+ * file is the correct size 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 == 0 || height < -L_MAX_ALLOWED_HEIGHT ||
+ height > L_MAX_ALLOWED_HEIGHT)
+ return (PIX *)ERROR_PTR("invalid height", procName, NULL);
+ if (xres < 0 || xres > L_MAX_ALLOWED_RES ||
+ yres < 0 || yres > L_MAX_ALLOWED_RES)
+ return (PIX *)ERROR_PTR("invalid resolution", procName, NULL);
+ height_neg = 0;
+ if (height < 0) {
+ height_neg = 1;
+ height = -height;
+ }
+ if (ihbytes != 40 && ihbytes != 108 && ihbytes != 124) {
+ L_ERROR("invalid ihbytes = %d; not in {40, 108, 124}\n",
+ procName, ihbytes);
+ return NULL;
+ }
+ npixels = 1LL * width * height;
+ if (npixels > L_MAX_ALLOWED_PIXELS)
+ return (PIX *)ERROR_PTR("npixels too large", procName, NULL);
+ if (depth != 1 && depth != 2 && depth != 4 && depth != 8 &&
+ depth != 16 && depth != 24 && depth != 32) {
+ L_ERROR("invalid depth = %d; not in {1, 2, 4, 8, 16, 24, 32}\n",
+ procName, depth);
+ return NULL;
+ }
+ fdatabpl = 4 * ((1LL * width * depth + 31)/32);
+ fdatabytes = fdatabpl * height;
+ if (imagebytes != 0 && imagebytes != fdatabytes) {
+ L_ERROR("invalid imagebytes = %d; not equal to fdatabytes = %d\n",
+ procName, imagebytes, fdatabytes);
+ return NULL;
+ }
+
+ /* In the original spec, BITMAPINFOHEADER is 40 bytes.
+ * There have been a number of revisions, to capture more information.
+ * For example, the fifth version, BITMAPV5HEADER, adds 84 bytes
+ * of ICC color profiles. We use the size of the infoheader
+ * to accommodate these newer formats. Knowing the size of the
+ * infoheader gives more opportunity to sanity check input params. */
+ cmapbytes = offset - BMP_FHBYTES - ihbytes;
+ ncolors = cmapbytes / sizeof(RGBA_QUAD);
+ if (ncolors < 0 || ncolors == 1)
+ return (PIX *)ERROR_PTR("invalid: cmap size < 0 or 1", procName, NULL);
+ if (ncolors > 0 && depth > 8)
+ return (PIX *)ERROR_PTR("can't have cmap for d > 8", procName, NULL);
+ maxcolors = (depth <= 8) ? 1 << depth : 0;
+ if (ncolors > maxcolors) {
+ L_ERROR("cmap too large for depth %d: ncolors = %d > maxcolors = %d\n",
+ procName, depth, ncolors, maxcolors);
+ return NULL;
+ }
+ if (size != 1LL * offset + 1LL * fdatabytes)
+ return (PIX *)ERROR_PTR("size incommensurate with image data",
+ procName,NULL);
+
+ /* Handle the colormap */
+ cmapBuf = NULL;
+ if (ncolors > 0) {
+ if ((cmapBuf = (l_uint8 *)LEPT_CALLOC(ncolors, sizeof(RGBA_QUAD)))
+ == NULL)
+ return (PIX *)ERROR_PTR("cmapBuf alloc fail", procName, NULL );
+
+ /* Read the colormap entry data from bmp. The RGBA_QUAD colormap
+ * entries are used for both bmp and leptonica colormaps. */
+ memcpy(cmapBuf, cdata + BMP_FHBYTES + ihbytes,
+ ncolors * sizeof(RGBA_QUAD));
+ }
+
+ /* Make a 32 bpp pix if depth is 24 bpp */
+ d = (depth == 24) ? 32 : depth;
+ if ((pix = pixCreate(width, height, d)) == NULL) {
+ LEPT_FREE(cmapBuf);
+ 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;
+
+ /* Convert the bmp colormap to a pixcmap */
+ cmap = NULL;
+ if (ncolors > 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 *)cmapBuf; /* and replace */
+ cmap->n = L_MIN(ncolors, 256);
+ for (i = 0; i < cmap->n; i++) /* set all colors opaque */
+ pixcmapSetAlpha (cmap, i, 255);
+ }
+ if (pixSetColormap(pix, cmap)) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("invalid colormap", procName, NULL);
+ }
+
+ /* Acquire the image data. Image origin for bmp is at lower right. */
+ fdata = (l_uint8 *)cdata + offset; /* start of the bmp image data */
+ pixdata = pixGetData(pix);
+ if (depth != 24) { /* typ. 1 or 8 bpp */
+ data = (l_uint8 *)pixdata + pixBpl * (height - 1);
+ for (i = 0; i < height; i++) {
+ memcpy(data, fdata, fdatabpl);
+ fdata += fdatabpl;
+ 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 = fdatabpl - 3 * width;
+ line = pixdata + pixWpl * (height - 1);
+ for (i = 0; i < height; i++) {
+ for (j = 0; j < width; j++) {
+ pword = line + j;
+ memcpy(&pel, fdata, 3);
+ fdata += 3;
+ *((l_uint8 *)pword + COLOR_RED) = pel[2];
+ *((l_uint8 *)pword + COLOR_GREEN) = pel[1];
+ *((l_uint8 *)pword + COLOR_BLUE) = pel[0];
+ /* should not use alpha byte, but for buggy readers,
+ * set it to opaque */
+ *((l_uint8 *)pword + L_ALPHA_CHANNEL) = 255;
+ }
+ if (extrabytes) {
+ for (k = 0; k < extrabytes; k++) {
+ memcpy(&pel, fdata, 1);
+ fdata++;
+ }
+ }
+ line -= pixWpl;
+ }
+ }
+
+ pixEndianByteSwap(pix);
+ if (height_neg)
+ pixFlipTB(pix, pix);
+
+ /* ----------------------------------------------
+ * We do not use 1 bpp pix with colormaps in leptonica.
+ * The colormap must be removed in such a way that the pixel
+ * values are not changed. If the values are only black and
+ * white, return a 1 bpp image; if gray, return an 8 bpp pix;
+ * otherwise, return a 32 bpp rgb pix.
+ * ---------------------------------------------- */
+ if (depth == 1 && cmap) {
+ L_INFO("removing opaque cmap from 1 bpp\n", procName);
+ pix1 = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+ pixDestroy(&pix);
+ pix = pix1; /* rename */
+ }
+
+ return pix;
+}
+
+
+/*--------------------------------------------------------------*
+ * Write bmp *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief pixWriteStreamBmp()
+ *
+ * \param[in] fp file stream
+ * \param[in] pix all depths
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixWriteStreamBmp(FILE *fp,
+ PIX *pix)
+{
+l_uint8 *data;
+size_t size, nbytes;
+
+ PROCNAME("pixWriteStreamBmp");
+
+ if (!fp)
+ return ERROR_INT("stream not defined", procName, 1);
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+
+ pixWriteMemBmp(&data, &size, pix);
+ rewind(fp);
+ nbytes = fwrite(data, 1, size, fp);
+ free(data);
+ if (nbytes != size)
+ return ERROR_INT("Write error", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixWriteMemBmp()
+ *
+ * \param[out] pfdata data of bmp formatted image
+ * \param[out] pfsize size of returned data
+ * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) 2 bpp bmp files are not valid in the original spec, and are
+ * written as 8 bpp.
+ * (2) pix with depth <= 8 bpp are written with a colormap.
+ * 16 bpp gray and 32 bpp rgb pix are written without a colormap.
+ * (3) The transparency component in an rgb pix is ignored.
+ * All 32 bpp pix have the bmp alpha component set to 255 (opaque).
+ * (4) The bmp colormap entries, RGBA_QUAD, are the same as
+ * the ones used for colormaps in leptonica. This allows
+ * a simple memcpy for bmp output.
+ * </pre>
+ */
+l_ok
+pixWriteMemBmp(l_uint8 **pfdata,
+ size_t *pfsize,
+ PIX *pixs)
+{
+l_uint8 pel[4];
+l_uint8 *cta = NULL; /* address of the bmp color table array */
+l_uint8 *fdata, *data, *fmdata;
+l_int32 cmaplen; /* number of bytes in the bmp colormap */
+l_int32 ncolors, val, stepsize, w, h, d, fdepth, xres, yres, valid;
+l_int32 pixWpl, pixBpl, extrabytes, fBpl, fWpl, i, j, k;
+l_int32 heapcm; /* extra copy of cta on the heap ? 1 : 0 */
+l_uint32 offbytes, fimagebytes;
+l_uint32 *line, *pword;
+size_t fsize;
+BMP_FH *bmpfh;
+#if defined(__GNUC__)
+BMP_HEADER *bmph;
+#define bmpih (&bmph->bmpih)
+#else
+BMP_IH *bmpih;
+#endif
+PIX *pix;
+PIXCMAP *cmap;
+RGBA_QUAD *pquad;
+
+ PROCNAME("pixWriteMemBmp");
+
+ if (pfdata) *pfdata = NULL;
+ if (pfsize) *pfsize = 0;
+ if (!pfdata)
+ return ERROR_INT("&fdata not defined", procName, 1 );
+ if (!pfsize)
+ return ERROR_INT("&fsize not defined", procName, 1 );
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+
+ /* Verify validity of colormap */
+ if ((cmap = pixGetColormap(pixs)) != NULL) {
+ pixcmapIsValid(cmap, pixs, &valid);
+ if (!valid)
+ return ERROR_INT("colormap is not valid", procName, 1);
+ }
+
+ pixGetDimensions(pixs, &w, &h, &d);
+ if (d == 2) {
+ L_WARNING("2 bpp files can't be read; converting to 8 bpp\n", procName);
+ pix = pixConvert2To8(pixs, 0, 85, 170, 255, 1);
+ d = 8;
+ } else {
+ pix = pixCopy(NULL, pixs);
+ }
+ fdepth = (d == 32) ? 24 : d;
+
+ /* Resolution is given in pixels/meter */
+ xres = (l_int32)(39.37 * (l_float32)pixGetXRes(pix) + 0.5);
+ yres = (l_int32)(39.37 * (l_float32)pixGetYRes(pix) + 0.5);
+
+ pixWpl = pixGetWpl(pix);
+ pixBpl = 4 * pixWpl;
+ fWpl = (w * fdepth + 31) / 32;
+ fBpl = 4 * fWpl;
+ fimagebytes = h * fBpl;
+ if (fimagebytes > 4LL * L_MAX_ALLOWED_PIXELS) {
+ pixDestroy(&pix);
+ return ERROR_INT("image data is too large", procName, 1);
+ }
+
+ /* If not rgb or 16 bpp, the bmp data is required to have a colormap */
+ heapcm = 0;
+ if (d == 32 || d == 16) { /* 24 bpp rgb or 16 bpp: 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; d <= 8; make a binary or gray one */
+ if (d == 1) {
+ cmaplen = sizeof(bwmap);
+ ncolors = 2;
+ cta = (l_uint8 *)bwmap;
+ } else { /* d = 2,4,8; use a grayscale output colormap */
+ ncolors = 1 << fdepth;
+ cmaplen = ncolors * sizeof(RGBA_QUAD);
+ heapcm = 1;
+ cta = (l_uint8 *)LEPT_CALLOC(cmaplen, 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;
+ lept_stderr("Pix colormap[0] = %c%c%c%d\n",
+ pcmptr[0], pcmptr[1], pcmptr[2], pcmptr[3]);
+ lept_stderr("Pix colormap[1] = %c%c%c%d\n",
+ pcmptr[4], pcmptr[5], pcmptr[6], pcmptr[7]);
+ }
+#endif /* DEBUG */
+
+ offbytes = BMP_FHBYTES + BMP_IHBYTES + cmaplen;
+ fsize = offbytes + fimagebytes;
+ fdata = (l_uint8 *)LEPT_CALLOC(fsize, 1);
+ *pfdata = fdata;
+ *pfsize = fsize;
+
+ /* Write little-endian file header data */
+ bmpfh = (BMP_FH *)fdata;
+ bmpfh->bfType[0] = (l_uint8)(BMP_ID >> 0);
+ bmpfh->bfType[1] = (l_uint8)(BMP_ID >> 8);
+ bmpfh->bfSize[0] = (l_uint8)(fsize >> 0);
+ bmpfh->bfSize[1] = (l_uint8)(fsize >> 8);
+ bmpfh->bfSize[2] = (l_uint8)(fsize >> 16);
+ bmpfh->bfSize[3] = (l_uint8)(fsize >> 24);
+ bmpfh->bfOffBits[0] = (l_uint8)(offbytes >> 0);
+ bmpfh->bfOffBits[1] = (l_uint8)(offbytes >> 8);
+ bmpfh->bfOffBits[2] = (l_uint8)(offbytes >> 16);
+ bmpfh->bfOffBits[3] = (l_uint8)(offbytes >> 24);
+
+ /* Convert to little-endian and write the info header data */
+#if defined(__GNUC__)
+ bmph = (BMP_HEADER *)bmpfh;
+#else
+ bmpih = (BMP_IH *)(fdata + BMP_FHBYTES);
+#endif
+ bmpih->biSize = convertOnBigEnd32(BMP_IHBYTES);
+ bmpih->biWidth = convertOnBigEnd32(w);
+ bmpih->biHeight = convertOnBigEnd32(h);
+ bmpih->biPlanes = convertOnBigEnd16(1);
+ bmpih->biBitCount = convertOnBigEnd16(fdepth);
+ bmpih->biSizeImage = convertOnBigEnd32(fimagebytes);
+ bmpih->biXPelsPerMeter = convertOnBigEnd32(xres);
+ bmpih->biYPelsPerMeter = convertOnBigEnd32(yres);
+ bmpih->biClrUsed = convertOnBigEnd32(ncolors);
+ bmpih->biClrImportant = convertOnBigEnd32(ncolors);
+
+ /* Copy the colormap data and free the cta if necessary */
+ if (ncolors > 0) {
+ memcpy(fdata + BMP_FHBYTES + BMP_IHBYTES, cta, cmaplen);
+ 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 (fdepth == 1 && cmap && ((l_uint8 *)(cmap->array))[0] == 0x0) {
+ pixInvert(pix, pix);
+ }
+
+ /* An endian byte swap is also required */
+ pixEndianByteSwap(pix);
+
+ /* Transfer the image data. Image origin for bmp is at lower right. */
+ fmdata = fdata + offbytes;
+ if (fdepth != 24) { /* typ 1 or 8 bpp */
+ data = (l_uint8 *)pixGetData(pix) + pixBpl * (h - 1);
+ for (i = 0; i < h; i++) {
+ memcpy(fmdata, data, fBpl);
+ data -= pixBpl;
+ fmdata += fBpl;
+ }
+ } 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 = fBpl - 3 * w;
+ line = pixGetData(pix) + pixWpl * (h - 1);
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; 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);
+ memcpy(fmdata, &pel, 3);
+ fmdata += 3;
+ }
+ if (extrabytes) {
+ for (k = 0; k < extrabytes; k++) {
+ memcpy(fmdata, &pel, 1);
+ fmdata++;
+ }
+ }
+ line -= pixWpl;
+ }
+ }
+
+ pixDestroy(&pix);
+ return 0;
+}
+
+/* --------------------------------------------*/
+#endif /* USE_BMPIO */
diff --git a/leptonica/src/bmpiostub.c b/leptonica/src/bmpiostub.c
new file mode 100644
index 00000000..a861d342
--- /dev/null
+++ b/leptonica/src/bmpiostub.c
@@ -0,0 +1,72 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file bmpiostub.c
+ * <pre>
+ *
+ * Stubs for bmpio.c functions
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if !USE_BMPIO /* defined in environ.h */
+/* --------------------------------------------*/
+
+PIX * pixReadStreamBmp(FILE *fp)
+{
+ return (PIX * )ERROR_PTR("function not present", "pixReadStreamBmp", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok pixWriteMemBmp(l_uint8 **pdata, size_t *psize, PIX *pix)
+{
+ return ERROR_INT("function not present", "pixWriteMemBmp", 1);
+}
+
+/* --------------------------------------------*/
+#endif /* !USE_BMPIO */
diff --git a/leptonica/src/bootnumgen1.c b/leptonica/src/bootnumgen1.c
new file mode 100644
index 00000000..b19f49de
--- /dev/null
+++ b/leptonica/src/bootnumgen1.c
@@ -0,0 +1,308 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file bootnumgen1.c
+ * <pre>
+ *
+ * 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 = l_bootnum_gen1(); (C)
+ * Pixa *pixa = l_bootnum_gen1(); (C++)
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*/
+/* Serialized string */
+/*---------------------------------------------------------------------*/
+static const char *l_bootnum1 =
+ "eJy9nAdUU1kbrs8hkFACCQgYakKTiIChgyAJvdgAG44tVFERQRFR0SSU0AWsgChE0MGxgV1s"
+ "CaGpIOBYQFEJomNBDaIYNJAbymHmTvz/deJd/3URyTorS98ne+/ve/e3i2LA2sQQwpKITZvX"
+ "bowhWCvO37IhNGITYWMkIXZtImE2wcraRlHRfeN/+EzoxsSIzeJPkRRHP76ctHIWIXHT+BNz"
+ "wraJd4qZAfN9lBW1FQEAUPbz9QwS/8aKXzhQ/Bfw9CmFJf6lEOu7bDMA/TkcOs169GG8V3C8"
+ "x8YNGyJi4gHS2YEbf4gfOvp5ui2SLwoTiEp5GDIXk8DEpGvpu4YnCug8EnYnNQp5FEX9Auox"
+ "1F3CHeUXJeXzEDa+n/DbAO0HRjh7wNV49D/w85rvecadmjwm2wq2bKVJ2bL7Z92EJduq40uu"
+ "i/ihCST7G52XIsNFyDGRxl+AHFcCCxWsipDXo1IThQCw/PTUz6tXbNOR0GgNW6Oy+DV1TKPg"
+ "mcAclkZrt+Eupvjh7AmNjlROLCWZxUhh5IKpHVq4Qk6GXs5HG5LwJVVgxOFzk3k+ctx0PBqz"
+ "gKQbQ42i0kEVggxwWU155/GZHs8ltNvA1q4y+f32XO//Dku7jf33vtHe5AZ9vwu5fAqTxUhn"
+ "5aaxDjNYN8CskfMk4V8hAgMu343JQ2EK77JJtxiMGwyGVjKOUsZI4wEYLkoHQK2ewpybuIoh"
+ "od9WKv3j3/2Udx4zf03/Gi7fh8nKHdOvl3Pv8ph2lKc1dU8aj4wF5XRwux1IFxxIVQhVJQ+y"
+ "kgcew8VoMfH2wKetGhsKjhpNl9BvJ1Xf0RzTb8OYkQlLv23lkW3nxQ9nQvpfcPilDB4eNxxC"
+ "FYDeeBxOn95uhWRuRqMHcLhwHLYbFWyCBoFUB9XFelbmdRJq7aUKIuPftlnu53hYau1eFPmZ"
+ "iB/6QGrZzXwgo0NLn2/jUrUmUVjKG6Ix00Va+k02LjGRghdsvghQ1yOz25LorH4AYZMfl8+6"
+ "VcBzseGSdzFFXwH1MH11wpytiRIUDrApVCf7vK3WjFJYFPbe+bjRT/pBFI1ioSk3GDzEZgSX"
+ "m6KMTg8RWGFFG6lRqB5kWXEDVxndgmEIwAA0k0zh96OwSTiiCFf50cO5bW7ZEZFIB1i+xFLm"
+ "TeS5IxIcjv/Lvu9gdZCoIH5oD8UdNruNTQcU0Wh8MG5kgCQ8GCIw5fJRGdG4XR2kJHqZNhNl"
+ "h0Z1v8HIzxYigKWJU901PsoMSIh2kqoLjXd4XrtBOyzRjiyiXaH4odO4aI071coAQR7gOGge"
+ "OIWujrXokL1cpb7n7tLWQ72E5MMtltqC6GV6RN0Ip6Bzp2chzu86kjSE0+jtOPjVTEK3lXQJ"
+ "VMq+7/RoptxoI7lCvYbQGksv5ZFsPDbbIH64IQcRaGHsF6Bug7jfrKEKmkf7Pk4vPIrcxO5C"
+ "u7f0oZWFaEwf+NxZozLqTKm7pHr4eVRd/DIYU9/vdQleHoXS/3xIfesIg4d6iUKS5grYfL4H"
+ "yOXgkeh0xhWtHDlch5sAg7bpbgi4+CEA/UgERmli8UkBbD4dTGUMAh9+gBe+YbCiUhEIWEyz"
+ "Sa0hakyRpIGfcdXEL/wYTe2MMmNYNJArsJyMmjQ6D/8DZOIpXfRUPNCPT8jQPSbA2LS5eJdd"
+ "RWOBoyIbEgBcdiQxt5V+vyupFn6OVZ9U22rXFgZLLeQPlo+rtbjLUaBT0J7tMzWp/p0q0bdV"
+ "Frl0LOkL/0BonJ0Rp2nsgouU3yD/3UnpY5pRzvktpDuEpuP3Xj06EthUk6/faxnGn8+x0dlg"
+ "Z5hr9vgkPudD49m/ol8hjEBLMwuW4xtJNPjpd7QV9cfQXl/E3YKFBqXfxeNoEXerlekE9VRe"
+ "aXBwjUy5VRaz3td8xcsSwF9RLYl6yvxm76ar5RuOOsZ96pnLIC4Uti6WWZnyxm/RD9tvrzuf"
+ "Tmu1qFO821jDyzdVbROc3g5Ym9kRm7fg2iSh4Odkjcmxop/+jA4LCsrJ/pNWmYfiilABSUCW"
+ "Dr0QTBaB/XgC00Ps4PD0dfoy/S9TyLjdiDgydgBhZR1F7aYXbsYqONDnUqkCoJ4wKBLZALlf"
+ "KQ+YJ8qcJEng5+spkz3P4u1dbVgkUL5eMkGSyM5nxwK78SRdqmzoEArdKPKIdVZ34COsAs6A"
+ "lceHKDIeP8TWehfhiIjB4JHV8SMcdhu6oqjAKkB5AJRDIsuGlAMG8CSy2MiKAGDOi5k/nFTc"
+ "LSSp4OfvXxj9UP6eoNrQIu50JKwsx0vWMmXJTXV/N8NY3znpUb1v2QZg7fUI92S/+b/pbcE6"
+ "HKUKoktUdjh2Nc1TvZqwdkqOgfZRrdmXQm99XvN1fjZ/cMqH/XU2b+VO9JNkUSv3SSZzK/jZ"
+ "fDRKEMaoZmlF6MCigrL5pJN9EcCOpTMYLFBOiR73A4VAjmDk0J/xrqRmGlCHIZOEZPn6loAu"
+ "lbo+rBCjLOKkiPoRX5toAPAY6RQz2HTbUhIAfmb/hc4GZfbgcYDZY7EAS2lfEPFW0eFJbrc8"
+ "TulRbcCVhlyKn9uDKWrZj4dbz+PsHNWnvDrlv7b24e+9KpcdTDaq/Oj8hna/nf/H+6qaVwk7"
+ "FZVDNY9u3fbFW2lQ5r2cxTqf5hBJt2gNP/GrTcY4ha2bGbC4oMS/YJxrsbi7sQmj4TswIDL1"
+ "VG2Up/qpLZ11RhUVRulBZeoLEuJcbVRU/ZqemNR47j4W3Zr8rSXOjKDIjRR+FWy//+7a7EOY"
+ "c5Ujvy+cyUs2SznB27deEge+E9CcjG5fqyJTYOFATmDepPsVpoOyNNIDUpIghMvvzkNz3bAj"
+ "jYiGFmpzLRtdK9LKqB192yZfS9mJ4hSm8wywKc2iAPF8kMtGRq1X5tIayEDnKZJQ2UATJQkj"
+ "3dR7HMZsRLcEFgxkBIyhABfAaaNy2FTQEB9MSipDyhRvpqewGGCqSiwA1D8zXJYS7DgiqRF+"
+ "+h9NJ0ZjGhcuSMqGpRFK/xET6b/ZSjGFgvPkmWo+6Yw5uL6PlyqvOf3Gnj3Zpxw64heaD9mo"
+ "ZWNWWaGrFbYcNdy9Z9n6HpP1Rn9uiGpbvuauy7nlRVN4Kx8Il3qWDF5ySzLdkTH4hZaRd/jS"
+ "Xotq3OWTRvcxZURn5SeaH1dKQkpnBMajV0f9rlBYkJARmPA4EeMx2SOwXH3fqfz2GFnvDE+3"
+ "2/KnZO4Y6vPeBBGSjcrcf1/63OG60ZNXLaQ/qxI9RcVd+QJCZs0cguX073G4pBxHxQDyXy/M"
+ "PALPB9pUoj4cZ+vYvbRP/atp7i1JNPh2QHMyrt1lA6tgoUF2YNGkHShn8kQoHQLyPoYuAPKf"
+ "AXVoe7SIEsxh8hYkK3PzUrWZGIodniEwwOKRPZGEjzQql42pa+oWgVply+wDNrMEoHpKr3Yw"
+ "BQeUf7Z7Tvo8lS7JBN8YjPZJwzGmwilnLWExQcbgt3GmlS0scUxT92qfg42XbagMv+Bv/8Y/"
+ "xz2FHfTNCzFsuzij/OMc/a2Y3pVxwtTofVPOGDyof9y+84txv3onsVX2Ijd47aH5r+I/FTde"
+ "ovuhAml7NI4YzGR5U2TPWZzOlCSDbw5+IbxB5iAYMgcsVXFH9GxvxRapBfnLRhgY4u5rTjuc"
+ "ygKNe17XqqJl+rtKVz7ZE2Do3l2Sm5igvO+SlgFNr9ZfZn2RS7Bz5fuGhrNdVw9u8Zq9+MjZ"
+ "pojlyV2lB6zJi/fML5Dkgm8PfqHFIHsQONFiTSxl8SRCrq2UwD58+r22g/aeVLAwk1D40p6y"
+ "ZMX36axkwpFtjmuDCeDa7ZnU8rIq9xvJrimzO7nsGRcvpBudDpEv59C2D7U5XtSZmuYRVNFX"
+ "+UwSCL5d+AWXDdmFZf9wcaNThxf+3tR9Sx1xx+pqL5Isa+X3qcvOtk3QXN2/Z+vWCyeCKntD"
+ "SxYXr/l09rRzv+ey9HpfXFFZ85quI0ZvL/umXL37tXH24UCd6R17H48oD2bORL+4p3FBAswG"
+ "vl/QnGypzcbqFrDAIL+wDBpbkA8qM44D1R5XUYlzlHR6QmsUYih+cU6p8y1WDQfMWJR6rq9X"
+ "+6px/oPN8Zeu4l13uxkolMwIL8O13W5/cuj3td0fz6ELrn/deN07f5l+5EE7u8tHfH4CBt85"
+ "TJ1sMe1NyjdggUnUEBoFeTyMhx4Xo+TMxNB6cfrCl4iXKdTmhlg+WcfjJW2aY6QnBx2VJO9d"
+ "uQYRlq9FGNAf2Yt42s/y3s3zYei1PaehKT9AWSfr9GEcHidJA986oCdp6l22HoVFA1kHLYiG"
+ "L/baQJaKOsmlTE1GWw4B7C4xUT3Gdq6UFCZduWC8/2xtr8PCEgb5BeqEj75TrZxCwsnxNntl"
+ "OjQpPqfvt6YYZwRVaUaH2ieapNWXrjE5KqiYoWISqLrDImnfstbmjlkxKSbLV5EPlb5Z1Oe3"
+ "SPXajQ3Yx8QnHiV726INVJ4SDyzLufVcdUipOsbeO6Ymu1CSD75V+AU+yCqEjPNtEY+P1NEI"
+ "TTMkuGylKFlumNPtSfGdc3zKQRKrwHwIuSnZdMO5Aa29UfhA1TXK9hGDy++2Dp+3szZJNrPK"
+ "Ovy6dH5bE1FtXeZJbmLcl7rHkZ/mO1xe4pD947cvqIxYF4OCqHYVSUDpDIOUAQAyDMsnDcMt"
+ "sWHAE0ZCEBw8XQBi8fQeJCGyKkSAqkXao2mK63C7kipDBOJZHWMdBtEfR4hBhSaUuSKRmkh5"
+ "KvjuL0ah3KLsZLdQTDg9MoUU8CyFDPBrXT0XVsrLSaL9f/ENS/8Z29SBdiP1++3WiuWBs7T9"
+ "OdrTLClDSoafp4QdrX9v6XRvRlyFgkn4AN9dy32482LRhmnnLlJUfd4qvrN17br7jW1oFrq8"
+ "NjzkYZ7G93yd3BZyTe2pbfsksaQzDVK2GGQaVk5gNVcriE1DGm/13KJaUFf88yErY71jTkyo"
+ "vW9RD99Fw7O3f0sefrlcuU+6737fwK2e5yyij599u2el2h7RwqluMsFlVlG013lvNa8PdNxY"
+ "XfG79VHX9V9lLS7bbzvqr/q7JJx0hQXjMThWhqkyLDjIOSyE5kgiEYIbR3CVr1NGo3GFw3kA"
+ "QkQGkUM0JFUowmBFRHoPBi0iU/o9xJ+pHy0JZxSxCocRXDJxhJvHJlOC9ZNFHYhX78kAkNnv"
+ "/WcWh+0oiQTfO/xCNftfpYbx2YZ4Sr7A61CgC+Bhlawqt94ktCCn+76comPgrCzTlXE13Knc"
+ "jhPT1BeULIiKnTcb77bc3cHA5/JJDjKzhKi4Nfcm98nxbxv76n0StHR275xFs0iKigx5KMFl"
+ "+//FOkxGjhcjk3NzI/HcHI/hckQu6BbRsrJCBkML4UB/640baWTHdmMQXBRSfqTxHpsvIsuf"
+ "J5Daul5QhN34tt3ahDNVIYnAhW8Io7aEETIQFG7fmqhwd4kkGnzzMBo5xmdRhG/XVsJCg8yD"
+ "9ySaKIWHeYlh4tEa6NGomCLHZeDV0CiRL8l25CU1CskT6aL7RFq+tS0BNwg2lEhGcxKbAF5C"
+ "hE9tmw/cxJgNOl3cXSZJAd80jL7XG6O4Vsy2gUUBmQZtiILLBjzJOJwrqYyOoMZZFyAAbhOu"
+ "4JvriS5JZdJVGcarVO9PRglgKYNcwwqoyjAxJPKiutoj98Wf8kIR5UxmcKZpMe7LZTmBl1bF"
+ "XYq3XXHoRMXjOJ01Nv4palXDCQrPPER8z0VO1hzP9w/anR93PwiM/Au9Zr9h9naOoGHfB7D8"
+ "qGVvYJgCW5JNOscwzia7FtEAiw1yDAv/ZmOT5GXZeSsqLjjwFfUtjDzvhGqlqRKnm8tjs/Zi"
+ "XTBOONmGbCUHTf1ljt98b8xdo6bZbPNuWT1ZZ8j1Y5btnJntfyaGbyWI9jfgP0whfrjBfHtZ"
+ "Ekm6NQYpmwvyCBNr0PPGkNAIt+N35fW9yqvj7Tfb98ubLZavVvBMU1jh47JEbUZdxB3iSkqS"
+ "iY7qNOMVoukbt9XGXuWtaDx9/MQGmmhfl89x+107HHt/ErKkcwRSjmvIEUDZRcCmcvh0t2Qe"
+ "oLucpCsQD3ICFu9dq9xMaQZqNSnmYEML2RG3W8SjCkR5YLLIiOSc36qyLwzZw8Rjv6LYCYtJ"
+ "QqqQw88bRgEv35KAxz2x7yWR4LsBjPilO4Z04FPHfamQHCaQoqihdMQgDS2k1ql45IhwuD4c"
+ "ToBTqbIShoQKMJ7KaBGGWoZEOpc5RRP2oxOAShPc3SYtxV2SsqXL81KOEsjEBP1dpxZHALn2"
+ "88SgnkCX35ZQzDXtQ2QPTj9Kn2cWfuPj+2as6afjyPKrccqvSvz98mbWMxalyHnVnMu9+NSU"
+ "bBJhau9ju/zSLfz8qTQQTzfJBBfLWksSSbeiMB5tqQbrVGERQc4F2rNGpSDEmULsUZaIO48B"
+ "gydKQWGTsFmHGIV6DFG/Bx+/jXiJxuYjssgPynkiKhqIf+EwxbX72msJ3Xbw0/gvLPhC9mSy"
+ "xM4dYfAQRlgbdZIulQSEgswGsgy6hSDEZLGEDXiZADskGlNmGzyMQSJf09DoviYBOblFmM5g"
+ "gZWXwT9Q4Y/xrzHAu4fTijbbFyhLwsBP3L+wxAt5kokl3nljyx9Yj/a8oCpkWEWYhrfbndD4"
+ "ZS+9y67LuOkGGtfPMD+8szk60dYcp1x+56FS+59rrnckOc+q8dJ7AHg1FAWPlB+OSLu2ziF6"
+ "3q3tl1CPBmY8p2X7EySh4Ofx0VKG1hjU5+UZO2FBQW5kopo2L2esXF13yjPZMlTGO8TyTqr5"
+ "YDlJxkTu+19YdXXL+Z+L3txL21xVZROG7iiOOvKmAIcIpHU0Xtscbbtt8c1534MeTC95N/tZ"
+ "0y6ERbaxsaB20bAkEPz0P1rNGN/T9NrPoBEWEGRM9KGhQgdAJlOGiZBDYzK8UWhKDaG7FQWY"
+ "t0zdeXbQtUNSnXQJXHtMXanArQ2WOsicTKzYmjRXq9IJ4in/JyzWH3WBKvfIE0T+VnSwHWmy"
+ "Iqo7I/tey92PM2/M2hp75rbWY2Jhn7nsDM/bm4r/TPf48vShS+CHU/amSWmXWPYnhlnKNy1o"
+ "4CvOtLUVm8MlFwbs4Ofw0Yg0vrDzW0VXPywqyJZMTIZ3NrPEnQiQazfS8PWmLrp5sk/+VFAy"
+ "5Zx7Z5maG5Esm5v4ebdzhlLg6evU29hsX/vlztEXW/Yox3QdPrxzwxTbxtMjqtdo7be68Tfy"
+ "V2t7HfkCbFninJfla39IEgt+Rh8dGyZjWIyXC07CwoKsyZzJ8sVLPFeEoQjLVDIYX0BCDD2U"
+ "SYsVoDJo9CoueXSpA8MQWI0udSTgYqhhVCo1lAJMYw4oJ2FtFEIKD6NEIJCrHXCe3U8RSKJI"
+ "t9Nv2hiK4tp57rBQoEw+sXS7pfmaKoWq7skbyKNUyu7XXq6tEcV7RyGSznzNQHMO9jXrb5p6"
+ "tbY2b75n1tCrrqPVT8xmNGwYmInKbvxsEF/kHSisV+46qvFuC3DDdrY97+TueEkc+Blec7Jl"
+ "YgyM98PCgTJ85D8qZ5TRUFyIzfwiY6zNIpo5tmQ2HJh/KVVZ4ZQes9s9eWrwgWjDEqznnIcZ"
+ "57AZrvK5PcVeO04dHYmw7JxqXyInmit4zCftAsNibjqdI88qeUa0fufanhg9ZBbapvy7o0tO"
+ "MVuoL0kp3cLAOGWaZukBWJT/qld4ca1U6AS0XNuttw13Guy6rhw4NM1SX6nWq2OFcwJpSZRf"
+ "4m2uUY6hgxp94VltfmyvauKftc6p25kP7POO3Wio3/XhfaTKgaOnh0P2driItn+cw7HdvLhc"
+ "AskeviEYzaHj9YopRafhRWfIEEzUlyJyx6yZ7Ez/AjuX37ypqQRF/yWKhxLF5iyIz1BbZjrn"
+ "UWlpwWH220UfSbtDShBnhgKPB8bfNfRKa1nrt23bmRvTepHeGxs+3mFer+4ivxe9638c90AE"
+ "mG6xeCs73bdPEk66ZQEp4wVkECL+uR1ntBiTH1SLDNnb7MWQyaivnaZlEJHV1BCvrn7W6f29"
+ "Nybxarm+JgGPMrtmX06cvwVpORtHuz60gyz7GSkXze084MGPEkXtVYiyMznqxFrygDOS534o"
+ "raZwpb7BVbKbg6fSMklI+IZBC4Dyq+m99ethQUKGYQEEeW0U0qt9xlXf2/LRtdGtd0L1TYwq"
+ "KiiFwe5HWU4Pf+RXOHbWV2QyMq9MPWK53f9CDkbzJf6k78zr6/5gOPbpHY7u+aO8JgnYHEJK"
+ "rWg7Hy2JA98ujB6rGPc/rbffwttsBNkFs8mtw3RAGY1GqeFUXKiJgAeKhF3kAqZq030ZDDfQ"
+ "c1cAALwT6KoetLpxWFIpfOvwC+dWIOtAgbIRp5VPL+IBRh4IhHqVA4mPp+aT2Xx8hjZuxMFa"
+ "uCZc0NjMTy/lpc/mpm9nIkSqWVo4t4x0PeAeqGYi6CiQPBtiD98k/EL1FTIJE3UZr+aJZU4b"
+ "9X2krHIrmfk9Zjmfc5RW+IUk6jPret6POCcaN3jHqwf0nS+/HeEcEzdDiTdvt9c1xonuwE92"
+ "J8GSOaLDe7Z7h3u3mf4RtdR0OJhQKJDRbHXZc8il45wkm3TbC6UMaxBb8P814/Rs91Bfxi3f"
+ "pm6MpJrMelS1T87dR1X1Juto4p7sa+cbtGs03m26eHB68YnQiruqXY6BcYQdCxP0eg0HDLNH"
+ "LrPif1Q5ODyubT28a18i8Gb77GHKpeuSK3D28G3DaEQbL8OWvKZNgcUl6YBYu3kilDNTNLrh"
+ "Q92BYuWhxyVjNSvDBGEBjfzyst08Lkp+l3xdOuuKXkYno7AI1CK5NScFpMs9NeilDIoEKGBL"
+ "PVmWwa6XLAvaw7cMapPd79SBpf6wUP61A3RLs4HK2HaItWZmTjJL3z50BJcWl5u7UR9fPHrb"
+ "uuI4sr+r1Scni6/IzaryXOF2od7ocse0bfZn7CvTd+IJNTm0NWeJew475lCLZ870fDEk89zT"
+ "a1q/kSNbkko6iyDlMhRkhFZDRmiCagH2wcNBouOZtNRFhKtBTgqxKXc0DlnOSfZycRqw0qsu"
+ "ruqRt1V7oqAtV5S6rvbD7tbtikpni8wz503xUKprOPtsg+IyRPrGrMuW3n+Irlu8fwXq3HK8"
+ "73jPUrKm5gDfLvzCxAJyQPP+Oa6wshwP2VRLzrErtqtfXzJvl4+nWsvUDfAPVt2vy46UZbSe"
+ "nd/rti+l8l7CZf+96h/nrth0gLSuY9XIG3eH2+bZcxpQp4Mc4oOnn1slCSPdfsNxD/7m4hJn"
+ "WDCQ94memPvtHl3uRXvhDU3kEOHyd2ZY47yyPMyRh8Jl78w4JXCd32+8fJBm/5G5gBk4VLAt"
+ "OHnQb8XiL3W8lKc+tKLXO45v8Lq28Yz3zYWeDfZOg542V5zv1E6PxG8htH7bcnpT/TO1I2dW"
+ "f/kTr6bphH13rAAvyQrfJWhONtyVxMYMWKyQFQqZbDiFVIo42IsCOjyrQJ626pljM9ie0X37"
+ "DVcgmDroHzGxRikzfxSxNAI7rwQrHHiqOuVYsfdr74MLjm1J+fCxZ33O86L8iJU1d+/tz2nx"
+ "vJmKjPvuELnjqY8wiEYpsiUx81fclwSE7xtGAce3+bn2XZwGCxCyQdCGcgFNiOGSG8hogiCF"
+ "xeT5pClz08n2aMxIE8lFyOXG8vHy5+lxRuoOojB6WCQhUrbHm9giv1iJ44LGMHDhmk3NGA07"
+ "rDOOkDDylwgBAOvs//J9tr9Vkgq+xxhNy+Mlrhf3T/TCooLc0AyIaimH35jMo8t5IHA7gXK8"
+ "ZxnGV96MyOG7JfNAFAJrGwsAgkOEGSj3Ej1JqfDtxC9U436yzJNKwsq1u03F3gef+GOPXZFV"
+ "JNDfuP92ztPolngWiI3coHo92rYrhV8aYPrZfP4C4bNio27bp0HL9kaom/hrRmBuJLrvfFYe"
+ "N495lml2FKPztvydXAzHoivVau0lSbb/6WmFf+0hHV/CGttDOt1GJifhWkBF+DGC4iOSTqql"
+ "nKGjadbjEiHm09wHvvsyHZUK/E7d21UzB2/plOHzQ/OBVtz6P98nP0deMedYWNp7/MHcIHzy"
+ "MMe+H7FjwOVlonGc5Bq8A3xH8QvNBjkK37/HDcgVRRo3ozkiSuEwyMVHyo+AtZQhzAcMzhXR"
+ "H0bYhan7i5Xoir1rY0ZiUxDYlBZ36294DxkuqswRiwBUCuz2UdfHfJTk+J+uMUAck6u4Ij6C"
+ "K0L4XgGztAgCPFbkVkvWdSUlOQoYPJuAD2IMlbpGhtgU6WgRysAM3mvd1GKlRzPZfITxtz/F"
+ "NqZ+9ltVp3WdkhTw7YMaIPUBEsgUBUxSNDJ46WmjS+hYV4CTQ2mm0XkDNNkRAqauGTuUh1Eh"
+ "uYx0Aw0iGonazaV0oeua8ZQOMKeTsV2A0adGkZPoPKxzhwvg5eh4PO2ZfpYEj6N0fmGcZ7qu"
+ "jiksnn+t/EzaIYzG/hufr6w6WZG8nnKFEqchW4hVU+/JvP3x3ibvOPRRo0HDSzIPI+/e26bR"
+ "/z3HD7vqSFq9dh1+iKf/6aGf+c31I517Y9IemkXPun9C8lyZI3zToAZIfeYCckDOfx9tZQO1"
+ "ytQ+oB6F+052r3KRJ7uyq0R8kCkidKnU9401mbj/cZJpZSHhV2mfUYAIbf2wObcNKSkdvgf4"
+ "hcaA/A60WA0dF8kmErUYixf4F+zZn3Eu4zwYbxnUmz991auNetfVjKYcyBgwKb4dTD8Xka2z"
+ "8M1dFv4tQR9YunR9+9IXN1syDTf8cSNsziEy7blZ3pw/U3wkkeBn/V8YL5InYFTpBMCjPT6H"
+ "R6zR1lhfXx6r7KhJ3z3/VtrvmmebXlm2rzilf+rA6RWnU9vwT9NXefk3Vfa60Uaq0hJ1qDb2"
+ "68nmA4sFJSdnjlx3dAqZVnxPEucf6d6a9DeQ+P1/aiXs+HkF/xOf/0bSrx19gTFtW1z/jQQZ"
+ "GejstID7gs1nXGXwwDAEl4NQRqfjSSQSjUql+nA/ULqEYOVrMGCDERbxVdi9EQAeEqZOL3Fq"
+ "dJNUbieV8tH3qmPKm4MBX1jK/71vNZFGZzHAHF2cvi47nb6ODQBmq9VJRSGIDElx9lKJQ0+K"
+ "43jHYmCJg+yJFfS1BrBjKUCqNg732ookTCGA2O9hbP7vdJ4PyE1HoTEYkm40cCKECgA1LNW1"
+ "A/MoJEnRDlKJ/k93YPwX0ZDvcP072LQBHvPM0X8l4kb6ScKCkNGDUWCOvtjUdocI2LX8PAaP"
+ "jLWjBJIR8rZVJN3ukEQqAhiga5SV3rldIUngKBUBZpJAeVdxHSwCKC3Pmog5d64pAAQ00M41"
+ "3HEyv172WyxYfSHfU6h71uX6S42lK6rWKyzEuS/M2jOloDTt/I0heVwrsgunBTz6oiW5C9vR"
+ "Serv/2f3GPwX9VA6dvp7UxUfkeM6uukN4Y4ZIbmkslJoOJUOkm5+SCKbyy9N49GU1EJDBI21"
+ "bWxuGwEBRDXrh6A6ciSne04kqcT/p2sB/ot4KPdO1Ce9asfCPcDJWxFg7e4dYhtdXdhZZxRQ"
+ "Q9l0p2R3zb6gWIO0JKeUQgHb+UkAJ+fisv7LueTUWO1O8iGSVkXPB4RkJdjJSuruM84gJBJV"
+ "YTH8e5/F+AAYwFgJUUftST1hApTnJsKXxt08FPahVRJQjhJR3nSTmVwRrlhkdUE8b6N36NUu"
+ "vd9rI6ndWurOozGmfZVhjzEs7VC6nQV1HvFQpafzYpW4/Qj0B6psuK5LLZ8uZ6ziiQrGPdGi"
+ "9AxjcAI8vWkQhU7H0OvwfHGrU3CuVlUMyXUTJ5v/ddeHMqvH5PVG6bwUZS5Cn4kwR7eg0R9Q"
+ "lAy9HELfayQTgUZjRjcakoRrQgStXH5eOi8PzcWUndFmiufffTRgaTfucXYnUXJty0m6k3/j"
+ "u6VO7rx6AZY5gDLp3wV6AR2UmYJTofNRcswGvCK6hdIsTGcw9LRE3OSCXrd3h9xwld8IbSUI"
+ "BUXqZrfjNODJQPowCIgMTJBfStsOSOqX7gaeX5z8G07ejMEGPEU43AiJZEsiUBDBvfgTGYis"
+ "EfG/YvVN6+MrnO9WSYX/0033UFINHVdo12ylyB219xvvV25+f+9x7MH1V897qPScfEN8d/+P"
+ "ktoom6Cm2pSIEHJcpr1C9cJOonn4/djn6LODA4Mj1QEHM9ZFGJwbunw17lDXSUxgZMHSHVkD"
+ "Vc9TeeRhTdZMcJ9VrNn9vZI1QifpTvSPb/TKewlKd4tT4GQf2kXnIQZAJioHhaNU9TMQHjbY"
+ "yzSgB6+CHqZ8J8uTHdn8PFBDtAa4bUXhY5DIXcRIVEgZzqatBFmmnX+VMOA2KNxLFneGw5a0"
+ "37D9DZJA0p3W+3+7QmLszDhJ3rNdJyXQZYn/Qf3cQBwnSFsxfJPaFd/+J6ICni++6VnOviVc"
+ "9lq354NE++xzrDwqSPY/q4APKp3xZm74d5RWtb0B5frXB5Ik0u3Bk3J4Q8kZugxj/AoJrfNk"
+ "qqNABHrYqDuQEFYIeVMSiV4tI55ByyFf49HoYZp4AJWH7CJEisB9qD9CksqARyjrb6C8tbhN"
+ "Lh0zVXs9u3qLBIkVSbqD/DpjKMtqeqxhoUCpeu7EKvxEfakdf6wJR7rz4UDyxeZpObERWb6c"
+ "imOJuRE7HxlEamQmyu/jbNgp+/j+o5zIEY4pceQBb9vIlk+zh12ePV35LXtV8FITwH2wVfUn"
+ "MNJd6POL10VAh/EcCSIGg+FGB5HadHHDiOfFZLogBcROJQlpQB3KlSTcKNsTSYgcEQ8cvWQR"
+ "38OqrQQ/iBWCqe8NBKC8qHE3TSQCAO5ht7PMF1+9foLzP62cQ2l8ova3+N7oFBOdxitN2QOa"
+ "69ZzzM2pNl11qubTN3HoWxM1V++9vkPhtMK7tKzY83HVQTWzg4w3vVt9gum4qXTrgd2U+xT7"
+ "m1ZOe/dXz5lTFV2oN3x8FmckEegcnnm1SYciGdysSNJNn8eHkOet+1mw2KAkP3fS39LoPFAc"
+ "3RDi6KaNo3QwGEMgAvEDNbcZXZdDccJGAT0ICrpHJk1rRETv3kX4Xa9ikIFYJNc2t6wTRU9I"
+ "oooH0Z3D+JxoJ3XJk1JWpH+ke5t/uBabn7uW0ZQ5Pt1QyPYf+hvI0mj0BSYeIyf8p3BNhoAE"
+ "KTy6HBdUYNqrMBNUmANotIiRkcLDo7gYBSZKkyAbjvAasSHku+D640ikWJJtMClJCAKuXuqC"
+ "+1y5lz9hsJOKATs5fmw+14fAYoAitPtkRfYIj+bMpSUwaZS6Vj4qo1drpMNGWIliYURub8Kc"
+ "uWEJxFkq3sOkB+xmQFYZTfgg6gx5zW6jJQACrsGKR/o53j/BsP9fN8W/96onCkaXKHB+JBcB"
+ "J1afqSKiOlI5bdM4pNIUHg0xq/gFm93KZtdy2thg0OBLFJDA0FueaLqY/xPtDlJp/0/38P0X"
+ "7VA8nrxnqbWLzU+vZvBQHuJEQtiVD9RhtHEqIzFUR+EtBks1Q0fAwFWeGQKwiFcdZBI7zlnd"
+ "waOfh6EMYxiRjObwjWKEP6c5z4i4eewnOI7/66b4Sc1y9Do+oS76sx56UA8tMvCVyRoBC+S4"
+ "eCUmXhONN6O84WCYCDJOm0ayFTfUCAIo8cJ1aeKcv/5Ev5PUI2J8TxA2fa8sLP1QCPaAiggT"
+ "M1kfyxWRxsxyqoXPKtk6GcuFacHE+29Prlmqb5L6ViB8tjYkeboHTg7h8FsbYva8DyVOdfXL"
+ "yefN8Tm2T74Dkhz/vKQPJsfPdgz9Fw4o3E6M7MW1Y7tSvDimx8qa5XP2tMf29px9Fd0TTfFj"
+ "7GvbNOuIv6Z3I6pYd+hk3S03tvLZ6uSHBUasrMPBRmeVa/y1yTPiwn5yk9I/b+uDgSEPQBVK"
+ "RD+iDRYGNC/RnOhOb9ZQZWu9mZG2HU2ATC4QdgdpkZdOS/+JNGupezpmTNrU4MQnsKRBExIL"
+ "6IBMFZWaT+1GGKogt5G+o7xpxrhdvqSkbmpiB7utSzwKxD8gcPGTwiev491FP1FsI5Xi0QrH"
+ "eF3SLO4uGpZiaJJKmpxn5/FSdLmgKxMRjf6wAf1hHrovnf0GyOhNZw16oNM7cfp8K5d8BHDp"
+ "Itb32CUe6yeipUuzo9Z7fO+OxyKXeKl6wOQ9paKZDJ4II8cUodRxI1ZEkhAMK8PJO7D5iEXN"
+ "QH2OvBCTWhTYj8dG+nTjmwpFoDiIDgOVTLDyIBi1CvEDATw74abm0ux58ic00iXcUY86vg9E"
+ "b86zbFg00LCMnbjW4F61choJiwjwUZ+uxYj+vDRE1l7gZq5p7/R2ZmyEojrNByxeOtSHGLJq"
+ "SyFitWbgzFnzjsR6od5lt868m6Lc14A69te5DN/KxCg9/WU6uh3dj53scg7vjTJcHfhgWfQ6"
+ "i7bfmWdZyEGbJ/jnzgtPRDanXPsJtHTpWReA9sZuQ1xYIdVI2TIRi5o5CqmjB8tNnXrfJORr"
+ "9hEsOt1kNCPrvA3tSY267g0OSpZ9r1K0EsmLtjKyjORkg61WLGvbOVzWWrFTIcHbOmhl2W+7"
+ "evd3JOB2bt+x5VIUwUkBeevNPX6FnGHBg3Ci0xfN6O1avwOxTFF6e9dRl4cjKwfZmYs8f0Iu"
+ "XXLXmuy8ixJO3YBFDhmTiVssttybWAbzsCRqMbwMq5dqu5gZEY6xKI6oDEOv4qVXBK6FW9jq"
+ "qGj1MNVEvYIvX1ivbs92W7QoNdP0YL7bEZ76YJ7urH4dZIVFx94752as/Xjnjp1wSd0XOcxF"
+ "/4ET7bMlj0VZWUmX8kdL3+OzluPV35xhQUIpfxG0Vf2aMp0AeLXPIRK1OIG6yjlpYVcUx84R"
+ "8TI5rX4qWr6rPerO3d0Un77xyHmLyt0RXk7NfsTdT6M8Lx+9evX7VfPNN88a3kjJWPDF4b4c"
+ "2Em5JjodcfMnYNJ5Ae3J1rvlNvUmLDDI4W+YOB91j6M8dmDHQNcxKPmY7/QEQ72O+wfcFfSd"
+ "sI+0ctvvKul04LuNdLpkQLRNGdWY69Gaax768MqUrTMqFGMEDz5mVJgce3Aye772jqRSAWpE"
+ "1/Ct4T2dGM+v7msIJX0Pc28o7mJZ7QDtmgP/lE3N9JcktpbONWhNhietm8qXYBFD84GJsxUR"
+ "90avgFCXbcOr35e3JFzJSEtZlhRbLTu1XXlXkWHvjjUFDa/jWvRMP57pOZKpBp5eiCDqGYlN"
+ "OIq0R+GC3IM8iz/2IYJBzO3ec8te2RoXtLkZfSi4+77Uc598TW5SZuNqrFdA5fKWq5J3r1hZ"
+ "S+cstCeDUrxenQ4sVMh3r4f2st8Y3ZLsxZtDjJqLY2iUUA7PpZ53UDQKCMIeOVhX2vB6Rivj"
+ "nWVbqJ8MqLVEJd6neKZxvvY7htofZX3bjfojvsrOz2cv+k3e3uOeXwnRCv8yPQavvfCqVfHJ"
+ "0nbSQMFq/qPDd+TMh/zrtxevkby70cpaOruiM9mZA1KS6LB4/7UDcfSsdR0B7c1LJxLP1+bG"
+ "rVxy21i13CrLJ9aPQbi1vze+xTT/Q+Pn8FNMOsbmEFHF6M3TbBFLY6ZbDErvWcI5KyJmYOQ1"
+ "jvipOnR1+/zAqSjZt8d/u77rjWELlRhwbXvuPHDQ1+Ia0DfkTV9Xvvr/OvH0fwCqtDFT";
+
+/*---------------------------------------------------------------------*/
+/* Auto-generated deserializer */
+/*---------------------------------------------------------------------*/
+/*!
+ * \brief l_bootnum_gen1()
+ *
+ * \return pixa of labeled digits
+ *
+ * <pre>
+ * Call this way:
+ * PIXA *pixa = l_bootnum_gen1(); (C)
+ * Pixa *pixa = l_bootnum_gen1(); (C++)
+ * </pre>
+ */
+PIXA *
+l_bootnum_gen1(void)
+{
+l_uint8 *data1, *data2;
+l_int32 size1;
+size_t size2;
+PIXA *pixa;
+
+ /* Unencode selected string, write to file, and read it */
+ data1 = decodeBase64(l_bootnum1, strlen(l_bootnum1), &size1);
+ data2 = zlibUncompress(data1, size1, &size2);
+ pixa = pixaReadMem(data2, size2);
+ lept_free(data1);
+ lept_free(data2);
+ return pixa;
+}
diff --git a/leptonica/src/bootnumgen2.c b/leptonica/src/bootnumgen2.c
new file mode 100644
index 00000000..ffcf6474
--- /dev/null
+++ b/leptonica/src/bootnumgen2.c
@@ -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.
+ *====================================================================*/
+
+/*!
+ * \file bootnumgen2.c
+ * <pre>
+ *
+ * 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 = l_bootnum_gen2(); (C)
+ * Pixa *pixa = l_bootnum_gen2(); (C++)
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 */
+/*---------------------------------------------------------------------*/
+/*!
+ * \brief l_bootnum_gen2()
+ *
+ * \return pixa of labeled digits
+ *
+ * <pre>
+ * Call this way:
+ * PIXA *pixa = l_bootnum_gen2(); (C)
+ * Pixa *pixa = l_bootnum_gen2(); (C++)
+ * </pre>
+ */
+PIXA *
+l_bootnum_gen2(void)
+{
+l_uint8 *data1, *data2;
+l_int32 size1;
+size_t size2;
+PIXA *pixa;
+
+ /* Unencode selected string, write to file, and read it */
+ data1 = decodeBase64(l_bootnum2, strlen(l_bootnum2), &size1);
+ data2 = zlibUncompress(data1, size1, &size2);
+ pixa = pixaReadMem(data2, size2);
+ lept_free(data1);
+ lept_free(data2);
+ return pixa;
+}
diff --git a/leptonica/src/bootnumgen3.c b/leptonica/src/bootnumgen3.c
new file mode 100644
index 00000000..6d45d16a
--- /dev/null
+++ b/leptonica/src/bootnumgen3.c
@@ -0,0 +1,368 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*
+ * \file bootnumgen3.c
+ * <pre>
+ *
+ * Function for generating prog/recog/digits/bootnum3.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(103); // arbitrary integer
+ * strcodeGenerate(strc, "recog/digits/bootnum3.pa", "PIXA");
+ * strcodeFinalize(&strc, ".");
+ *
+ * The two output files, autogen.103.c and autogen.103.h, were
+ * then slightly edited and merged into this file.
+ *
+ * Call this way:
+ * PIXA *pixa = l_bootnum_gen3(); (C)
+ * Pixa *pixa = l_bootnum_gen3(); (C++)
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*/
+/* Serialized string */
+/*---------------------------------------------------------------------*/
+static const char *l_strdata_0 =
+ "eJy9nXk01P37/2cMM4NhZqxjnbGPsoxdhRn7WpGSiowlqSSyJmXGTgiplJSt0o42oYx9iUIq"
+ "FWXQolRoG4X5jmXcy/id07v7fH5/uO/7zHHu8354vd7X83ldr+t1DZ/jjghPnMu2vcE79gTg"
+ "tPnWhO722rYXt8cXF7gjAmeM09I15OMz2/P/+B2vPRHbglm/ReCb/fUtBPeVuIi9c5/oEAhq"
+ "uH2L/82X7LjGWoBPkg8EAgnY2lisY/1bkvWjBGb9AxQi0yDF+hdvoM2mYBBIQ2H2Bxxxlhg2"
+ "+2GIpWuI+Z7du7cFhIAMC/B6OawPt9lamK6Hn9jwoIOXQkDwDGw0ingk4Ha2/3LQdYOUAS5Z"
+ "YxGv02fz5aJWDCfS1qpIm5orBfgM1tlJ5/DKJ9sEFPIZ1GJDn9p0P7//NT7Pr1focR9flrM0"
+ "xhETaaEun/ALNuT3aFfACeQE2mql12jY+9lHtLVcY3HVjBwzx6kFiBPD+lGY4zxX9mPVb3ES"
+ "rn25e5H14Xo2Z5kAi9Oi67pbsQ/VRiK+Q15B7D4+lWRryUCdFLm2ojX8yZWr9pbnxWT87M5k"
+ "IJ+JiLrDd9fImOwQVtjvRFz/Scfnq9SWB75mntGi0UboD8srdnaYenBgaQNePsU5rBpT8Xu/"
+ "haXV+zXNiPWh1TyWN0OByQTVg6G4Lkq8IQ2uKEtJwuIGoxWS/EBoGL0BPJEEK7EwN26RQOD2"
+ "S0hjMPnckFM8WWQj2hiD+AsM2p/gIeq8Z/VHDhAdQCBSiyCOsVGU3wLRNp3uS2B96DcPsu1B"
+ "GZqGQ5g7BYmo6pj1Kl4q5ZVNczLiUuJJVFDa5la97hW8SloqsT6De+t7a/GhGzH7Vtyxrg2A"
+ "JDHwDZbiLrwwr+BlLmJu6apNtqdulirH9Fu4KOtcu+3SM+0uftVD+UwUSO2RTf3+gmE7DlRd"
+ "QKjii6h2NacEfwtVR//nKIr14eZ/bcVruLM+XA3S61MTjCQdaHysvUjV6LWVTHl6ao1JkamR"
+ "f1/7aKrr0QI7gpJDcr3o6ybtiZcvLqS0r0oId+P9HhzZZFb1otFldOvlmUQTfpvlAWYdlfc4"
+ "4PQAwUkvwpV7bJ/8LTjdktP7rrM+9JyH291ey4onKG7aUaOJVFE//hHaMTU6LXG3u+rGOuMD"
+ "cQ2r4lG1BxBq/IdcjhQe9StREAxvMcVcQNYmCK1pFM3zeffePzHGw8aUf0Y7loKAjt5Yg23w"
+ "HM0zctkRCXmMsx+wvKNE5WDUB8Qosci4Puzy3d9i1Ht1wlaJ9aH7PGMoa69SCHCezodn80O4"
+ "rVv9PHX7LG7b0CUm0R8dxs00sIIhJZ7WWoxYTOqG8gy0rvbLtuVOJl33TgnZvikZ8tnfwMVU"
+ "OHibP2nYqK4K1dGaftTexfywe0DFPuOA97fThTn4DP7XfPpWGZgzrA/d/r5BUeZO1qrdtfDU"
+ "rK7BLm07q/xi0soJtKrfVs+8KJev/XyiHxL413T4kzbmFef0xL9AXqgF82grdsrbVKzu8oyC"
+ "O/jaVScf1+TV1nmSn6tOH4Os2GlIuOIZ94wDzxAw3rzknVkWjfktPAOt43he1odb5vHc2+fe"
+ "P27a4XWO2lxFpRihMyk35cvgCsXFJK1bSoeSNNd8rjuccgJXngnvsPZ5dB2e7MwnB2nt9Bh5"
+ "UGyUnt427c17MuiKqV4FLNCneL+9lb4gnX9Id22eZG5oIgfdiv+10LEF3WmebvWDKgEaAQ7q"
+ "MtfI0pBSKqr1Sx1ErzuX5OM9cfOrRTA8uFDaf1RtvfIpIW6pJ52jzcGVdvobf7XmSSieFNq9"
+ "45sSWO/08Y93DT41YVfu9lDZbOeSuJFTvIG5lD8ImSueaPII/rUj9R7MRhWEFX17vKxMra2F"
+ "uLBqpat1K9w4Ep8WbQl5pT7MUyN51IpOc7V1EKLf5vaj1lY5T6gku4S9tD2jpmz5HvrJnRZa"
+ "v8tRda892vl+ztnb3+p7Zb/WqK7K8Rrl5+QD7k7mt2TgOxFlQO5k06L6zUvCnrM2tvbN8nB9"
+ "hQe+qXVwVVX51CPyQoURj1vDmwqdGitsRb5/8hpUDFsJCwk+nK/so3LOnGEY3bDt4fMrsgGh"
+ "cV2vbvEdsl+PYL7lOU7QjjyS3rKdEw6YRxFf3JEbKy/fAuRRNBY8ih+Z2UyjpSZIyDFAKCZk"
+ "kInrs5ELYDSPJRakF1Cph8FSfmQyCGQMXqlSeFeCh/OJgZmRP4gQbDOyY0HEOsoE4gki5k4K"
+ "fG3+oCKCpO/1VCF9Srp8XW+2BzFlzZHqcLmkd67JwvZ4i3qL9amecEJwr430xbiDz2KJH8Zz"
+ "IivuWp7/cWHfdaPRY7s8JiS6ZTqMT7g4qIu7t05HT7y38nC55zVgnadffMSUExaYHcEsvltV"
+ "N5pjAdmRhb3nnjYX7SGadplZaNfY9K4WP09hq3wH0SEYn0VQ5MjuyXdJsUebHLZxpbQi7c+l"
+ "N5hoZyW/4N/Z1D9u2kCcGA5wjI7o2vI1/Nf3OM3q3OlBtUrQrtfq53a1ns/khANuR+b33rXN"
+ "G00B2RGfv71YOBHuTh2R7ErqckPq1QZohmuFWBGhTlMVHnHcr6gmotF97ySmX8pK+HGmfLnN"
+ "DNJGvIM3WuK9mosXTFW++VGG/MZS79q3WS3k2tNF/Xd7bLH4+3Cv/fg6VMCjnbbcazabHeXk"
+ "BGZJ/iCAsC3J6r927Kxkd2HP5lejMUpFr8s3WXmi4u5YbJRL2bjpVqaBMux0r9qpznj7VORt"
+ "EvLY3Up3ka/c6ClYaovnhcmbN+4aHKpYrdnzmLBhxBzkxgkEzIOIsH5k5oBipoP8AC2cHTux"
+ "YY5D6pkwxyhQihQlhw6pJ6LEGLSxIlASMYrMADUgqQwtFBY6GIb3PehIo9FwNBBEgPSdCYGS"
+ "dPhxbw6SakEKu4k58asOx3PSALMcQqwf7BxN3fJ8RUCOasOCKM9aDhyGh64jlp2qmHuhIPBK"
+ "szJGPL8YN4D1Lmzc+FJLQ6vw3RpdzffybTsTDFdHfvTN7VAkta8Y4Kvgu3eg76HnoELGTpz4"
+ "r80eHx/dDchn6Op+eqDbxgkGzG0IL4Kpj9yXBCTMC7mM8fy+g5DsVddpc9XwpmY5kVTLXXqH"
+ "n+FeizvFYBV93HdIHXC6kT/mdjJji5mWCt4d5cHgaXtAiX0vqM9jFatsYJKyaUTx0+ESAnPz"
+ "5A3VTxeQjBjdV64DLTkcdNrAbIfIIt1DvU5vQLJsw96E9VNUOqweloDkxWJkp/DkCEY9hT6A"
+ "hSS8IXIhOpi4nGksxDZHIqn2JxXj88Q0/wTuOlGENPqznkGlE5kwUPxpzaK6VXJgThZgFmN2"
+ "C8rO/V94w4OpgDTNcSEStpfxUkgi3F1o/lJu+/GVX2g0bbVGUalJJTc/3OnlGLhgdQg+L8ml"
+ "BC2htb3Mwks9+afCzmr9F7vLn03diQwSHMRcSfHyHNwS2cenov9p86d0V04mYM4Cvbg+qtJS"
+ "KoCka8HrurOj3mH8ulknf/ICfpNqkmWWAAZnF8i9gZVI+2O3Ce9Aa8E7vM407Es+BvvxGfX4"
+ "TveKLeOrxdRN91v9jFCIEP/xrsblbu5y/Z7dh61WcUIBMx/Ci5GPLLcTDSjyWf4V+cDzkS9p"
+ "NvKB64n4GUgdDxJzMCqCNoZOwnKNe+I/wbwO5ptY5ZuEjDFJXJ0OOOhF01oslpXk+BhdsN+k"
+ "yMcJAsxY/EFsYAc9l4U0cnZ1SAgISSEl+/JYbilf7pjQofjVRmZtmvKyNSgbkY2NYZ3e5uHg"
+ "IIlM/i4X+e+wVXTN/gPyF8s35K3gu2wryFhhwvM2X/rb8p7dz22GBMR09JPMJSR3c5IBcxXo"
+ "xSXy6alU+y0ydgZptOho/cgkHgHEBFYSMxM7Zo0/CG88TGUQRLAwliDNrg18RgXZ+F0AEZ3P"
+ "gMGfmo8zoSD1/dp8u+6+LOJ8fmBu4Q9iATtHdJ5/fsv22tl0A0IqMil3sfIUuWjiej/J8rnA"
+ "RLuwuanRmhT5FzYKkXLpa07Re4N3nsFdDMTfRhX49364900qt3L9rUtSlNfXjN2kBK8GK/Br"
+ "Vjno3Ggn+jdwYgHzDH+gsmwxWqjxrp4LBwieLnMRVR2uoowOkFPpE2u1QbTbOgtuZ9zZiI8y"
+ "oUImfFINSkGuY2aVxIxVF3QS+k2XYwZEdvp9F/6I2JI0c8SiY/xE/LHOr1w2w1pvL4QORnBy"
+ "AXMP/0GGrNkRoZlJpSO/sGQolTTFskL1WB5EIhNDGR+PhiI+ktVoNMFmhAUZXHraFwU9GYyK"
+ "o74xYTjSQDfi6TCF3jUgy0Yt5zeoHeKcKMD8wn94c1YtoBjSOkFxCDICipTEDBN1CVOJ9MM2"
+ "DDkRbMLgEKlPsHHaJgeb9FoKM0MtPQCpx6KuJnqDNnboOWJ763ZyPL4OMEMAX3z8mj2o36tD"
+ "sFNZWfbjt3S20OANuAiUgCzUfL2LgjkEwnLvYpvQviIeL5ZxPiEwmUcvvtqDpJYYQH9gncU/"
+ "MI3WSaPwzf1xrZuQmWOe+IA5m8yKStwzKodp0aNyszGpXgtU0qT1roW4bgfnYwNT8v/gTtYt"
+ "FHjaF0qOzk7as29uhYZ3aYoS3xMbfYqGZTY5u757l1tUcfyAQ6+uhYKgkPGdVYaCr473qx63"
+ "cDp/pRwdqmKnIttax4WZ4aHz3z2z8p3pWUk5TipgUv4fFoO92xkLux3yHTknFDDcglAImQg2"
+ "fHTsQzZMwafWylKZveZBxtxMUBdTCrR+u268F++0NefjAxNw1GLcaXoZKAHIXjksxNM6lmUk"
+ "iFh2SR0ct7cOzL4sVlYacpb70GW0QQuo9jw8JtH8sK6KVGuvgHpJFHJ9V8KpcV2rAT58Qerz"
+ "KuZpB54ttZ4JERDnNSoiYzx3Qjl5gMv2Hy7HikVnVUYFc6FJM6YQ8+lEaAKTMIVKwULGPRWj"
+ "pFNeFORgY5it5mXGXEwwPRI+86sGDOL9pB++99Y+zjMHHWCa/R9soc0/bSFpISGes4VFYxS6"
+ "NRcK8jiKe7CZNCWddBgStAr1BaK135wLAonlgiCkQeW1voVDh8kI0KbTxul5ZZZJnCzAhPoP"
+ "FoJdrXD8ewV+zrfjJahOQc2bPY/4xHaeTQoxxIs27ooyERaS6bosmBhhlq2a90Ztt/y1Txry"
+ "fV+SW5xvftcpf7+1bIvbzFGD3poO8QM7dR1Wh6Y7czIBT/Gl55gsarpTAIn0om1XiKbQIb/A"
+ "CbB2GEaQIEv2C2SJHWMAZP4FAiP4zrDi8hpd6EXYh9icrkCUn3JnH4z0hTiGJTM8o0GgNILK"
+ "rV898NucIMAkWoz1ozwH8u6my+8dILDf+oXjkQPsWrSm0jYrcjaiESRiqtbEWqduS59gcNe6"
+ "6vdRK4d2kBErpVSONsnznNOL3fY9c4WiiUuSxpWByvA2BccnwzGBydi2Izus8p72nXoROrMX"
+ "EuWtvcp/aqqUA08XmITPxrFlc3gBUR0dgPbewkHz7vnDLQjNWiSbtfcw6tgyV9QhiitFW7Qp"
+ "Ac5rEXz01UFz3oBnm7sqJo8/UAcfHV/9PN9G5C78W2HdOmJLapeAjVdkC9EN/clsO95ru6i2"
+ "6kWPTwWmgX2S6K9WistrVoLP8WzZsKe6O5iTFZgZmF1K/Bxrd42B/W+xsjMwz7+XnUTi6Gf8"
+ "zOrhylcHQMoavvIBTfhUsq0pzTxtc9S1feHW6umSjav40ttJ3avd35Rq+w5m8b7YfQ2bTd3g"
+ "G3RY9tHWbw89Rov2U9HXqs/gSqRudinuFnsi3OGjNwFLeoPghARmHf5DZ8fGhcJGx8LZkIrx"
+ "8Dt4+qBXjLq7WkA3OvIrLm03N9ooCrWXgRVLO5R35GibmacVOkS4kCfrPnf4VNKjgx/eHnc7"
+ "aLzqecxnsYZVRQaVV37sLpd606cvX1L7o5oTDZh/EFtEq4hoTvotNLbd1Fr0D1OJBdRxJAQi"
+ "BB1HgsajUQHF8tCTPyhU1sdUniSJAQYIFU0eAIOmEk3D5NzFsjgfGphrmH3o+fOEswd+EABZ"
+ "ua2LWdisa4ijE1GlrXBVaYtArUOU+F3bslMzuuWH90mikfc+9Ru3663RX8Yfb6OcpFKgGwit"
+ "kj5WijQX73f9lhM5jW4+HXa/p2WXwrOBre2mQ6ejP0W7DPJMrtYWL6hmqnASAvMRmMUQQr13"
+ "7gugCOnFPv8vQLNCiEXXmeSkYm67Ah3DBC4/1bM0XGkm/pg9lGpqFFp1PTFMDqoRqb9S+0KZ"
+ "VtbNwJU7ZuAahYNqQfvf2r98/Ob2qupnz1uJ7V3lzaJRBl6hVRdqECLKN96DNw3YoQ4Fojiz"
+ "aV3gXQ7/LVAeeGDMx3qvEujZgjLGZocHuxzGybcMxqGVI7imajiP+PFE7ntTSScuXM7acFEx"
+ "SeZ5dYmu/Ov7fcuP8lphcKZxA644t/X6hF2O0U5jWlX0rxtc8g51rDJY9vleOASriMmrT8Q6"
+ "n/Ko0B+mreZkBd7xAJCVHSj3/KMlh3tWFFJpeAmB7TrXk63zHdCeMzzowhXPNGbS2hszVjc4"
+ "uGGe8ucWBFudsdnSciclzQJdZTOIxXuJOmi1x2qP39q4ZiThJyphiIfotu+qoLOUDhfCUPSx"
+ "RXOE9oYTtRPDMAP4M7Kc7onkDZzIwPyK+KI2GB9wC/0tZHYNyJ0dW6KnsBD4DLnUO4KGaw6k"
+ "UJFJzzCSM2BWkPH18rOoh1H2wnb2Ut5PYkxI4xA89MMV0EhiybaZV6CmUcf21DgJpg4lyFwG"
+ "ZU+gfjNrz4ZUvSXWE0G8Ajqd6+W3KnECAm+EWD7fMzAY6vJbgOxqkP/C0QRrTetnTeaeEscL"
+ "PFwRIruPtYx5HV99SmB9Jaqd4JQxqJeoYKNYYIlYphIfADvBqFA5KZR768zbp1efvqqOZ0qO"
+ "UyroUz27c57HnDoeZv/oaFfpo0jTsr1lK5371ety5beUvU/rlrrx1t7D7hBWjINXD5ixEV2M"
+ "u2vsHHQAiYXholgwY+mgX8gE6BssAuG3cNbuh8ntjQ5T8+r9qs3VsS2DTIIJIKQRTKYNCPSZ"
+ "adChcieUUzP0/ufdmWzNcP3bMdLs+ZiCSHb6AD79zUNfRY06+DGRhrc6koeu3fUu13EdUdPl"
+ "Mz34Mdnx7MWgnJ4rB+ouKulqvxeLv5828iTwS3vhtVvJ5zZGBWK7P0+ubhPg2WoQ+fOdA2cZ"
+ "XA94i+Z8bIm3D3sASC2CFgznw6o5w+lYNBtb+FJpPqgGr0TXODRetTAnmJEU82Yke1gNpigg"
+ "UeRpvau3EyO/Ey2b0FZwfa92xiH8I7Up3utJpk73crMnGh+W4GXNnMJJIi7voiP20BUfv7t4"
+ "e7PqyHiY6A/NYtIq8EdBH3dBh03fOKGBOZfZ6KIyB92+/FP+b0Gzs1Vz9mZ0Yh6mxxqL3Ffw"
+ "lFiBf+PNwIEb9EsYVB6Rp7gxKrGkAhyn3ovBYHLyMdAcjAlhTGuKCW6MLh2IjgKd7zIoyr64"
+ "hNLrAfMyEosx5CxO9vdOodka6Lug9A8XcqFD861iJxXE+HMJXCHrMdGkT36nRVrvXH/96Zy3"
+ "lIN7o4+mXYR2N5l6MtvipMRpss176y82QXtKzZj1QgfdAq0onzPfxn2SfPZVtbDc4s4GTOwD"
+ "DbcQCA966+Oa6yvXcpIC8zR/IAdsBdzGPh7UEqyb9TR71HavfLo+8Eig+jLXgGNFDLiGs0VV"
+ "quXKlI3TXRn7FaoyNTCwtLyjTjFl3NOy/sh9sNO1b9fz6OW43LH9tU9SKVVS/NipqbXGoqFP"
+ "H1xraup591Ny79hPri0telY+pYMvOUGB2Zo/AGUXgSz+KpA2d9IogjkYTHSpFiFWCwv5hVyR"
+ "MIMMQzBNGaYyXFgGrDbNpkIi6QSagUVcVxjDuuVgqUxq6RQRdFTSaiX/MxiGk+N/blnY+r37"
+ "H02aEBIrrPiDcml+UQ1eOLHkBi5VZwtt5hjF72qpz32cKnn9suyYTRlamDxcwQblbXnHiV22"
+ "WO/SzWMB+1oN9B8nbqdUkYeUkxweTAw+ew+3lPIuyi/bWPvj6uTO8B3N+xzX/HDa9EFP7AmE"
+ "Exi4YZlvcaEOrb0ESM+3/K3FZTaZxfKPYsBPS/dqe2PSnDDUrJjqt0pWyw99rqzy3tvZqit0"
+ "FM/Pgz9fJxcxWXs+Z7Tkh1/3Ta3Gj6Ljqgp0981PQnzfVQVd2N6Du9RlUAXOVdYyrTy6XZ4T"
+ "D7hd+cMWwIUWkfgHl1jrKRJPr7nDxStVCk1vhO8Q3RUqYkWSPykbkEuxjJow6pRbVkgI1tqr"
+ "baiw1qrPLZQvbu2ow6T1VPWJ4p6qk9VVjT+3vrjr7vlNJRK0XV1P51DkTAUHmD5wXzKv7c5r"
+ "ow4B8iXs9j/GQ5YvAbN9CaEdTmeCBpk9dR2OffAbh6mxYBldAivTvGxjqOzgwHea84mBu5H5"
+ "Ctj3KN/fS7vZbsST7UYuoSkkEfOuqljSOq6znRJYyy1VeUYiYndi5LhzTKVPSCkWKK6BLjOG"
+ "yZjan5M7fC6+cOK56Lvbm8oahS4KpIjti77jfOZ0lbWKF2rfz/afA0iba8/K44lnH4dASGbm"
+ "x2VfvDrICQnMlvxBbYhtSxbSdHd2mQ9L1xbiJ/CZa5FSQi434lNJJ+pSgmxQQo3L1c68HuJr"
+ "M5W/znXoUKllDF+w6OhaBZdT2x+L1uy/+Sy97GmmQYLR2IX+rQ1MO+Z0iJ+70NtigzPvDTMj"
+ "eG5wVk/0gV8pmX+jcl9dUwLkQdgBo41d6ZvtTfLnykUvs3Ox059Er4uJ8zqleRoqrmJ/ayCB"
+ "b6/KikHEGlxH0rCW1V6JR13JPrzLQhV2UnwC8Xc/CurcPz7ccxQiqUlBRxt+47mA3EzozDA6"
+ "zIkH3Jv8Yc66iy0Atax4iODpFBKUfkay8tS5kKvFpxD4lVTCvKQYuW/Hke+3vXyX6Ru/KxYC"
+ "O8u3Lqv3HEYoxpwm3S9WW+a8bLn/EQt53gbNMJjDBsFL0Sczbj9GtvmUe95uFShceaT5i8/g"
+ "Y66xJp7OK27Ot0xvc7bF6AMvugB8Kdl65/GvFmnsnXyfuMuk1kB4VkedsgQV7NjOw5eB3+r9"
+ "atMbnyHcy2UK8VRtiRHdvToxNX6xb54/LPR/iMXeDNdqE2qRJTzMOhgTdlnb/uGlH30putND"
+ "VR5Z8stqX53Q2MWJCNyazCPCwo15ACncQl3Jkt3OtFb+bJxATiyfmDnev4m7PQONd7YIx/Dh"
+ "Tw836d+TSjvR1mK5NjPWYivkGZdhyLWpZI+EKcIJR+S3LsHn/DkM1WMHrzRfcit8+Laq9MIu"
+ "aSdokVoYpB+qR1vDvWIbJyUw4/IHV9bYQufNFjotQda+jadvx7g5VFOSrWtzweNer5WlazOp"
+ "rh6Q5AANj8ob0wUXY15kQjVLennEfcsNB215W1u4nCazblRLE095C/m86Ji2U5yestD/BTXg"
+ "u9hVWv6wWxda1weTy3XL/OkvaMSJCcyuCCwupsEvtd9rRmHLnjpb9jRrxxJj6TBIPQyaAEG8"
+ "A+G5sn9CTKUj0BAhs6G6TqJgAlMIlPkDl3m+5+wpzgcGZkD+wF+xVc/9H/7Ksmu54rHnm+nK"
+ "lVnCKKuiYAGr/GIc89fRJ8dPfXf3EZLMtMsvzTDRcTu34pxJRLDUxy+dW+87QjZvhZ1UOMl9"
+ "0+NbTM69zfU97vjzGaufM9zrjLNvan/6Wpk6wQFoAMyIzEaQ+ZzUGHdTHZDiefx1MyZuVtZX"
+ "nSSb2eULaD9/Tj57QCgtzUKhEyyH3vpFtNbWH2ZV1ItabswvcGRD28iRCx4vtK+POHXuPBIf"
+ "xBt+/6WKoprGt9cPussOaYCfpehxX4me6swnNpwi8VpYCTpxIgJzLtKLot6Yd/73Dlb/1e6+"
+ "oWPx5hbeE+JChjwhI/Rt1pW5WLXCN67DtcusE6QPbvDQtuHDW9nbTR95RkVnr1pp/jZSAlm7"
+ "wnrbhoYdF5yfqSKg3PvqYxD91hDlVX2m9n5am069k4a+Bnf07eh8F/5FhJMT+JUSgHuVnZdv"
+ "YN8ynKupcHdlZjpY8WrEby7hdjtpH8QbJ9gpc3/tXtJYYZWMZOays862PR34u04V7Vk+leFV"
+ "DHQg5MfamfGXakZM9azj8Pf5su99XdrIKqs3G9ZhgmsCOcGA100ASsC/0nC9jlre+tk0/DAk"
+ "ee0y0xGLHa2ivbt6eo8NCkX1yPnp2AyPYTCYBL8TYxauhUcYdkU2bw+2VgUfUCYqv4hTsz0N"
+ "USuN+SjUtMJwg6wCs98HFrEp8KPGx8/jud8qhW8yQaRVhll9YyZpnKDAb50AlHN2Gm67sFOb"
+ "ywRoOJacm2vMtgBob57N5eSU4hupr3FZmncHY1EiU9evwQcIe/2mbMgGElFbP658f7fzqFKF"
+ "Z8OOkAP2a3RUTPXN70G+DDM5YYB5k1krpjZ/Tqek0QnIm+xdgHm4eHygKkG1RPlDsWU5122t"
+ "8mO5WoflzDrdvmBfUKuEkVUkeoOZdE6SgHFml2gCqVe+Winrmugvxxgj/j5ugYqRrWarVxt/"
+ "sWlItLgcIC/80V6hkOui2dHdPS+Y6eZDa/tHy2becW32IEd+Vj5gz0kN/IAIYHGMbVdC2IXN"
+ "2VtSKIijtUh2NKQcLlTUmNQ468iy5AfvTXcdx/ttDS7/LIg2bVDWSEp3OIVJDtNBheorodUu"
+ "JWFXlVkdOP+yU6tLGQUbeEPs9yuoSLU/o4i8YVD5IOnRjcPCz541Hmj8caFQSeekmuCBrzxj"
+ "4d56GfdyJDi5gRsYgCMT2AbG6+8dBLOrrYrfcYGcXbopyXWUtdo2J+TrerOkUB16x5hYeTMb"
+ "pwALL7JKZRdhVSi/3clUB0cbkQtYwWGDp8Qblw50Fp0aHNfbuWVL7PFwb3LZgUq7B8Xv3sDE"
+ "fMklyXcstTkp/+flFrZ/MV3sN2Ji64nfoxHIHKaWLqFwEEKagiUxYwMV0AycSGxvh2GDlNyb"
+ "A+SB2YpaQyclDkso1ZqKpsFAQih7eI++eQgnBDBP8x8u8vn966q9+fy5XtqLuA3aKdY2J7iC"
+ "GHCzwn17h/aYC8mNNiQffefa7qDrYGEkIhR62MIWDxJ4biCgfXZnxte+DXl5D9/pTTQO37z1"
+ "pr/XIfML8R0SXr1e+NwvuZEBgaqnTideMQQHOVgNgdsbgBGVbW/c/7Ytcah4+hnH2Vp1hVg8"
+ "L0a8frWDGyUr5Qu8e1PU46rq8Ba5EJbhsRtocg7MLgmzHzlmb2x37E3GxRTricFz+gEIefOZ"
+ "2uMSaz7KL+vPbQrrJHbzd98zyu24GcLZfGsIvCyjOq+Ny3rcAZmbhdseu1tmE16WNtoaBfZB"
+ "EWFceQVnnVKUIoZLPd24vXVltx6cMBWSTO5Wi+Zu5A+59XIKJWsd6J51/17YnZOs34lsTXhP"
+ "e97yDvbxiIrkQ9KMGq8/mhMKmJP5D6fsSwWTEUx8l1eDl5ValqmjLDEpSb5D73uNQBa0GZ9V"
+ "e9CBem574l3PFq9poYuR/G+Kj9aSAgZRGnawPUb3UzYEWfLbZ7s8XnUsbPVhlTJuJWet5wIe"
+ "cO9DqEdenAMFDIE3sgAsNrHVnn0c5EemcacSXTE51JxpGBJ1n3AjOsOTwayFJu3CzBBBdCKC"
+ "iSQFQVAG8p6+3DOwulHUVOL6dmSPChMGMjtsHdLb1lbHiQG8tWU+g1AYiT8NSOcXCoMbUudM"
+ "CzdRSCjtrBornqjzoCXai3y47cThE1aqZkFpB34QdkUEkjLzj2XkDryP6Ya0uY5XTVxT9u8e"
+ "nWEk/IjfWoxtns4sO1IBiszH9rb6H7i+tzmwcM9Hnoj7+m+VNo9wc0ICMzNii6+ZxNDwJ0Cy"
+ "7rk43YKXQmK9ZpqyOdK9lLPwW9tw5WFhYV68CeJqV+slpKr9eh8O5gVfD7L1rGvpPUm3Dcl8"
+ "BynzsIbcKtrfM7l+n+KNxJVpJ+nrrjs0KFnmvEDyNEdK+GQ3rqj3QN4yTmvWaOZUN0Pg92X/"
+ "sGlsy9+uiuBEQF06Itk3wtS8QnTiSzENloEQe/6hH4klZRGvX2dsMNxkODZacc7rQqzcQ7uV"
+ "KRaF2dbK05gLZ3r8L+ruEb7xMK0g/aLBz7KfIT+/uznYZEhJu5kkZvRKaHLiAbMo/6Hmvtg4"
+ "VsuMLaBOwyAQuI7CGBg3czj9WXJD71ezcSyUPwEKhRISwKhfIAgMpLJa3/HrA3nOK7+GwBzH"
+ "H1xEZ4s1eTF55W2YzX00RQxDeD81aKze8pzPoFBoeDQmdhyDaOZBQsVmCu2TYjWlsC7K/LF2"
+ "61ruoq4I21VCz4+s3Xw75pxl6K59W9I2NfY1GFro3/kS+5mOpX+zkcqW2vrWuA409HKJcA/8"
+ "lGc+ELaHwRiARHrhvmxo+ny4X1tocwKNUbpELfezyi9OmqhebW4eVD5M/JCmqOzVadhL3cnX"
+ "XndLv1iTR33Ioah89GFxUG5O2aMNHip91R/eTNXe2V85Pdoxw1CY6YJUNZi0H6gxGeegWwHc"
+ "ggCk+5dCh/5lt1Svg7dy1/rii7aJWAWOcDW6EWwcHasHw7GfBLfIPG89nkw6j/jwJn/P/d38"
+ "v4ri7V7bgKwkNeKXvcgc+LhWGfxqTS+3yeqYAh7OS6YrgNsOgN1+bIVmTzR6UIVm2Q7uzm0a"
+ "uGJuuzJ1HU9/xPJSnjZCoKu43F7E1r1n7kJsRSwUI8paFS7dYxKowmonP+z3HL167h69GHLi"
+ "/FDR077OYf2xCTKvX9uFPW+U2wzkLbuMpxmgfnlb43CNn+84IYEXVP7w+H/nwrvXXiZQR8Dw"
+ "0A9lntKYPU72ygzMJ6sfzkK7nSudaba3ODL4YU3iz7jNVo0t3MifKH/vzqthyCfn78ZUOpdO"
+ "iYY3Gvf5lyOuW3o5Dhb+8FxvPHlxrOx1Rkmw17bjO2b8Z0KjLadAG3uMlYKC8mM5aYHZkT9w"
+ "kmw74vBXbkOsB0OtZiOhImKYSfbxm6pv75xKlEmalEhnZmxnIOuwpF5ZKrbHfIzKREWhkqQK"
+ "KiR4dYoGownt6VjmWyYSVOzvyHVle/8eTh5gvkR00Ze0vDTJA+RL3P86G5n1JSwTWQdPrXNV"
+ "VTVV89VvhWdryLd4W+7ICE37eDt1k+illULc6L08SdAbzUWp7gr0UStGw6u27d+lv6Tvh+UF"
+ "1waAjvLt2fbtmHDVkdEz9K88x7x0LO2/Ru7nBATmSWZT7vlSwxrxs7sAeZJ9fxVY6kgisZ4s"
+ "l3wdtM6KN7c/z7X92XBaRpZ8Sll8Rv7kxfPMqAwL+Js6u+pSFCzAwlM/vF5fQsBrI1KxKfHn"
+ "2Uuy95YfHLfVU4y3yKxeEVtZ1D3zEP5wIDx0X8IDx61KVyXunC1Xet4+TD+1Wr3gtc5Ml9S3"
+ "9qA3JU8q0jnhgXmVP+gCZ6/u+sVOzTFkPREqlYBFaCOQmAiMIKGUoJs/hCRNQewgkzBaDuyy"
+ "bAKS1Gg6gRBDIBDR+RXQJAEMLR+cYvqeFGkCGsfm32VSUmaYTAgIsQ+nIriPh/NEbwUwk4IE"
+ "sa+XofZfjAJkUjQXGwOaxs4k07HC9UjlBIgDQsABgSD0gMQmIaYIx6/ImBxHtP44MwoUdFEy"
+ "5k1ZOWd9fQUwiyK2+Mj3aaCtgCyK89+uKc6ekexSLPPktvIk8B8iSgbUwbNFiLUqg/ZrvmUd"
+ "GgrRTrNQWgc/Ag5rU5q5s+3rWahVMmKr7dHL6Y/0z3/aa2jp8MpfnZnKAN2jKb40nLTo48QC"
+ "fuVnfsLEt1JfYCOANrMPHueuUMTTmZ1FCoZJMNgX16CylEMhcdL78Wma6zLGTIXMZD/llTSl"
+ "l63bhC9ea1Jsu7Zv8H1/cdKng8Hb88ONIGtaTYNf+syoVV5r03ptedVjmmfDfY2EqQ1vPTno"
+ "tAjAbMks3vx1M9CpR78A2RLbxXBPS6AzwRIJTAHHdlAtk8JAijBBg0jHKVAcP45Bg0kkQHH9"
+ "GbR6Go1WR0FIUhggx1EmRDaBSQoNnILE7Qbd1lDi3+y3a/MSNMAv9eDmaExGbwIb9bOefXlu"
+ "YbGwo2UKIiEZqcqugWWp3QKjXeYkD0ujCEZExnB8ehfecLJ5TF02NCd+g0PK8MbC7w2hdohA"
+ "fLipzg1v21d3L734el64QcQg9IFyeAbshcESYMA9yPwulNwrAGzs4AKYesecivF0HeG3syIL"
+ "NfigPXUrL9fCVZ1P9K5JedrbTfx+Fp+X2LTaM3O1V+VJTP8pSZqes4P+iEpgy4+ZNByi6uGy"
+ "sttm6ek/qqtv3+uDy/ePtRskLwEGvPoBMGqw7YbNYvWj1JMEE3CcYoL5UV9IZYh6JgTnD2nQ"
+ "QzClZghT20FeTUQEk5QziazHYnyZ9WMQiS9MTwYlLroF8riFCQY11WiNeSV8oiwBA3x66fz2"
+ "69J4+XslVbYa2f2jLpywkqVGYQgshoGRJZRSx8GoY2QvQ3IsDenfOzDRwgBBhLlfEAi6PbUE"
+ "Ye6nIC3YetmriUi/4zKdL6NBfRJyxkV2rZNL4ADve51fG0WuXWRAzsLpHz0X3LWNYDklR95C"
+ "DS7+O7rHB4VMpmjaffdtuIPp6gckDA2abL3Plzu5nldwKk+lafI7T+tqBe3TfSK8oU2vZtDA"
+ "or4nuIl/TZf8cd8CkyWmMhGAd5IAXCR2eeMfg3FYr5JzSnbPeocY7c11fr7CsyM8BYkJKYO2"
+ "x9TOjFQNrdcQe0AIyuC6LnKhG6ob/XzXr0cPzOpCdl2y0LyUV0Q82C+ycga0r0x2PdXDgLAE"
+ "FTDD8Af30dmGgbBohE7Ro3Xqo60SolMRRIyEIEFbl6BNoLRCCT+w+l/PoC/yiHOp+kFBjiMy"
+ "d6Ikng4t8dDAz1EAbrB/WYaFO5hQurCwnVWg0KBfQlfg7EpE0CeL0hqfugu+UmmyfZufqbjO"
+ "g5oFcYtS7hu8xR1lczhidQTa2IHuesv26W1b96/mNTVmUaDIVMXtbq1uS4kqMM8gshitxy1v"
+ "/V4bD1tUHdkVtNlqPMi8S1mDBk9/A08lDXphxKkCkcOkyxu6eofzIhmOStka6LohBuqA33V1"
+ "7kOZJbcEqq6k5thxo8Y8yv3bwBOa9/r8bwtjeowtEm7Ic9Z3tQDOGZ29xD1vtcuObJ8ApK1r"
+ "Fy1pBIUOjoXUg3EBlCEI4uNH0hSW+qwghyqTdIKaA06QGahgUiH1TCQCwYwm15KCZpCCiHby"
+ "TdRdHAPkx4Tl74seZlnjV6qaJTwT7l+WYALmF2YXSn6OKbyrAQVIVjew78cWsGQVY0E/jitN"
+ "rfNL7TwXc9OvEq6AQ3NHEi9cYuwdbPCWSbbtq9S9oPh+Z99EVHkE+Iji3iR1pcHI3qkBlcO0"
+ "s5+zY8oSbmadTBt5zq+4VsPvnJXtySXIgI//mt+C4v4HfgLS1QVfZ9k0axhQ5k7h2XMtBKl4"
+ "V/7ZrFfVUcOEJ67mtA/q5WmmhsvkrizFtdSI47EYDaGHe4X63Rt2bzmqvKNNqBhDXMmvX2Tt"
+ "m8TZEKEFcOio6CKNbGI/BZCwbmIrkRbf3K1STbFz17/hZTu0M+p2qW5VfInbllK60pf/UHMd"
+ "od5rvaRU/NH27jy9QL0yknZNXnlV5NsPv5rCnaAfrWJHJjvK7dfJSknHadh3WdV4NDcvI1je"
+ "Ob9+CTrgw8Dm6YRPXGkGpLOufxXc5/o9sE6OvnEEySLFwpbNso0KjutwmeYWTj53B/3zHJBr"
+ "GvUkctWcdg4GJPfivm9jnvglv9Eyr9ESnSfx/uBO34ocnTXTz6T5r5VEMSCNtVp5qbZoztMt"
+ "LYBTRkUXX7Ec4WsagOR2NTtsvJqKpSPrkbjLZAaNXDtGMY2hU2O562uxQoi0aEPMSaYp4Us0"
+ "FBpmVCEjYKAwFI0gXxecAnlB4dBnX8BJpt9nvKfAoPMoDWUdIo3zkowWwHmi6MVAuLm4bxyQ"
+ "1C5nE3m+oo2R7lELwALMAugt66EvBoQpcgDZkAa6EQsGk/zzMSCdEALK+GpG8xLPC3z01/wK"
+ "RD68sRGQym78x+gvi67D64p9uF1afbm1QMv78RJy2/gUg1UzvD7c+SKzR+iAsMA+/sdRNjIT"
+ "lObBp+fNdI8k7ZzJk1BY5R3e5Hm+RE6jSl2TvLdZ+9M0aH2JttOjYDjnPWwtgMND0YtsmQzk"
+ "+t9iY2fni7urlrUWlDtUOsgbAkHoY8lkBhOURCTXzg4VMtAnTEEuMuvhBaYMJhWaJG3TgdRF"
+ "iFyfnQ94BwUqg/motHwiTgiAuj31tkNsTS4sQQTMO8y+L/P29PELQWCd+faLOcQAjJUDibLy"
+ "n/wccAwTPI7F6JeQGeAGLF8+w5ynPhYmikBSv5riA6BeTTAxxLt8cBKWYEOYYpLBDaTr0flk"
+ "UNtOPQ0bO7ElppMDnBOKWgxujKtiDYBsg/FfNw1AODioVkXkWCrtYmoTPnUAWpJ8VOvO4KHt"
+ "fLxlvOdcawIx+bjmNfaV7ybp8Y3G5q2mk1ya32S2OAZvqFmC4P+bSdj0dzfHEp+1/pR17xzT"
+ "YC9ieuFhpdbIuFCCtFG1T1Ze6+CxMfVBi0rdfTa9O3OkNgSo1u/wSdBtfDhDe4kLcT0oeOdc"
+ "4X6XaxeJOd+H1jpPkDVpJ3R3xCaF3FqCDphR+IP4zJbWhZZz9ftzd6us6EMVCiEiTWbycnDF"
+ "5UfXl1oJi4E2dt2Y4mlQ+TTk+NbCV7FLOKihtvc6uu1tyRnc+TerVl3trWF2fAs3kTxUeZW0"
+ "MXfk5rTLx7B+o3evek5EWjt5mEeCHLbovL2dffLmEpzAR4f+ochu/IfIzn2zQTlXro+oVZlF"
+ "5k3ttsMkW2rg6TibzNDGLRNSB1QkS1orGjIc8XpyukaDPdHZa1VFK/cFdfGZy2wlSjxIDlTw"
+ "3xJjMW0cAUGiNE4Zgu8uMZId4DTRPzjS/ldK680gMmH1xCYsAos5SDAil7CkiAxuSsMqYk5G"
+ "2xBKmeDBaFx7Le2wsA5JK2zfd6I0OTiH+kbLKIPsRwZ5JhljKWEzoLrodiJo76VlTOsLVpwT"
+ "1rT+YJIowNj+74P6CNwrWiclDgkax7r13qPSsULQcc9BjH8IIvtXLUL3TAEVLEGJwGIkQXe3"
+ "ab4y3JY8ssRTA59FNv/URwVe7QCktn8f8TLXIgJNODYQ6rX82F6ydwp1d6qMm19gRXr+QF3s"
+ "NtewkqQjyWYT0Ps+KpdF9V+omzwoUjR7U98iLxl0szBs5tEhbXia2GBpx5uNsmcSGqLL7/yC"
+ "0o4ZeOEZzseXQARuKOZfpaktTb/3PTb/mn3HEt0oCp2VB+JDQEP1SAxmJho0iMWNUmhYCqMW"
+ "nBCdaoMpGYqGoIiUC7Hvf0EaSfrETrWw/BOoAVAT7oUs5et1LAgkU6t/xp+/pGwJIODzyf5b"
+ "arutvYAVA1lpoPWyDPcRrMDGVps+vEyBkCne2g6Fu3cqsnlFsh1uy9bCr57Trr72XOGEvtUX"
+ "TF9hYS9r+mKKA4WLviROPS5/l6txR1ZfFHzgSfESTMCnj88zbXVCtANSLdfF4h0vbbblEaum"
+ "dj2maEWKe1PLMjOxO/6BZxvRwSlZg7HN4xHW1+rsm2x0z59uNHavOB5cFSd0yiBnIgt+Msk8"
+ "1vs68fB+R9/baqEzQj0G6m2wFUEazuAaoyWyW4CzRv8gNLCzW/ZAuYjZOZ6CgqBxJgzXRxtj"
+ "yqGwcYO+eN/E2enCsLqJueHCicbc0SD6T2gCzDFqgMhlYBakg8IyX4FAVWcMToa7ungtQfI/"
+ "Nxcco2aaGVRWegRLwCJ4SN2zBRZQPRhnwoilFkwiRZixnYlUxgA2fwoJzl8JEYkCJeUS4YQb"
+ "kNfPqO8nTRlErtiGHOpPqpiWLqhjD+gFpHsLSOQnK7eRXfbt9t7P00sQAjMYfxAQ/yW8G9Kq"
+ "5rJbj+yYLFEjyaKMZbu0Uqzzi0lyKqeUy0+r9Vs3B6+UeSr6vKniZFC4goD+vhlbna/XzXlC"
+ "ucSzMjvWRQsc+qxUcvXUuubEW7u6w76Lm2ur7bzgFmK8BBswUyGy+JJVp4nfBCS8fx0/MyD1"
+ "xAQkIhrDx5JeDEt8UWRDUAOMSSBITzXTOhENH5noHBl+qxaD/BIe+hApioHljm2PGjMH+cPg"
+ "0aDnPyB+mi0zzB9EEGiViG7/RwqDtAQYMEfxBxGRrb26bDDHaAqdMg1OgOJ8KV6oX2AUk1Qf"
+ "nUHQ14rCnJuBrOo0gcKgUIIVBYaAgX6FGfYK+bn4L/HgwEzDH4Q9tvxu+uctr67DeEysf90y"
+ "CSX0ITmn0qYy0zYRJ0FrXfKInoZ/lPbsVb0kcGIx9E69xnFG/oNL2WPlSdjCNJNd1u/lLznp"
+ "Bw0d+6LwK4Khkiwsy/vO8OKxfU5LWCKAg07/IFr8q7C8rW1h6uz2o92pg3wBw3C0+K9nii63"
+ "rq1H1WhiHMfqRjSiFKKOpVecdP6e8Bh/Ap/1tHmdqHzvpdu8cpZR9utQdH3aN6aj4MsyD1Pm"
+ "zlf3vZeA+p/XJ9jq68BW3yoBiiM8nv6qFFPU/6DbyY3qhKntruNG3ml6kxUlkgizzKhYmXqm"
+ "XsL1CuFZZNWkdVDdwdogc+NLD/ab7HopMA2ftjskHh6Ah4kEFF5eAgi4nQAIxJZeZ/Zt0dkb"
+ "eAjLLvGzga12zxAoSGmW9Tg8WxVF31xcbHhs6qnjI/ULQ2YZv94/b36b835knSV1XS4+0UdN"
+ "yvoYdtO1r/zBuUJEVMODjuYpnvpryqmjhamcc4u0AE4//QMutuoufKHm6vqFfvzPs9fSWny5"
+ "t5VtP1LKerHg3vc9ArupeMalgxEfLE/sid7xSKkmSOl0xWuJcK+G2Hd29x+feZ6KTaWZPGvT"
+ "GeacB6YFcNIpajFM3PS9/RBQfFNjG4ixMwUFp6m1simSoxImvTpTlKLI9XHWfLa1sLAEqBXU"
+ "ZyxWAgT/oEjZEmlbtcQDA/+KEoB/e7bnZo8FTptPXzVnS8NpAq6t7k/G4KqqqGHw2dpuvc/R"
+ "MsYrRFXivA8hjDRTIwnXUQG6etGVmGdfr/evCC/E19zsN5C5lRrUemYK8vqY3gN0Ta7vElDA"
+ "aw8Ay/rscLb4Da3Rs7WuWYkZiHacgsWJ4hjWXNzREWOxdDkhE0Tjx0C3WhoNB26QJveBFJGg"
+ "ESapjEm6w8QeJA8eJoK+QSx6znTTdJZAAeYE/kMKsXqxSRQ997URQSIaOmYu434vKUd1WTbH"
+ "wbJQF5OyJyRrTNqizjRZPKij26RzWOQjxavg08ecF71j56vP17vmdsK+HbVKfcDTcFAnYUos"
+ "rGMJIuCnEgB9GzuKrWOfjS30c9mrucZ2YSyHvDD8TyMCSLamDcjeTad+dq4rRpecy/G3sDhU"
+ "kWvYPnmn2jWv4JAyDaRRv07Zg/lde3eMbvpBy1WNQ1+5XpWq75bWtpFaAgt4VeEPD8aMF2dc"
+ "UXglKQwmGBVAGYh17EM0jqKmkHGTUpgZSqkRNxPk+QuagCS4vaKNgeKY5mOmeJNUGsi9Rs/j"
+ "VbSI5BIEwEzAH1R7/jXtY0Na2VzT9UO5AftSkbYybnNzGcn6ArQdYYpCN7aAxAp88v2M1Vdd"
+ "pu8Gx+dgu+UNL+EMTKNa9fd+vr+y+p5EFaWh2nwgu/OLwNh3lfJVNj+EJgPwzNxHUmVtyztl"
+ "NT8scWoEcOjof6hobVqsgk/B6rEJWJJr7Ri1NoaVFnHPpUXWMQXjSJj5FyLKl+w3g6z96Ngu"
+ "2CxA7hM8K+cPXY/oicb4PTDFmTjKgW9BfMThTPDzX0i/X8xmIuhavt7gzdTSf7xa/wfeUnmO";
+
+
+/*---------------------------------------------------------------------*/
+/* Auto-generated deserializer */
+/*---------------------------------------------------------------------*/
+/*!
+ * \brief l_bootnum_gen3()
+ *
+ * \return pixa of labeled digits
+ *
+ * <pre>
+ * Call this way:
+ * PIXA *pixa = l_bootnum_gen3(); (C)
+ * Pixa *pixa = l_bootnum_gen3(); (C++)
+ * </pre>
+ */
+PIXA *
+l_bootnum_gen3(void)
+{
+l_uint8 *data1, *data2;
+l_int32 size1;
+size_t size2;
+PIXA *pixa;
+
+ /* Unencode selected string, uncompress it, and read it */
+ data1 = decodeBase64(l_strdata_0, strlen(l_strdata_0), &size1);
+ data2 = zlibUncompress(data1, size1, &size2);
+ pixa = pixaReadMem(data2, size2);
+ lept_free(data1);
+ lept_free(data2);
+ return pixa;
+}
+
diff --git a/leptonica/src/bootnumgen4.c b/leptonica/src/bootnumgen4.c
new file mode 100644
index 00000000..731884db
--- /dev/null
+++ b/leptonica/src/bootnumgen4.c
@@ -0,0 +1,823 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file bootnumgen4.c
+ * <pre>
+ *
+ * Function for re-generating prog/recog/digits/bootnum4.pa from an
+ * encoded, gzipped and serialized string.
+ *
+ * Call this way:
+ * PIXA *pixa = l_bootnum_gen4(nsamp);
+ * where nsamp is the number of digit templates requested for each
+ * of the 10 digits. nsamp can be anything from 1 to 100.
+
+ * This file was generated using the stringcode utility, in recog_bootnum3.c,
+ * slightly edited, and then merged into a single file. That program
+ * generated a pixa of 100 mosaic'd samples of each digit,
+ * which was copied to recog/digits/bootnum4.pa.
+ *
+ * L_STRCODE *strc;
+ * strc = strcodeCreate(212); // arbitrary integer
+ * strcodeGenerate(strc, "recog/digits/bootnum4.pa", "PIXA");
+ * strcodeFinalize(&strc, ".");
+ *
+ * The two output files, autogen.212.c and autogen.212.h, were
+ * then slightly edited and merged into this file, and the code
+ * to generate the pixa of sample templates was added.
+ *
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*/
+/* Serialized string */
+/*---------------------------------------------------------------------*/
+static const char *l_bootnum4 =
+ "eJy8uFVQHUzQrou7u7OAhbu7LNzdJbi7uy3cPbi7S/AgwSG4BAvuENyd8+39n3O169zuuemu"
+ "qa6prpmamfd5kVRtfUwB2pZu7rbOTgAOJGVPRzNLN4CzFcDF1gcgDGBnQ0ISd/7/KTFz9rF0"
+ "/6+IDel/VRuwGQkAfNz+Z4YJ4Pv/ZkgxqsoyqEhESBAQEKhyspLq/8VxCAhIJsj/EoiG0AHs"
+ "/wKii6yeO8T/N/LNaDj+C+geUroeEs6OjpZOHhBO/7sZtkc89o7/1mGRkwRp6kbUZzc7HUhj"
+ "ReZW7BaY7JBZY6MsBYWj4qJIbuY+b4nN8BBEA21C0NDmIjI3Sw2xUXBecXPmoA2R0RLhsR2+"
+ "grn5iWzbOogNxV3uUelb/jXW9PstZY3UZN2eKJCI53ncey3fbKvB4mY8MGjjbbtK/cJ/yBrj"
+ "YQjWYfinWa3YqbCtRqSrdzhayuHZOXDMhj+a8MTwJLrpnHBWYV9OvLMRpTHvBchG54Wvf/UN"
+ "zmhbHDhiIZnsy07U9txvfpbmBvqt47cl45WdjUpQRiTPRASu3dWdVUioVXtthUz9bKmFQhM/"
+ "s9XgisnSy0ADehGR13qt62Jp0WEZJwD4sMH294x74Se25iIy6Q0RnRFV+GZbqU7UPlCkotQX"
+ "ZbEK6sZCfwUlWcsUndfry4D0rDFBFM/mH7Hn8o2jgWuHi0HUuvbILf3Ah1bGuwiTiHyixkkR"
+ "8zI7Erfhe5O24UpxYK5fNwuHNEsUWuK2rfA3g9FWm3830Z37gvovmMjr8bTQERsGF04qBhe4"
+ "uOK9IC1au+NvhI0LkEDVgku8f301WnoM83RVvtEiuRmhVrXiaO7KCTNw4YXV7uNbkjqj20Uu"
+ "uNyJfCFs+C4KxsNMn1a4P8KDhzezkEXSM/I1A3x2sG04979sfL8XsDanLrTuJz21E46qnwZ1"
+ "73lfOibw9jYrdQriTrPtKhEnkzgjcSiOlaDvdp8UAB4r8tv5xwe8Ys8uhb77nrhqd9wtL8cA"
+ "gmUfD+fcBqr/zQQPg8Z3MG36Uxd+xoS/7GMPkONLIdgeG+1uysOliu2lF4Tv5L5TyaE8MMGs"
+ "dHLeiBPZfTCzs0omGktVIN+68fbEfjL5eip1GwR05c7it9DqbeOUwejpjG0W6ERZFHXit9TR"
+ "rSQowN1iRfxrUbCfvnUVRAIpysWiWeO1nZm4RY1zT8H+9Yk2RigJxCDNgueGURtPWe9a8VW4"
+ "AEJtEpNeXrahOWn7SvT6HnRk+Cn5Mgi3AJ6KxQ4raKR6rSr2aLX9gw26oayLcUb+2rjEcqq9"
+ "X7GnVIfMZAMc0Fgk+ny+7SvgeqSXuUUeMg9xJ37BvwwNH4g2Vrlrulc4Jlvva/A+H3X49Ptf"
+ "wAm1kYmbGvzhr0PE7vPukNkXLJNOlLip6nZpJSUGRb1CQK3xzgWt1foNI/3IQ+Hj7ANJnREl"
+ "01J0wrvtKsLxVzls4NzyH+xLI3iEEKRWiStDo+FZv43/uPWlji1UM7zlz88AHV6l+FIcGpQY"
+ "yz7iSgZzT/S7TD/nWOBb/MDyLXRIhQqZJZQT7KVBZQHudrOnYe3qpwmx2vemtLGIx4qjppbM"
+ "cVLIRnK7EadKUHomLQG6aLpYCvIEU0nQ5gaqfUrwmx4g2O+VN1UFJyRWaQ+HMGvcaMM2OmcZ"
+ "RQt0Ta3fog0xqGeWjOs4GNSokJNFFTEkKBtoKl0uhplMxkL2QpE9opvLVn2P4iSXw1pkM0Qp"
+ "N1PV9mfHmQSoGL8cDrh7LBsKaFthyEtAXgXq7smG40uLMBrhre31OmOOM0eRrHg53AdVLQWo"
+ "i9X2rQOYIr8joLxlw6chg8ZKMrZOKogbxdZ9GznPusfW9XXzw0A/e1vEUdAto5yMEE5nhrWB"
+ "0OLKYA4tlb9gyTX53/4S4Cx9rgALtN5J6gkdrHA+ihfGPeNo4xjgOu1wPQwe9Mght9pVGgNm"
+ "kOZFpQXA9VbEucBkXEShicjyaeatDqZ3XoBkxqJQlpXxLMc3+Bsv1z/9jcX9AMcaqxhwPahz"
+ "+aekSZAruqTTWFbLRmjrOYFCK+MwVyaMC0JzpJdyjCoF8TrF74NuLjsCHS1+T4sfdPMUl8rw"
+ "9xEhIpiXVmnxGnbYcxbF6j+KXNEVo+UEYGbKWIvMbRLhhm9CuyBIcEUE2xFkBA/M39FbzCyg"
+ "1ZjecfrLIKPgNjjYbDV//VgLaYxWGML+hfqzfxgeud/dfAoi/3g2FO/M65XnKqAV/TwhwrTB"
+ "WqVTXzSBPK7izn1+wQDPF3562TChp1AEL5D8Mk4Es6HKPydZNAlb0TbOtjSzz4hTIQ+n1CtV"
+ "ZDpxvmhoJT4pE22rJbNIqpS11TXMvgoDsW7YSIE1dAtjX1KT5CTHlFy2aL4FS62lR3SIY96t"
+ "R2LqXgIH/B6TKwluhRkadAfdG7RgtjBjzTdGByehMK7hNF4+Qk9cCreoXO1boi784D6t3sbS"
+ "FWcJf29WgGFksFcC4wbu6PfTeO3om/d0VqgJZORPISuFFQ0gXihtUa6TUmX+d6eXUEmaf5OP"
+ "YfdllQhvOrCblnSK3M0lpU0Q05XJsISMV4kdZhdL2HoxtLDOdU6ms22pxyzRTUioO4+n7tUC"
+ "DenPWf8UkiESSCMij/RB7GvqoQyLlDjuFO2vuR7epcKlWcDmJq5SjHzGcdFlh7HZsLlWjNyZ"
+ "fXeVRW4OXg9Or8tyGNJcqVEvndlI4h2/OP2dRds6UhBUqab1C/AnUFI4RLilYiz28UIxSTMC"
+ "PaLU+iybYdoX4kSTVGRuNdZavFoEW4CiapfiE1rLYWt8ihbIclB4o6PWk0EYRs3h15lgBFPt"
+ "o4ChnL8J21xF+Glov2lQIkoROxwYWtb1xF39+4s3G0vOWSCv8BXsYa0Ofs/HXkUbzJ+13yzz"
+ "xG7p17WXhBwWLkt0lTtx8pZ6jXfRn0iFYx8Znt0yHsIr3XL9ZmNhIytpVLvhm0zLFRdFplfy"
+ "F1Fbv5l43GUNzbSi5rJDFvcg6kSJCjq+wIVtAFRKGnv/JD9qRVhKcdo/IWbkQnVStNxM2PKF"
+ "/wNqtLosSYBdQZz+TdYGrzh5GngwSUD9w8EO8UWAIb6adRwPqwLKVXoh2+HZPgfoxEOAMUyF"
+ "K+1NkJYyd8iKZ++SJkD5SmIFTB50sanSYoUelE0KP0Vq6WdYlwloFq8shywfqBuFzj0vDjiI"
+ "xPaOS+VmjrJL/wHdkW1Bjti2ElOJyzvJuWZVo/dB8V1ug9k61pcQxt5Sew+/RPdFlZZVdlS/"
+ "J32qw8KR7ipVCPNBr+Xb/oPZEbap8a/Wt/4YA7kb8W5vWb8zZWHD5GlGt2+kKA62npRv1XV/"
+ "I3/KIJvG7MaSWVgrx2JupBN3aMPxxIhTkZIe5HACKMcwni6yrI6p1/N/ChIQhEDzpjnV8TOW"
+ "nfR2cyglqRVf8KFIaJXL1yBlAe9/VNK62Xopv1KF0hQGrsu47aUY1my7t59knX1I5CU75HRD"
+ "xZL7PvrHfw//KXPYeRCJgrkAdZLSwsU657rL5AssmLhv+GD+q0WXkhC7P8RcoLObNOcrZiy2"
+ "RZmwdi6gT6M4zkt3pZv4mGKlRgQE1rmviV40qVa73Qsr2iS2fWfir0gokW7970Vf01+qsV0T"
+ "r0H9OSAxXFS8GIQQc35EOIxBama8DPw2QcAcGkxxxO1a0gO/NB3xS3XRfEczmqK5x486OYe6"
+ "OWZc8Q2NHbnYPHu7V7rcfgM/2jfK4yiw5SfgzyvMT4XULQ9f8k6+/Oi6GeXlovvChhNlbQ9q"
+ "nGBnSqw9XoyYwZCsvX8EuY6N2bgE5A7LMwbDUebus5SrCq2gzuKZLC8OHnN5m1xCaxu6bXPd"
+ "HrB4z8/jVZTmIZl5cMC7iPtGp4o8zj/OeGqFu3E48auW2H79YLwQ05QL8ojv1ziW5LaKeuyT"
+ "JpAjX/i2JmLqP/i+0kOm/BrMy/ndx9HqLff0JiQefsZxMBBcstk6Obvad/z6IcyvyK/MHuZs"
+ "mickQ0rbNfeTOKN4zVX8yCpVICh3JSu5+DmEokQdPhUbUZqBcGguH72iJuw9FqJRDzFbgwUI"
+ "7vs1vXpWSY3fg+HkRw0O86Mg3m8iK6fdlQ6iVB1/j7UAxM5/chBGBNDpntRPYRgrLFkjKJDm"
+ "JhvdVhbzfkJVs4wfoYZmHnxXmyakrPLT/5xNLlD6b+OI1ZmHUx3kVN1IxlU3EdcGM3UAWjPk"
+ "pHmSkDe2t75xWX6aXUSFCsM45j+GuQrDSZwJLvb518eYxzN+49RKKke0hiKFjqh2FzC1Dqmd"
+ "1jHoo2nu+g2v0yu255XvL0eTlPPz3WOVFh0xWCIuvcUp6bnGcQytrcX9kTOQ5nyDaDgqqOGK"
+ "w5OTtG71QMkGHnRAJ65LHzk+EmdLxN8zxaP5/MPUMy2UuTR0jY0MltCHn4J65R8PF9pTYy92"
+ "T+2k1/9MnzPTfKYJzhGpSARRQkMemXHFObMxbhsVa5E1wdExnWPr2uMgaogqk4c7uI8Y53YX"
+ "cLJYcf4jO0a31FA5H7ifMeK8Kuk0WCO0JVqj5X13lO9RVLBXWpjkcaRkFoZDRPu5Wkm6zguj"
+ "4XnwOCs/XFAYbIxx25P8jIhYSJVysKIi6w7imwyM3GkNjPLItKCVLHAc0jOhcKIg9i2MuH5Y"
+ "POu0WtNbTT1nkf3D1C/k4Mhqn18BJV2ckvhtiJPCjREUYBOHTqPzh/QDfV2DV7HyqlPDLWPy"
+ "kd7DZcS4rAh6jRcrwkc2LtzgbkMj9ERsNOJEKmbOgNGqQH5pf1aTcgHcF9QiXQHhyt2SXXbQ"
+ "AZOIvyE+SJhaEU6a81sUkn00YR4tz5Dmf8GVnJSyZIO4Cfh/Ixv7/zVkQ9j8H2Qbye5NPOjE"
+ "jWgUM/kDKuWPxIYfvcUlUW7SfUkZ1YOXjkuAXJ4Y/Om8g78W8osgy0XdGm9nmCIBUoiLKVrk"
+ "+JsctVb1q7XjL1RFJa1A6tvYHtFjdXXz+kIFDfcq0fS+5fYKtva2e3VqTTl186qruPu0IfyJ"
+ "P6sTQFOMFLXBA7HPveuIHaOoxj645KlZwsK/lN9y6g6mCyywvmnEByjzSNuy69sxjItxyJI6"
+ "bFVHMHdVV4fpOfPhIfRzgXMJBGKwfmuUrWGJMmBOxdq/1pb9TfRKKHNhz/mbZlP2LTWlzMFR"
+ "4VOAnpXSLaGudtF5vNUgLLvzWDZ/wifn+DzH1R70baLbCQ3IqPYXLnWeMCYo+RuiF0uoz7mp"
+ "5jmsMpZYwADY3zUAM/Rva8AKy4G2VzDvcyXeyd/wbYGPoGTn61j6Ps2UPo8HhQhys9ZY7zb4"
+ "gR4CH4Q/f/bg0KCEFrD7MVMJrvk77n+jWaVD3CYeaC99aUNdTaPmk+OFQxYmSCakmsHwPvXt"
+ "proWCKWpTThL2Li8rW4u3Qimxp6OVKq+OAHZt9U4CLLjNZm+Fcgtv/mC03lXZIN4047AB/YH"
+ "5Dpy0z/+4N8lc8kMTx+4teKnXq/bL4EDHnLsgUKeCeey/EZp7RY7TgrWwN9hRzMDYcCeIZnX"
+ "TN5MuJjpcOERDO8Fmodnun5MUxq4Ty+0sKpwETuLX7MXOaAg0rwlmYO9+VKMpSQ9Q9K3A26I"
+ "8IIzDYOoPso0hWn0ni1OkSX/lH41Jo5/Q4ICR2F1MBIcaeYcyRsLjWn70ycGG/RTv5F3CRyt"
+ "UlVLvIh2exxvABu4poR6iFsF4+SEDMDdPEXp5BleC8pqIp/Z5NMQi4q77FbkbCssY5m9f+PX"
+ "+m59cNjfmwIjz5lkpb+Z5opSXSxKxiO4C3WsMJdayqLIRNnuMjnvCAKnvM96lYXXP1WdzPGz"
+ "IeWWWY/F5xCoFBsxR6Pm0oC9/oy3pmecjGofaZhTBhjR2tqn/dxPWc1ixTsz0KTcwN1DhHgR"
+ "HkPSZ6bh2Ek8TwFz69uoVxmpOXFVVzhqSwqQnPjFyaRiVbqsdJ0APvBBEQkom4SZ9WBtsqaB"
+ "lYGyqlxFz1SRGgokDP3zv1pPkl1P83NRurtD6MDW8qDeh3TVafQlnLh0I3t/lVPPOjWWWJCv"
+ "kOAgSVuDOp9kB/12QJcrjkv6+XEDpJdMW81PfUb2k6US5dnWwLFtfwJqitx64WPQmhHZqnSW"
+ "cNPiasQ/fCsnBuM+mN6bG7sz//ECkWbURHazeHI/1PjT3n2rylNjW8+fCm/YqD631hrQB8jP"
+ "peTJGk3kMzRE2CzFpWD8lDIOs6mY8yUgkAdrrytLwP7hI6JHjpGuoOK4lGs34qNqTtosVCk7"
+ "n/COurvef5ru1164HFfJBPKbbcxmgrhHIKsZXVUwz9QwvBPi4cs4F77lVjLlz1Pf0FIKpgb2"
+ "rha6yKkGL3XN24I9iMGbMt+VQxMPI7GLe1xSUi2KIOR+wMre1xH43DDE7co5ckeYBSeakeG3"
+ "6LZS/RvcNGQyfNG0SpB8KIctyniF94Bw+cRUCvvyric/Q7eBrLkHH4aUx4wC7Ezw/Cj+hZXi"
+ "cbTPkRAWiyst6RplEXT50pxbaqeu7N3gsRqaUCm+dzrXi6JzThC4zQhyJ56z0zhyyfJd2MDo"
+ "ZQvicn17BBLRS6/GXMl10L5QW5DVCwP2E1EaAUqb+pvz/YnRUh+EE5kW66tWZYI0LdSV81Pg"
+ "RSIHZoKGZISFxtILhIUX6tMMBMm5x6Cs5nSf1/MYn98EZbt8zTAbsSvLo1zqFvb65nrE4IIP"
+ "cAoxjZ48uWDuWnr4z9cOPXEKzYOCeh42s12JclqviD8QzELpyALwXvwPwuHYNYZoWkEqMhdu"
+ "xtzGOhanv6ygtLcXuwRb3ww9N+hJ8ZtPbWWLUCTe49f/U3y+jCnHUCjRWzMpBHpZmUpIHxAP"
+ "YH2mux476jQy63moyRvO00jsbPdx8ds4uQ6KfX7bQqowzMe3D1uuYEpZRoCTSgUpYiPluhZr"
+ "6U1dJAyij8ogYfYjsO6ODtFgQUjlyHedNBttwqlA2SCqdB7KAP4Geff707+ZgUlddjjJX73X"
+ "O0Nn+mykM7G7S10yjHDofgJl9YP5XPKxLyIZqj39lj82mvMztH5vN69IEm+X8u18qQT4SroM"
+ "ZiNH6yIbrpEDvtVrmVzlYfpOvWSXjReBsYO9EmFAQ3hZvTJLFjUwCWFaSiihmHrsTcqc81OQ"
+ "ah0UvMTgdLHBUjBbNexSiuHGq+yCJh4B1b44UXJoOHIfFRqMwlqACI3ylYfEKPPaNwlvUgOM"
+ "pXIZz0iFBRFQDumukKXEYzuuyl5jyi44XjSpSKpHmc4RbVnsI4Dm3wdQAUJG0js36v7GBjWV"
+ "bBu5NGjzQhmd5vgH0fssl6T4x+LCGrEwr9lc066mm7lRaMoU/qIk0Eme4efRAkdmgpy/GA7s"
+ "MCCV+KqusUG5Ti8CFv5VaVZEC78F10XcERq5r6IoFBvtvCeBbbF0511e8y2qtXXELPy24a1Z"
+ "TTgr0x7pyPHll5i/9T8JaepxpGbmW8GCwaH2xvRWfVs5LltVXq/QwHHC03TNDlnIadn6fO1P"
+ "fODHVYyQt5JP4n9f13krNx+NztEadpd07HLpkVuBdtsX//xHTACfGNZMjK7OtZj6QSZVOwkZ"
+ "JUPS/fkdWTUO2hLBPPS81PucOyAp9SU3poSXM+eHaAkEpisneVdQ0BRKsgEeRupr8zXwzxCZ"
+ "NoLI4E5a5HBQqHXdiPUjhFounLGouqc0tR7jYsdj6+CKCgoCWeXe6P37Wf3aXu0sMiF7JCae"
+ "oOjNtdY/28YOLDkOcoZ4om+06rKxP6xNBAdHX8HDGSfu2buSJp7rya0UI4kXR9b8tAvCSvrU"
+ "dgOijamnAuyN+kw+9F2t68smvRVvoilpfz4l6Ypmoy/A7dh+oqrijmrBcXXor+MooktjncEr"
+ "dqqX9/zZmD3X0cac/swdQOeL0NwfEHPnjRZUSUdBu5JOR1Eesi8PjV+QdEIi4FvnI5//Q2Fx"
+ "/F9TWBjJ/6OwLPUGnO5k/Rwiw1rh1JaIpiiI4s2Ovpu3VtLpjpD+dyz4z6Ew0fjsU0cY0Y9N"
+ "BIeQ4HlkpmR8hOuOojA3hNEOQ6WhKNHKTZ4AW3O/1epX/RbrFpG51FgHnomaxjP7GjXL7TIG"
+ "f+Lcc/e9FV1ray3Veg7QpqmGtWVYt7afqh633Y5MI08NWU1ZvM5k+9OG2ktna5sOstYLg1uh"
+ "nAjfeZVWJp7jiuuUEa76RQjKDHPIPZXXWelvp2CfiTb1GtIaNpW2dTc3A+XT/eskWEStd0rk"
+ "hdfNaNuOIdFjsmMG2UYv261OIQaDZAHl6IBAaSEMhwJ3kTM+R4mouMs/gYYT7BUA/hwSNLH2"
+ "M0hZneTMPH9KCTxgFLHBIRf8bfrd9/F9WLnOQtsYaFFMRDgJOIWDZI38Usd/Ab8JaQcmXGHX"
+ "6NDq3W3SZp4ke6YFfuYRR3zdmrD1ARqUlEavE4jYxeLyIa7cW94tZNAary2f8c/D04Kmqg8r"
+ "OXpiHUX6zGhJbNynzB/8s/XHGUxqGgmw8Bf39DUCg39LvF2n4zpNXPJnv9uScB6RPgg7D16m"
+ "4z5yz84fGfmNiPCfODue3To9lb6m9xdq8slhF94Fm3gL24dm0dRofCbURHpxDRXx8PhzT6tV"
+ "62Gna2hodf2rVv9WX69jIqv6jjobOHBxgKp2yJjyQuh6Fqb+hV4N+1K43UeF1H/oirrsWvn+"
+ "E30jSpSBAzOJ+RYLPm6sjEgIZQMhIQqS+aAYcjMPQRz+DNbQawlKU1zPxN0ABgqkWzWMn7Ej"
+ "c7U8qQ+zt4SruReyRBnnqNuGm1H2j28IhaFf05N8pWZr1PfvA1qhR049F5zRzhgmPixZx+Yy"
+ "NHtpFyQWXjuHb5ABr6SX6eYyTW7ZwLDU5SOhOtSCx1vnxAchpG13HkW5IkzZCz8HUosA1bRv"
+ "K5xoonHqYK306p/jeaA7iOlSq+eFn1JSWXJwWtqnY3pCkhLobzNgX+h0E3OQqASzT/oIAQah"
+ "l/WyghHwOtptQB5UTQhg2ZEr0gIS5SjUo80iH5Q5cVQicy+y9MlPHTeTA+dYlfaRy88ozL0Y"
+ "oSZUofzUtESBJ/Z2c1IfAPxpMdY3woNy6OFSuKkfx++/8Pjl7GSEhUqja8Zj7gk9NV68IgWX"
+ "abSED7+0GFq3iU1KKXNpayK5tDx/92sTmNmRcR9CRNIFodecOYMML6viAeIW8kVuZ8hRqDBF"
+ "9DgvtYGmd/gSF7FcS3rIJiUIe/8gc3FFAxNVwl61WqjQxryJ26i6XGmEHL9/065dgtugFD67"
+ "1S2KgBnl3XkpgkcoScSNvYAYYyNwLJ0a0IM3Hu/kcCHXH0+AgwrMV82RhPtTBABFCWhVSxgm"
+ "L+j0FEyv/GE3+a602grvOewI45pZ4TY0qo0RXu9Q9im6e/SHfLHV1Un3r7K5tBExvtivbgSL"
+ "hVcGJn0d2uouZ1kJGGhLnZEYQAKgumERiKyeUfmlcaByZ4rsXL2UANm8oC5tr765H0Ti9ky9"
+ "wYTU0gwf8TvPAz2HrDy+U9Y2hB23E5kwL/xnQ8KrnGSYn8T0mk/1S5kJBvXueSyULR0u9BCy"
+ "EiYqgvSHKrIPjVLduI9MNc4wbqiJ6qs5ni/ZKTJfIQKwNWxmTJbcxsZILwH4ZfMEeVnYYboE"
+ "J7gwr4nF3PqTqgJunfbH3X2iecWk2KFvx8d86KPIOqlQawT+vKWd4eqMpAxz+PIO3q+pEw4f"
+ "D/5ZxRPZN1PuxRfcjmM9VEmyMdtNek+JSBVucozVfxctsrCSn2I5itHv+H0Cnz5W78Z5E5cz"
+ "f4xFaswgH2YDQJoc9LCdIgbI96Hzv+aFWMYV7d8wF6ueKFjIdCV6CA8RB5rXo+fMqUjBfRC8"
+ "gBfzaYtiyvLQA+jRbSX6sFmuwIG5gh2Me+Jn8EidMzPHooD9kzflDG6jpuM62qJdSnbfb+GU"
+ "nLcmMiaD46Vu0u5rSxGvobw7ePJorq0XWDL6Co+TQaFltCTPX55QA97EXPEXmGuMWlOCYTWD"
+ "CzgwOgt7WQI6uKUJNZTJGlm8kj6tE1rM0Yk6CMqmoZPQbj4U/jE+gARhhEFzARekEgr7XUX1"
+ "NIo4FmUHUBm4tjfzBJrMM9qUaUxtfDW0F48LdqUdfo20wyCuH0OOiGP8yHMnqaiLxiiUDgHG"
+ "QuXx4VdnhLvdWq4WE0/oIX46IRNJQJH5r2BVQrjfJiZ1gi0LpnQ2U1W6FIu+NBRCt6SYtN8W"
+ "drpyPmXO28PeB45WzzMyUGIrHT2Gy4itmh6jTKyzsFihZz3gCOuuHAZP79ALELrSJr6Rbrwu"
+ "0xR+kY6T2gXMYVhWfxaVesFb7OOPvWPRRbtnwBtFvAUbO0Dt9/TAk3hW9PphoyaEOBW9E+/N"
+ "/PW2+MSGUexZE9Mr3KRDvHvsZ2Wv1c76uaKWGT88Fk78jQOrhtJeZGpgCAvjQd94L7mkXhMr"
+ "lqQAIShcwqeZqNKp8g8iXINgFMaBBPTYXYmCT8j2CKOdS/4Z2k3VgmlzQ4ayhhAKH1yAWUYt"
+ "mQgEGssqbpGm+bdFsIcYG+x1iU5rA3i1005Q239+knBecr2zhe/oTmips68UkLR6l+g75mX0"
+ "ij1u4viJwe+C0OEU/espyVO/paXx72eExROvsyxrM+DbMWUYhWx/CHmHHkY4YFpls8tYzNAM"
+ "i0hhupJV8zgoNKIwossqxSLGCtNM5xczfCeAlJD1A0rld7GLxAnclOQCa6skVKIfRMshP6iV"
+ "aTee8BTx0yXpeHxONIejH8SxgPijWwd3YZEdIMWFGDWI37S5W5VfM+fnYExJKUmyikCBCAIs"
+ "QPwinUJuoIcHXPOUcW6FG9eesQdv9xSlue0saqWl21ey8cJTkt8+PU5wwlBIGVgVVzvoIVZu"
+ "yFIbI8TRHvMdyBcX4pf5rSQdPb1uiEyxJhZr3nVnzoASCaVw0dYlxCYlteX7K/71T/H/jTJ8"
+ "hTUE1v04GjKNKQmqNxOyc9mfV2DMgWYGtLjs1Zz3E+pQ4YDg4Pz01Lh4z/NdtV55z2YeoRt6"
+ "8OXQhEoFwmvbjWv+PoZoE48szWKDsSQ2eMm/v7BFmAt1szUHEX8y6Tyl9N7xkuO9FIWgGOWs"
+ "mtLCaqhP8TLSaVR7ct5fNxdLnt9T2131N+D+rG0j/A8w9TCfuwEJjR9LKCgGYBJdmNCNKeQ9"
+ "bqkH7OFAXa65lzr+67HQHkjwHITFYlOYmd/eZaivOMbVRT1grAXRceJvr5+HeukE1tgORT7M"
+ "b4EFjbGjZfZGujOZV9gDR3cQICsFyWTMfNyjFPg6njSSAiwRVriqIxe3LU2zoJxOIyn3brf6"
+ "1Uxebk0omXSP5oE1lO/7e3cG7ttEjARqHkKrC8E0vXDio61Fgbway5IqsnL2G3lh6mbiCYkD"
+ "zjywCw7wijvOAir8DmXr04aawlj+6uFYam18/nUa+/TwN7KvCRjKe5yDvp67c1WUp3DV8zl0"
+ "9jX2fINVzfIfIs1gvXxI2Aqd3ZY6WORTpodY3dIilfgxfWpaVrfdupCpSXQdAdBk0atWeTSQ"
+ "WyGeTyHIi/Cc1pBlc5kSxvIDWjziuXdH4J9hBMwueBUViJckxzcoEZndtmmnpsMt/U+GCsMo"
+ "skz/kXa/UhH0V6Fg9V3t61T1coLAAIz6RPWPfrBIXS5n464e/3UDlhsnM8XYz64bFCkxAjAd"
+ "rtOOW5CTIe046VktDaVtNWBpcX4bicM2dApWD3JXfu7H0gECcu3eIODqmRtj1i4n5werB6yr"
+ "N9Ox4sluPEmi69qvHyQrHulSy3juCOW7GcB07Dd2BJcd8ZTKoFXlJgnlrGB/UF5rn6I48Cfo"
+ "AupNKArLX4J8gW4ezl1LQcYsZUhuqcsbbUpr9jr7A2BleogdaLoK0BE2S3fjdBHoLWXN2nRh"
+ "7GLgiXy4JYNf3/xAXtQoVCXwfnbVrizurdyZuoTFMWlQr5C79KsolelIeW+z/fYrjHAhLQdS"
+ "rcXCAkVk+hw7MNQjPyN+mJQYhd1oBd/CTfrak/J39Z8RXiT1fNRwe3Hr235vrDRKWqtM4k+n"
+ "2zD7fuhcZ/rW72vkTxf07qEJ3Duh2kIEpyMWSlrqzEjn49x/zj2S44HI9enypJMC6pNnSSo1"
+ "TBSuE3RVbdK7/e2kyZb1ry6C1W9NxdFH7X9bHI+Q9OAPKqq9XfW8mzDjHj1ThP9GQacuJNSj"
+ "5ljeE4gVReAd+lPzf4560TDrE8VfhJ8LvIu7vcPOg5Ll9pXepaQLcZzWglXOB1x6z+Lr7Ci9"
+ "z/tpj5qSJtv/7Zshchpig0K9AvUJnqECUNO3DDEy78jz58D3logx+jaD9ZcwkG9bGKY9gHv4"
+ "pquG+YjzPqZHLm4dZQ36J0O5P+XejwvO9rfqeykw+Zct6D+NTvVi+d9xJakmTV1QEJNR5Cmh"
+ "Y2mvGUmoWskt22w6GRNNYx1RxFpSpNLMY4/IlO2eJPb4YqLjW5RUYVlaNwu/pX30Jrn8sv8Y"
+ "HGmC0XIwlnCaYtT/53hQUyzNIT4ziF1dGHVT44uqYR7tcI9NJTgxE2ky8Sr0Fn9vmGmhiYG3"
+ "+Dt/RXPFOSNwdHPlqvfuDNV2tboBCXxAOt8vAncqco0TPb4jieFyOy5sqlVpilvujsvMJs9G"
+ "OBHJMw/n2y/mVcrVbml+Ao1q+I2g/2hwnvvvkZLvooly8T1ytNMM81ppIe+DiDhRjQzOkmeI"
+ "hX1/J/3AbAUsigY0SEKgq77LCTlS0YkRUC9BCxl+hQ+IzlH5uEOxcANdmsIQGH2/UmtNuUrO"
+ "taOMp+wV0sOqfrMH1WjuD1rIXaPSN8oYvnO0jP9etwah4RqT0pHnghHWkjbujYC3JA4goEuh"
+ "hVxl3DVSQ/V3K73MGUghrkIwILFjmbclIGeWOFhm7GSVafIntJ7Nw4JIyO/UD88+JgTkta46"
+ "Lrs+Y4KpKvnaZAsJYjAroAY1wBpnvaNg0O3K2O0QXl8EvvKXIv66xdVmzhW+IdVkLcFVzIYM"
+ "pcPVz/EOBGX0xwKOnQgOJ+Fj4Zptdqr+Zy9apYlETtHduSsX70RDZvDHGd2Nzc6/KI0+jhYy"
+ "ynTpT9Nu8zfUXN1+b9jFEgjPrbo/f7f9dwtxDg3VZ6nJtI4bpwRTyJN240OcS7AExGfcnYaF"
+ "6jpUWNU+JJT3kD2U8BAbMhmoFGSv9x2ohQlHqZKFbz7E0y3LHcpq8XghsGgfSvtTGmXFpxS5"
+ "N8SUC+alGpfnDOPOKRkN/nbmQYEytkZeuj/xYoWrHHUDtC9SAU1vpH+PaMb1lQ4Mwrm6amDY"
+ "EQcmkBbKpC5h+HfnX2JxvmtNcN4Wpg2igwJAbq/EB4V6ntLd4m9gtmkVxur8roMq77JIavwy"
+ "QVzQNW7KkkFHtmyhfkliT/tZNPDCeRZBQop+ykRorxIxhYE/3YonMwGqC1y3yCVVnaR3xvPT"
+ "zlZKnDGaiyPG2uxqwxXd7UWor/XueIuwYO+wPCSeUqE7oHQUHyLlx8ffeRGhZtq9VKcg3/IW"
+ "D5N/ENTCeUWp8KeJIh9Vc9IrEsvk8F7ET/ewXbBPjbGsGCbi/vczy4Cnw15Zy68auPZhua0u"
+ "yq8dt0f026Ak+xqbKirM5F7EqHfS8lvBN7peCI4XcQELHE096HVFSyUtdmWmLf2IFwMaht77"
+ "vXot3WqFlTqX3E6SFw+88Rq6KMG9lvZo8StZ34/rYOgoDxQKZRyr/8MU4fy/Z4rI/48pYqSn"
+ "lEgl6AccgLUs2qgOxW+I2AjgbepZ/CXu6iUyTmJi4VMaUZEVAyM9KSBubOrENoW5o7+MKRDR"
+ "cWL5gz1uLKpYSiZA8c8/XmKQOt3X+HNf9HOZ01GFIQf3i5ndPa+Rv/e/mtuzY11N3jv3Ba2u"
+ "upqzlokajeDcHy0phdYPdd3pBen5WuraRgu78JwPtINv+b3fe3nVSJWlEw51H+yFXjiTzVgP"
+ "53MVNuBQh/982q9HsDOoWBxGsTv22skq/RR4nIDswpww2K5FTo72b9i/ZrR4hm3WuJSP+sHv"
+ "PiMIaAsXuOb/mdN9zmK5f27viyRpN4IEQ5IjQKJfoNxuZdxWU201dLdbxtKdIzE6X0yk1a9V"
+ "l1Ojtvx7meAIjXWNgSTZQa2tQ+sCNwi7ZMR8GI0x3DzkerJ3QeqFjJHYr7JvmgIaEbbwCR6g"
+ "vGstov+g4frM4bfR7db57YyEJdZ61GjyjA8QGOXt06D7Y79383sHdlCiN3hnJQzAmfJMfzf5"
+ "l1S70dtiQbIP4xSG8O9at4BNwcVUUnuduOZR0ETxl5FOpbsgXMcHspkf7PBvdl/m8f49eg6e"
+ "bp3VPnc98+KQ+9/f29QLU2dJfVRnzfXgA5X/FS11kaQ3s0vy1pq3rZ4I67aNfOYh60VFoMqV"
+ "58editGQxaplfkFr+CcI/xagF7UPUPRpPvt7TKyD92YQsjPgxLAhlGnIAFH70bVn1MBt4ul7"
+ "rBz6HpiZ/i/w6Myc/YillKsuR0uVpxrVnqKKJWd9f6uAHCWZNqID/bfija05vNHUam0aJ8Pe"
+ "sS4BB+dFGOGrWQW74y81AFn07Sv11VCR/5KqxPVdkX/b41Bl/KwA0q0s8MOdtAKq7CifOPLA"
+ "OCZkBfrS3TC5N/cy2ijZeTA/wK2z8wucKcNZg39+0zWjzjCS0Ni8PMlXKHCN+v5DrImnOrIZ"
+ "64W+gCd/0M8KuWW6zJRmTU05cBMb8qUZHjPufF6kaeOEl2KCvfcFDTTQDGEFPSxFGmNS7p/2"
+ "sBIfOOQtaRWogatEj3xhn38b2gOn7g8o+s6GPr7hs8kAMRsGpfcSjRpkgEGFG2ImTVhnKGQu"
+ "FZzwnoLA4rD3PZqtjyIKwiIh1x6BkLWNvSKMdg9+6Ec+7OkNYJEUsR/QUhm0wJnSUiv2eGS3"
+ "oI/xJ2GH++c6e8ibSfSAU5qgAYeIYOlXwC1e9IKfiBNTMilBI1JVBBx46EU5f67RusePpedf"
+ "fd0lquRhTsqKtIBhFqMx08R0r8bGaIjWdzifH8CftcEUqbecZ6eG/uijszsiM99RvG/UH38r"
+ "GSPNXmYcWaolRHSrPiZtXjP/APDIq9iOectYErkVJBV9QI5Ti2Kt0aMemJrP3AWX/jyAL49q"
+ "YXnl7qUK/BIV+x0Gf5sL9lvbtTWeDIOGehy9xbgPr6gUPRT2mzbw1U9G9Hegx/a5tWbSZe7L"
+ "gUQhXKP6yaqXvONnq3HEmIx//R97GsUFQgZywbo8MXjcGJaHeG/jlXm/yLTiOrhaRcFZVsgz"
+ "78aas1vv8u8IaNC0YJZN9sM3hjaFLSPkP3DasdwKbPfRMHNLZ+2JAbHlgiH2KVaJuiOZY8E4"
+ "KgfbBtBW1dcMnRytMMzJCf0377UICNqK0CDv0zmsY+QUBY14LV6xMJ1kXkrofhLiEhi1r2pY"
+ "tWesJ5ZjI4xG+jIxbdQBdnsAnPVPKiYvPVGQdBc0QCd/xXTPk8a4HS22wHvA45SAhnRlqwAS"
+ "nYVcoJ4yJdPe7aT2bxKpRygpd6gWu4RvpYKZjkgADGkxch0ScrWY3tTJYYHdjo2BQDVSxNHy"
+ "EH5Rr2mmHuuzEpoC89wrVxREfbCEaNfuirIr67Si/zApRx1WHpFACNIMk6jhbWMny4yWHnIn"
+ "I+U3+z7hIRnbaFghdTBlyBrds4qt8ZSCQIn83laSxpYYe5FDwdZh5LHgpR5c0Fmz5A4axo2g"
+ "mDKo/3AsWNJnKkbIBqG1ObErf7pg/jMI9IvUAOwDfFvAMKZdUmhBsgdDyd0Tcx5i6uSpMrjQ"
+ "+djgTP36dHHshYFKzS+BErd2xbOUU+2mZjIKGHSXbqXkCkIATifA5dlHZmrq5lsKcjUMKWtV"
+ "d80SS1nmuKKRRCZEl/XmUTYpC66XJj7STUm6dLQ6lMCl+XAFfOf4JXHgBBr+E52UmmmrhqJN"
+ "uM2ChnzVcCy4BzvvbMmunPCIDMYzndkTBVmBGjoPSJsvHBi/s10iebceeW5M628w5kTajcMK"
+ "dlhuX4+sveCEq6VJiQ7NPFTbxRmFJIjwdZ7LAHzboihPWqHBaRC7SEO3aHdEijDME8xZz6Kh"
+ "5zYN/lziVQhMNPpK8E/gBnRaD+97mWudCIYO0lUrWadz8ZadHjCcpkw+gu7+2jagq+A3pqTc"
+ "AQkdg4OvlUbtGVVDSKmVYcc1VhB9GHF8Ska+JCyb8sE0PGdz0WQU7uVTnkniVybWfKZMZQo+"
+ "lOIkUh8SM3xDmSiGrwt0ICWmZkzK8nLlouDe2zuo64Y0lHwEKYkK2M2qUutt8bycfBcxlo6d"
+ "gjmOi94GwI2xBFBJP/xu6YQE/XLllYEk1GWINOpKXZgiINs+wGNiKMSpBsSR7i5vhbeuBmni"
+ "ScDoLpT2hj2/I4jLp6qKI+cAWPRStFVkYGEYhpBCxoM8NvL9+kiF24Bykqcln/4vO3ilmjt2"
+ "UtPRXElcTNFlUNEZjt5e5r0WKRB5xinH4GE5jBZvJnSxGTCbHErreCMThFLD0Ng2ZktXJUF0"
+ "Xi+VrhBIERN9taff0BoERxnWS1XXRsWEK8woLk7gaAEXQC9YcLgzFc1dwoJT+wev74ma3OtS"
+ "pYzLrWxo0gk1XxTunAQKbM17uXd18B1Q4cMPwo+/vwDiYU9GzkAHljw1fmkR7GXNrI5d72YI"
+ "oo2qSORnx/lHwgH5qrEiubnBDi7Ss3SkNPCjFobwetkIdmyXesizGNRsJKzP0QIB2layb+WS"
+ "s5xCqVgjTYj+QgfT8z3qWN2+yFRVy2CTL8VP3mT2i48DjpROGuxQRbj2ETZcE5UOsttbVhNW"
+ "dzbM5X8F45CulF2CHCzR8G73paMVeWr9J5+N2ZCA/Og8MDtCufZHYBlLyBwX6IESVkzAC+yi"
+ "Uk6xx6sTJDkog8ZjljQ4Nco4ARIn0DyqHHaQM5igceiCT7yKB5hgPoa/0TzsheudL3ApX4tu"
+ "KCqEbzHS/8nxcduLdzEYd+Aa0Or1Dr68H/R5UWId30CbUkgiL4PQ96JRM8Bu5E2H9B/ewHDH"
+ "hvHW1Ss9EiwjhZbQcVan7hdXDbPfFh/2jjbCYIBJkQynyokZf8b3K9Nvh0R8KmxyJzXj8mmK"
+ "VBNHJo1GwVg0iTmvl0NA5aGUK+KC6voBlvA1E4/E8JEqTcRoDotXxW8yq0NFAKkMWB5jIuOG"
+ "uIC4U2+mNmBYJSN/iBShBtf++TKEIqgE2CnG1iFJ3biQkOy6m0bspmdmk9HLm1mthODvF51k"
+ "4yrQAtsG1395JplmUXqYMBG7qrzJ4Ew5WbMgovMfkE4JDkPGYj5Bqtz4cOTBhI7110OngAKV"
+ "BV34EFu8UlBoKZ0+RKOF8qbEwhNIQ18w80uQrmIMbZJW25LRLxHcROOXItzLYdeaxsKOSffI"
+ "ZgrpEWOUo+fF2l4UPPjStRNED6AGqCdk4ZHhVmDkyrtCy66J5FsgctS3rUSnkG8X6DHjrPgS"
+ "AHlsA8yE7Zwemq8MhCvspYQC5EqnJnF3hR4DiGsRzYJy3f3xkCVpuwT2vvMRdLhsFHW8zVl9"
+ "T7J6YHQWiCHC74iLF4m/UjeIg4n5Ni8TFSIVDtIKt53utZD3dvxMzC1oeBbFjcYOHSTRGNVE"
+ "cQQxz/6VbS1gYzRj1WE9/phoQprbVPmc4op3aTmaszOQmUkR888g/1XzoF14czg3NkSG5sN8"
+ "GcNRq8+A6hOKFg8grYFBpd4g6CfQswjHyaLY+WCERyvQ4KQKTJiAVMHH+cmeYKXiDqHEjRJy"
+ "KSIjrjK8DJwgSCWZuoA/Si2aMggqYclTXvWeUfVc6+BPDQK1RtoL+nVW5rfQIYhCvayiHlNS"
+ "ldZ3LPa8RdPu192l5hXylREL2hxG9+xo5oJbYsbuHLVYnWigXKNRCfGBhnZQ6otqyXPJ1Vva"
+ "ZONOoG+3fW6JwHQfj0yBK2iB8xQwOLm+QhvPH7Dw3IgrFQ4m+DOsQxctKsHCMBGxFbdN8ED1"
+ "MbdS6PzWMM2Fq4HXaT0C6h63nM7Da04Am2qiwMHwNjO55bJR4CbnCNGY1BOmrd1dXIgBYxTi"
+ "SHrcoAAIdH+TQW3DTL3elz5fRqx4DIOOfITwru+LyTUGGT1ctz9ujqOapkzL5aOnColabh0h"
+ "Di0SJVU4/0bW/Vzer46eZP8uwUKZjtoxpXkogNzy4A2zoeO8AUtI7HJswrqiKUvZaIsdiZZ5"
+ "Pns3kMTapHkg8uih/RfV7MiqdOBD+PXoUq5wn+UXkI2pXzoLE4/Ubn9c02VwTHErrst6/wll"
+ "yYJVpOWYk+fpuwZICOJlsF3a+82dmSOiaelV5Xr4mdsztJ6ZkwggNutgqaZ8qfpTHa7K2yOk"
+ "KruRuf/fZZqGIdG6a8zKZzY5byd0X8mgGkZI/4zznS30gRqO+QOm1WFp7onu0ewp/w6vIhaH"
+ "BixYfVcWfJ/32uo2VckIZ8f0z3pdq5kHtQvskxrEaqYDSwhLwcon1sA66J4YpsqAnhjwj4+l"
+ "yB449C/JLB7AHC+LVeOSUjBmHM7Hwom7kyEOeFvXZzsuPP9MNIcvpKuuw5o1xeqcw1DXj+f7"
+ "GZzebgtr2lgtcRTMVFW6yqPS9uWTHq9hGYfxeG20LEkw83PU5+9g0ysgra6BJewPU/Q7HEr/"
+ "GPELI4gDe9vVKbHvx7vh1B+w7GJvKzYFOytC3gEA4UswmllsfxIf9GyzTUEpT2rMNxGapvbe"
+ "vwtVEGfhbXhnT058q8pP57baeIGI9WFw3q4vbJd30Qyt54Vm+tJ0x0J6Rqdov6h9Bk4TJTzX"
+ "MuPYFHDn2MOX17UlvGTj34HewF4k9PtD/LW6k16lyp6coYfMIdOCiwllwUuHKqRdC3tgVoNS"
+ "DqdTRrXTGJqXl9K/phsoXad/EGcmTxAZNKlQKHABZNQYxIFCT+4UFrv21kWwsNycVMS96CP2"
+ "MCSfzvPSvfZxIXJCDO2nsO2d1wGw/Y0KuWw8jf7bj1SzaLxAXmwkzOEffM9hIEOPartpyyES"
+ "6qWVLJc3R1rNPWC1QlCNgPqSg3GqB9BBS8pRK+EpMdlbLBgqEzCVO5koxjCcnSWRYURLq0lZ"
+ "y0+WYweB464TUJZoJoyzhV+TAMmX2JgnMNZLPgSf/YBgT6s6vExB9k0OcrGtvyTVdFwD3/Hd"
+ "3gzqq6iRy2TavTuAFphv1HLJe1cTphU1D18hZd7hQfDibFAp7gfauN7Drnh3iSeRU6vk+paU"
+ "1oTilZ4JrG4oj40UH13vcCsxBs/SIEWB8bUeHjCAsbRi3i6E5EPSa+5puSvEykXhnff74oRt"
+ "/D9yB3Tm7JO/CQnwy1ALsaaaFxZusxXmMSb1Hwka+cdHKUxRMlVbw5dXL7Ps+N/qEqsnCYDv"
+ "oh84Wke1pbbK/4edwfV/zc5Ah/gfO8MxWyVpX9ZtJO3IRx0Zh+YvbWao+OEM79yWhYSBg7lo"
+ "NDvtNgrhNQi+WJaFWIHYRldkxjT0k6hP6bdd3aOJSeB9PT2yy5Wjus2vmdEDjo9vQK2anDYi"
+ "E433FJyvnmgfzHJ12VNSfJ4Ioq8xHKVXCXlpamPz0mPvOl+OhC/ZjYwyruTTg2ut8opEvNoB"
+ "E8g9DkRW2bTr4LSeIyjJPwO/GeTCgsNSRtxLZHFi5P1LpbX8c/tZ1QdXDlDw4CnCRs78ORs0"
+ "mEal3ARqWrkZRGiLo//dS4+tnOargf7MmFV2wBEHYop3zw9QK6j3doB2l0XgMZguFV3MLdN1"
+ "/hAT+yweq6L380W52s441UX0tFmnalRtfkq6uWFauKB746d2f1AMo8r+wt7sOswX7p4eDUCp"
+ "vhIr4e/EiFNUBkYle6xEASTp/3RZWj451f+uD+YKJF+ef1efBsl36K5L96rb/eNpH/uuo39T"
+ "y7B0PlP13oU5OKsYWFwQbFVjQ3zZ4MBxm1+rxiskgUQ80sGfUuKpxYVmDV9Ntc0jW4oyvGvH"
+ "YFH+2xq4K4mN+CneUpPDWNxagGkZmJ1zJj8oKW0ZSVkdI2b8oHq0jcnxOwlRbbmaFuu3mpam"
+ "kJy5UYrXrWCzPPkoh6kgZZH8cXq7nHmxetUCD/j51LvJf3DlW3RMRDgItq0dIBQh/b7TImvo"
+ "lkEbY7WEQGkI/3iwBni0a0JUmRlWzJHXbuakNKHshKb4huDEyy+IFx0bU2XH5gPAECykQOjT"
+ "RmikpexgHDYar3Hj2SGx1MQlI0cEB8zZGaqtRNdhrRdqWVMQw6TEknU/Zgsx1WjkCnDCpM6K"
+ "bCb3rAz9O1wPctBwhjN+Er+U3bI7xrN/+8lD+OOubf7sABNPIu3yfmZa4qFYqvMCFC/wfLhA"
+ "stm5VJ/7dmYkqL/4IsMIA7kYj7zYXehr0NHM0Zu8xVqyxuvqWKSms+RYFf9IPGxb/U1qkZfC"
+ "Yk1n+kIl4/FLVUnNJIct63i98jx30q466F4uqEAHSSqQ33OyLRujTxSOfBsHF8v1flqyyH6Y"
+ "frEQt9pTiZrQa8HBk3qqjcb+WPzAYa64sbQqttmkEUb8to31+TKKkiHsQBHqwr9nx+8X7rU+"
+ "K3D1pg2Tcowgb1ov70vxgZYszEy15p6+nIhYxuGdJTDkvIkUdy9s0FL6k48lUDJItiQQQwGL"
+ "BqqaeBSqoYzMVZRZTgK8jd+iMCQphOhkfEmoNi+HpcrHLjvJJtaT3PRdEabDOJnt27WNWA5R"
+ "Y9ZeY9UdLfgNtCXzyOq2FMLG4HNrlieKyfSJmoQSnqIV7vuxPf4JW0CP1y/pWoUuLt9fEwy9"
+ "sHcTyfOBVKz+FH9bfF9woOjmtJiJd9xJa/u7tHE7BbCc6NjdsXoXeqByrzweAxPn7/94MwAD"
+ "0yLyPB2m9oan1ukcFOjsLSKSM7uq5vxBdCDWYm+Fnp1He2uz9B2m278pH730ZSmo9ncQgF1K"
+ "Fa+7j7McOenI/K/qbiMZIUQH/k3tH5U4CcGIkJkvVHXtztX2kg/bAfFf6mV4NXNIjVXZe7RF"
+ "RFxcMDWDgjTZS3HnBrI097ECUV4sMPBN50mKfhMOf7m+p3ktYQ9I28nImnnyoC7ujt+ZF59N"
+ "sn8LLH/ec1Wc0E+axCgBHWQdS1IqNC/zUiMTqwubEcKMIqhRYOYBBRxfmSjkacPb0IF8PeUN"
+ "syhq3vV6iYLVirrvZ7UyJYZaaD6YDXzFyISFmH7AjE1sbVBZ/PgldEwnbd1oDIo+njqJTSzc"
+ "tOIMLZK6Ba3kiIok1F7RjU1BinSUFo1Bwr+gIdLtfb1+uBtGvFNKQtJ+SWV0VKa0WxaKUj+K"
+ "0j+u2ts2opgILydq/hSnSLQ1l/eY9OTNYi13tpEBnMuAJG+iGNKDk72Erbeb5SA7UI+f4Vlg"
+ "nH7xTvqlx/FYhHBbk3jYOpPn9qB4NXTMo2voQtqENFVz2P3Mtu3+CrK/TeCCcn12mbXP8uhA"
+ "4MkR3mW1sEyyVtEqyONHjhmk9xxOQk5/HRqrKHlR1Rd55WMMjlMQYhaj6yWuXEYg69ZpnqRH"
+ "CJPRgp1sSZk4pbHvq3mJHWa2TxmS8klMPJCLP6DyGixehICl4GoQU4g8sI4Mpp55JJTHEV+s"
+ "GAP2/4i6dAdQCnrjdEjVR4tLx2goqbco6jqU+gk6hQ8je6Fp9JyPRHnQK5dVU6cWfVzKp5uk"
+ "UqYVAyfUODaDQBeZ42+xCkh6siYEv64lNnQ4rgdgEgrfWInYdGpv+G+A/ZSnfbRrcukQKzKp"
+ "jUxkQduMTE/A/oESEVtNBIUWKa+B/34Z4qHuokDBrb9zxfmAU7nL+GAj+0CHMRl/lkWUMoXx"
+ "R/GTeFV4sotqEuc2Aa95skiaQbSBQwDKoI2KEQz/ubknn9qeP1a0f+sbS13xM4tmFLTcsVg7"
+ "cBXO1RnkobLwZonqRvPCLKv23L3KExTW7YJ8XIMxZrkWngojBRHOQntqHfM1QtvHJc86WCRD"
+ "D8LY3Ijthm9WeCGpfuOi1L2pelaOKsElv7oMniVNKQRJCtJXyvTI9GBRjPBEuyevIqivedJQ"
+ "P0Rai6pPA2K/GxzW7EVI4oGaJSDtrcIHGVk9d+WF+hPZGrAJUaH2PEyOfzXGhAcdoyfTNiB6"
+ "0fHO9QCWIE9QjcQJI57tWrsU86jlMZyAXPIZXFIAwRis9nlW2R6MJ8kduwNb2SpFTwr5XwOe"
+ "Z+elqhR1S7A8CEOSZTU4+GacauciuoJAzTIzXUMOpvR7ZHywfsugdBcbxTx7S+y9Qe1ebLlj"
+ "/meb+ED46c0pM+OwqH6E1lPHwEoc0segThPPaaHj87/7ei1dR/mrM08aWnQVGDfGMsZZPlPu"
+ "qsPejxNM0SEWZqbXiLJJ7IkGPZH1MHKZn3+LCaqSRLJvqcjU/vAgy65bthT8P7ydVVAcDLCl"
+ "g7u7DO7u7gwwuLu7E9zdHQZ3d3cIHpzgEggSPLi7s3/V7j7d9/var93Vdb7Tp6qLjfEVfSYs"
+ "SMBn5zY9hsic7dRroUw50g4iRv1bMOQu6v1uX9keCu2o0aFk1cSYY7IVyh0C2vXbhzzZgBY/"
+ "LcwDypEd5i7vKPX+7n+7jwELkn2i1o0KpKJyQWxYnT+Xsa+BtCWNwDzgqJl9YZubm3DkhcOK"
+ "viz7Zqo3TxLbN9f4uQCzlY3BlUHbV+OUwd4gcvZUuehMBjWVVar0pR9ephHBEwAKvj5zfI3o"
+ "GNI/DHwdKHQE2yEOa+5aPqVHmhvVxtHSDqI8DDwYP0Sr9iP4QOARyyHjMqrRR/NS9PSNm2SE"
+ "0QK9lR88b1UKsGd0j9CpQn/749OZBG8iU5RgpqTBAWmYixaYnBik3Cz41HwLndh+YqTTWuIT"
+ "hHMqCXGnHYhnv74gZ23ETi2YJy1G2XS0h0k+rengZDCvpTV0HFeCTiTbr8Qf/y4U/8qD2h/3"
+ "Szaveg3cLuvf6FGFLzk4Xt21Jqu4IrxPUXkP8ECMTxlmD38REdKh8rXp87rpf8XHp94oJdUg"
+ "zWCCWLALgy0SeB6YAoKXu4OTomhzknBuH9YZJNVeR9WUMbWI8icz7bhjPiKmxXeeo16ALM0L"
+ "B6Ahyp6Uauhnsggy8PQ8eSt01eFcdVh0QDgXE7TPRGo75VWb8jBFkoGrS1dKYrUjBUc0MHQ6"
+ "vJ4FwbiOluCXAjpz7AWNnoolAEp1ejkIGwshu8gtGFcWHylnxqVKYiZvAmOxUhgy9GCEZXc6"
+ "5RpqlWLdisU3PbF3pThx6RnbtOmfhLw280dYvrRJ+jRkCbqtv4/osQtraYhCr8ZgjDPmOLeL"
+ "skRLjqJTEWt4vaSwwr66beqJP8dkAT2a3k00n+Lv8xrAZIjk4lMQcGj1pE8K4G1fFjIm4oci"
+ "fz1wTNUvzi0m6BKW8NsPe+68QOydlxiZP6uAr/Ez34IROn/E5SAZO5H05tbeu8TCz+PyE+6/"
+ "ksE65W0rQnEJrUSLdqGAMiYv7sdP2J6wIsoGX2+qZCgE2uo/HyEFNoaiYHRVpneB1KGZQAWN"
+ "5qNBBMh6+d8hacKBf2OdBQf+HMd1f0UcCIG3RM/wDjiDCKjjY1t8YlGeeB1SrZEOKMY01mVv"
+ "nn/hlKBr0WSg4LORJ9RQbVtSqD+KYMkxpt6vl9EOQUykW1wYgOh9S1UxS2gpPuijmlUh0QBy"
+ "4Bgu+E3D8ff9JQVurOGYVLkvC3K/cW5eRSU4F2lhv8erWwWcuJyy5k06rG+5uWS49rNdhJE/"
+ "s1jZ3vLzJvMtTxv3Nc6Xl2nk+7W4BcvVIM1O0frnXnKChMls3bDuU4rI6Szn5QbCNt4Atx4a"
+ "aJZyt/n9LtOKj094FHukA4fwGRbDZ6GdvazbxZroa2iwLdtnEeiNl9xDRYGSa0gnD8xHHhM3"
+ "T8bBQluSj9F1P8OmjffFa35u77v4Ju9GT7iy8fv+A3l+OqHyRpA88WHUwnWK01y9AxwYJk29"
+ "eyXxi062fnwr+VBb6zNOet0RE4CO3hL8qpkuo7WdV0ld7saCMt+8sE8eqPwxYGnfpPNlS0v4"
+ "ewZZWflDt0gYU8LUKvvLokn7r7LlPi2HGU7o1aVukInQE0teFUGS6+c0s+KRrRbXE1Sgl8bT"
+ "OwLj9zecOBlhsEN81mXTKx4XLdXlUX9eZGMJ+nNYDNdKLq3EgPm/rInAXdd460bIxTQO4EyQ"
+ "bVY0Vsmtd3qQXldKSjeuxMDw/C4fvMcu5YF74CkmRY8N1aeRlbz6te+sK5R9xlwQq+mAM8IW"
+ "x1e24l4bxldHLjIqQp+lE4qr9cHoDoh86YftxFIJ0m6Bkrw9WYv2jwQtnwboUIWCDbN0uHof"
+ "czs+Db4H8QopWwbcxCMVqreC4VKhKMtmsateiaS48+0nYoxiqDeBj4Iy+n1EYZaJjqizL8gD"
+ "okQiE5ipnXGYSincF9x2SXIeyLgIhjmRhocfPk0fW256XMS7jrMstswHy0IaAbTQo2YJKWx6"
+ "AbfHgcozKR982A2e410dfniOPmT8XEieh/az4GfBPiTNhbemRX1yJ9QW4U90h9U6nFeo5Yj6"
+ "HNqO3lzMg/PWhnZjl50vKqel3uOwICEhO5YPic9wRk+7ahbH2/iLEUL77X1EulXp8UPT0URq"
+ "RcQfmKrwXzarorYy2HucxVCXlQivwCFgskb7e4ijmuYwO4qgpq8zAbJLgQ3UVt60q64mhzhA"
+ "JbNDd4IsiORSnI/5lTENPqUgX/IGeP4FkYxSHwpB/t74PyCc+38vU4Dw/yF8xomSPyPiOzZW"
+ "EdF8FuG9dBgu8QfsvrijRrApLhurWlJROV4kATuEAtMDJMxBrrsa27zRKwYHNGuvQncfHU0K"
+ "IM8NNDl7SsgKzS9CHyp6tOXHQs36om3r2uFWcLCydWRbjLpvvzKHeam1KES7Qc+qXWKlG+1u"
+ "hUstLtZrcGdJqchQVm+R2popaPvoSsmAaxakTOKKDQghwkRxleT4KgKtW5RwFXJMxgHRuc8U"
+ "zDgHQIu/7GF9ptO7/Cl4vhgRePxl/PUntrljEHm+ZqZzmbBga0jdFtd4VfO+i31qP+fJllTJ"
+ "pI544jsT5cwfsm+aOqRutoWNbz6IOADhEFAm9SZ/fj//KY/5gK5tv+m7cWDY1SpXcH2Bx9qp"
+ "E9blyuDFm+J1MvR4S7xBPOCN9489/4eI36pu6kpD5jYmfMWN8Av0oiwLlRkKJwhuz1HpKAIf"
+ "M1UQ/exOQ/bygLxgwnr6JGFHUPK9k9iLKstxAzAJeaflEvM9+ZiXo+I3GBqLebLl4i6t7zcZ"
+ "BYPDZghQzCFVOFJty7Sc53HyG68AS6PGpbjCuhG32Xs9HZYT7gw+i9QcA69ulHO1RVSfCumH"
+ "1nJ0O09/smRwjX8YrZDnn37UqXVPGobkiY+ftx3ziW3qnHFYcTifpA6Z4HO2adn9H1lLwrDf"
+ "UvSElIS4IlklCfn/Nfj8CsJ6iO1TX4xpfyPTJKWQelwqx0hh3G9+Iomlgy5mn/Qh/3O8Gk8E"
+ "XlbJRufSi8xjELeYgTVOoEIoJBS/noiXtcNMaAa4u3Z9KzEmDzwmhN7reCEYnv4nWJEMmDSn"
+ "/oJeCwgFGoX8BTWL7aIeMEbT9Qa8SBhqc6SSufLZ5ULh1uBILlbWw6hSae8ZQUaGJH/nhBpS"
+ "UERHasl4pGabpk5xACqOcIS3H3+fVEQkXf2RpED8CdmJj7VCN+c4VMqDbyKQekjCoa5y9VK/"
+ "p5CzWV/2+053Q1hsoW85Lps1kHls+mj2br0/uaAsiixsa3c7ZrXCa6/5r2zQZwHT7Sew5TPa"
+ "a0kYvsNHxO1t+/qrYF0lp4x1JBk5/Y+QcxIZK+ZI5BkjQkSe9q3aRJEY7DGq1/DAd2hdkzxt"
+ "XDP/yRyIPZnpe4QXF4/ScR96Ad5drdwujploqSDcf40j8baMOgxCmh4qv4ULnoMNu1I9FXij"
+ "7an6Mm7D+g22xNN0zwO6cJiIeVYoyZKbku9h5IJmWeGQZ02uABxFbX/ODpLT8/fVnGNIqusE"
+ "2UFmJ0gTOCTizeaAHkxv/JiH6ZvOUA1XFByTK24cSFfmdIuUQxDFpCF51rUggSs5NgFwYke8"
+ "P9E1JJt9HA5mUm554hnFi/3APlX8k1PU0mDU+UZGN2TH0gLBm+GUuayaao031JZqhm7xn1PM"
+ "NkI6fuLOkRZ+CUSPVZSyTlolefp50Jn0T2i5qmNU03kJ1XzIH7/IVs34rSdIcsOfkxLOh3HS"
+ "YqtNLlbpAkP9PaN5YjbHPQoinWdDbdU4X8u54JClDvhD4n+6I3ZLjyGSKVv+5FtGGAqgrHVc"
+ "8J35xDI6jLwSMIiPeOZVW02kAkwGx0VF7Y8nHciTozBMFLNSkTYjpxdRkeZg0fNinnbD6XZX"
+ "VmhkY9bhy5Kfgth2Aqenf8ukYNvH+KF/ZeruBlFdtfVq2kkzSY5kvhSn/tPjRlgbCb8oV6u+"
+ "0KvbpbkSEG8+3qO+Ff8TK23JQEC/RZiE0+HvkjA+hPqM7CoUOSOQSIZ9GprtdKtvQ2sDcrRq"
+ "ouUe9jIRYIG3z5LhAIZKGwGpJwN5BY3tLhkncjbsaQur3smDp11pniTmgIuy5AAHGGDQWA6M"
+ "YZYEhXnJPhIgPWVPRyUcXXQiQDEKzwbmcg/IYSb5D40wd8vW6g/uWiFJ9aQGKy+IUou2C2aq"
+ "O1mVtmWGlZ+Skaj37mJVjx1kElsAGnsH/NwWbn8Y+dhMv7cBXYN82U/LMgix85UCHhKxioLk"
+ "GZTpOj3dqQwsTcmbptEsNZ3ZHdtYPYrvMlMJhEsOdq7kbjyjX4miutEzySA2cqE4xucNF+gc"
+ "b8UeaPqQrEIjbHsOOVNISF7Uqd2gIEfZ7eQWZ5eClmMhYmDCTPEM8SDxPKnmyLqB3zrV/6n1"
+ "gppSQTEZFlETcLoblCegdbU8xmIsQh9eH1rVhuSODLIc6FjletPy0GlZFnCBJkOZ4gWoRLms"
+ "sUySpoPSTuP7nmvYYF8b1KlEbpMeT5epUbuzfKaOyUBEJ3GooqVLqzIdkTnchTxWA9f9odpZ"
+ "9ukHrKqENSJgFKcVvybLmk9N3tMu+cT2C1k0fhq4MAo7S7eopeBMjuvVI11dKTiYeJjEb9AU"
+ "YCIFt8CKDL/1sw0hGzVJD4/WfUZNYyI05gbURtaXarDLT5YYg6GqCi/4LT5dCr69NLmx7Xgx"
+ "LEu4D/mW2xMrZKU3LQHuBel3bvY/DueyGQpbukc5prwFpLjRgmx0pWNg72siGSv5jX6/hw38"
+ "p+6VlbA+hKnv+OgIttuZ4aCnvTET1pKg9vOKiQ6pnjtMnhG7dMtzkW7iuO4KrmmfJWRf7Qhq"
+ "W8HTGusmevLczncDJ3QkpM5tvxTnjKtSJlW34i3YWOPBV7EGbJaZCVdVBgntO34ERDKERa9M"
+ "+9EqAd504udZUkZpEczCO9169O/hH5LUh8bRGq1qk8JADyobaH3RXrDwnbGLaVo8M/0ByZOH"
+ "uvH50bSNhe5TzfONz2RNaDu0uMJC73nus/XlwiJFYHjuGk3Dhi7Ja9P0Pn7nZb+p4/QdShsl"
+ "OnbJQUoEX1fUGOUPOx0aQyH+x6Mxgrr+DdmosQ6E+yYs4cXlsvowZrfI/BorYc/QJu8iiD/d"
+ "MWH7cNn50lQ5gTBhNPZhm2uB7xzaUUmb0Ul1x2+4dNyuxVE6RFaCQloSzcOsBvOndeF/pSrF"
+ "eajskaFeJSL+zFP9mU1mcvyz1WJleBd3nq/K0ySdBQPol4Q2d6HlSkZ06mzlqJVznfnlN3Y9"
+ "6MDaGvaTP1mIPRUNe2oQ81sfnMKiJiT7Gm+KjXOTh+iGBNvlaEzTu9r74VWbnbvEPRVVE3vs"
+ "WqTpNOEEis3hQI4E2SdjrQ4Se6F8JITgZ2PVVJUvEe8g8800RDPZqHjo/GbhPxKnSVSC5m+u"
+ "C6HPounSvFek841DgVMlqiGG3Pbe5P0p0Upm8eWp0TLkb3PRZQHccjH5I+OpznHnT5o3/hDi"
+ "83i3157NgdZMEpFl5G+YrEG/Ps0lDqWh8dQsjOGdim8aKtsY1tTPC/YuVSARdSlMOLZSTKCn"
+ "NNUn0VmaBe6o12MWvw/Pm1Sc1/gF/VBNIiOMyZeynfqtq9eN4olwc6iV5GKeHikTSv9X59De"
+ "18AhgWAowVLMucfbcS9MUHHt2X2FQ4A7NzLDCEARjlNdI7Eokb5Yh5SVGLhHjlITma/xHOkQ"
+ "MiHO6uaqrNrvoQPnnY6wwNPjd7zVpAoUvzYgv3+gQvBA1T0AyTkoGAQlymX2nPCmMKrW6XIp"
+ "ce1iASaTg8IqbVHPvUZ6Xn6RHwl9wcpzH5hZHJupzNgZThib/E0awQ6S3AORyAqksJOS+CQe"
+ "XMR4H/8NPf9zWixgBpn4keLVZIiPlEQmFhvW+8b4ysIVTPXK9gNVlf0L5DoTfyCwpwSmI23k"
+ "tzuEm4MOxsb/7SYmHBSLV/5m49h6tT5rguL6RVwPq2veWe19PPRuB1tG95W1jtt9yK92gWlS"
+ "PG09N0venra/x4ybQq91lbYft7ddMmJj3E25/ymmQbfN3/kd2nSQZ50rnYnukhvAeUa7cdyR"
+ "HOWalbbifCyI6Y5ZfXFD3KtPOHJyivZfr/agoFs6nLMdWwKejjZTyXBZmAWYjvuvhhm6i9+e"
+ "qtx7KdbEvkP+pUQxdroL7iCAUCXk8kflpuYgcNS8FLSJAUpqRmOS+N64JzWgYlw/S2yEASJk"
+ "9uw1JsBMfriTuLkQCwuUaxi3Ysz++Xe8VGV3uPzpSmOkqQJRV6fi4F7p10hK25dLEKExsZT2"
+ "0gfM19z4MLVdGFXKNXGGiF2KWbls1jKpb+xG6bGdhqbv9ZkccRrqD5F5ReFrQ/5tYbBGND9Y"
+ "X5FVC0EUdVwTwV5UZoeOL2QPvHEpW9M4VGXI4vjt1HeSJgL+LWLGkV948XwUm9lk46rGT6/Y"
+ "u4lwU5lKTSBG4/SRwLr7QFdGN0Ffr0L/UYGk7ErFmFSCqFmA6MhHDSYJexb4hxV+6Bt5As1Q"
+ "ko59cldXafgS+JnbMRDJXtxw6uYg7UrCRed8xF4nTwKGsaWqeLEv1bbOEtrjOT+t0xntX9wE"
+ "QuGSH23iFbTmZEv+vb5FPnwextlQWWzrUr8wZgFRiIGABo5VEJj07hMj5Cn0fqtGwl2PJdHi"
+ "Wv4t9PDZoP1m7EEg3ZWRfIkyu3KhVSL8oO3wB6MZuUEpiwM4I0dHjocBeh3BgPP8NOvUB3X/"
+ "OEAvI/B+I/sTjTovJcwrktQlpJJletXnMAZJPVFi4EV1WH6jeBkGl1YSh3Lj6L22SZ4Z4Lox"
+ "eUOMsCbCxYjOn1K6xxhiYsOL1CxzmzBe6ZDr1j2Gv4BjgTssKFLSdp+b4Tv2AdLgz/Y+7DL2"
+ "roD7Hv4GbosQAvcuUhafp5NANf57tzADVIUnUMTMtOdqRJisZnHmaYhqdMvSe8mvNwW2SDaU"
+ "CQhOteoBfISiPlw8Lrj16x5JBT6C/WasN/mnPvb91fgDun7r3PR0VGu21kEsXbIVI/w9uiRE"
+ "FCfnqOo5cp84ZlcRM2EaygmPYmcqpaKzz0KdGOIeJyZNihJ5e00RuX6KeGKjw1ugf6OUvTpA"
+ "7mcoPKHw86H9BBUd3arPNmdWhw/I+svoyCd6vSXie4pbew7kMiHUiJ/gYDCk2XQ6iH8yehWi"
+ "E304GTHUjZWbMjnM60+tcDuGR0jgv88feN+FMPqaY+NsH6OnroCPQCIBOyxUdXQG3Xav3qV+"
+ "QYjw9I2AID6zCLZ38qG0YHQS4nYsC9sBlz+ps+ZT5pTj2/FVayvxDtDGEZ7UNT+qOomzjW06"
+ "oh9l8QnvBal4qVYJifzGVUtOPwrw0RlgKVXSz0kpiEIFw5nnDLkUz0xBRAVXyQe6ZyHmnFx3"
+ "lcYYFEGQWUHslKyEvz4TLFKIsLmPu/Oa3wuK+PxrgKtjkM3OFABzcc4byoT/esucmP3p9zlq"
+ "5pDJjOa15skedQ2dFZB5InenaTfOqk0tp8MU2F/RlX1nW7JGeuw6VPZ4s4lq3rV3uV70BLZG"
+ "pWTo9Q1lDbkYKGFki6nkY2QIzO85ymnDOz6mbC+cvZuk/M3jYzpg/+2AGVkoiDk3sVklUKpK"
+ "qQr0/EWedNccUvCLJs1MBbVKKGiVTuoeLTz759mzjOuXdOzTK4Aexb2JWwktEu5b1lwSgYAM"
+ "esz2MsuRicoi6q3LutXx+/SDzdRm0/Con/AHdOg5Wij0PCW/YN2D8ov4e8ZlTOV72IBN8UKX"
+ "VYQz5XqfVma3yiwzyhnkrZXOcg3ruxSW/C4NIZkDUU/BoPrRF5RCOdQ9wi0W5/9wI3j+99wI"
+ "xP/rRqTryidugjL8HRD3m0ET5gRxee4A8gwId4o+wgAMAHnw2ghFMtVPmEwBCVIz1Hkl52JL"
+ "QHHePMQvLL00vFhJ0tZxEL3mX3wTTHSpRfz3l1ZrKyV61vJ46h6OTFZq7lquhfT5Oq2mHg3q"
+ "ZzVTa7uWZxOkHTk57YzuNupVnyuJ1JEOKzCHZssKNQHLprxrAAKe/ZtBpmmag5x73Gh1yM7A"
+ "+9bDGi4e1dqib12HpRxaQ7xYzUEcEfUrBUxthcLU73SR7RcBnliuLngtx5MpSIu1FxGeuMKY"
+ "HTnL0dmUgOqp0SBUlBUqrl5/3Y9IfJZQ1fCjQQlwRRaLsZw+hu2LlZ5uzuKXssCrlyvu6i8C"
+ "UIQ1tKudXP41JDDRTI+fJC8676mjj3hk5cQBGX1Dfn3bXDFxvHsKjt1jvccotKthiLEu+HxO"
+ "sy96+aHl6Q994EgFqVSKQbnRqEsPQiMSGR98be3ziwxVtC5PmezD2ToRlVlPmeMRiu1e5L33"
+ "9stXyA5PyHnO8vfVNe5iPU9eqvtaih5HZncxyEvfo6Eukmhif7guAkmRHZ919yZTg8sPIdOF"
+ "JJ+jcbIksSbsmKnS+vH781P0SwPSf4hUv1I1ZAvKlUi1m1Uv638iuRVSa5aPGqatuO5ioh7k"
+ "iBvmYhLjFXBrwL+lv5X5M9RVVnBZfmNcqfFn93ZCdnOJgDnHC8uXum6TTOI1BqqQMkF/ETQW"
+ "MXVl0goy02ayO5OIfgsTecimDNH3oGPCE7ZL87CJc8QyKh03LN1rRYqAj3Nrxpztdxz611Bt"
+ "1JIbjL3sahdNhaa316fp5gl1loOTtPdd8trjpz3LR6+3X+1zdLdTz/U36H2jilBrDOQi6ouA"
+ "kyFAckp0MFdo91ciaEkr5sqCGu+AooNITb27URD3jQA7SFcJNxr36we9/3i7a7HkGmH1HskG"
+ "aZJ1HAZH1V1ftTjhOaftQK3K+5VY3IEA27c4/0H2uvxfTH4b2rvTn2R1+WEA3Zh+Tu8rsUQW"
+ "Oojuhx0rqVAYzBpg+L8JiNjt5EOZsheMNd6v0KxOkKpFBup5fOy9CZsTkv0SCj/dnuT4mb/k"
+ "GArBtSQZLd46MuG0GA0CXZoqGJ2lAQmSZZrPNzmL9C8fC61o8awjzkEDARY6441/u7CJPGy0"
+ "lmbhwQ0g2Y6bJrArh7zYHMqnwzt9bg8GgA91103GOMKMw5Dr8bc7HcXbst2MlmE+bTu4SIUB"
+ "24+jSoAY1k6niSJU9Ess3/oRgziVyUjJ059SGnEWAvY5UBIzl1f6Sp2Vz89ZNe/EJcMGKtCw"
+ "xJsX4dUx7veTCYrObne02bjkC2uKRuCKoIw0LJoF3e4+FoNLxbsCvneG44Z7CMgXmiNjtMhg"
+ "IT0EtDQOJ7ThZWHiJ5Mvf7IvTCK8K0b2wqyGBXaHCM3fi2RwjK5diogfyMunoNu8k7lflnKQ"
+ "6v/2s383kwqHYGcis/vMGREcdjIayYmBHgqC6Q+GPYJanh6jtDT/Unl6CTdPtNZG0mGLiziG"
+ "Pv0eJR0+wDgq7tc4Q5f7GonPrCBVJMUkfO4gzeQgb2GhDTt0aiVDea7Hc/GMcgMklOtFis0K"
+ "inLfBpv+7RP3zueNRhqOPI8O/mYj/xmZLBfsJV3N7pXsQTaVT1QNC0CvroKah4mGSParJsNo"
+ "OJuiBNeeSVJwsBgpYB9LGBfMm8xqnw5h1dh9AhIK90yG1MpmWyRcy5vDykRV5KsU5osqJDQ0"
+ "4FmovtrfjZoIhdGzigpthZlwj89QPIvnum+V9WDOv3AeUgYjwnlBtm3uDIlOMbi0oUjMFlDv"
+ "pqTFqG0ogX8yVEqH2NLoxpQ2kN+hYuHklr26JtudXGoQsT3qGZSgRsWP9ItUHG+ngnK8XbgV"
+ "ZQe5tdFjzBb22J74D2mH9c+adeZ5OT0ji/4bJd2t4cG7Yn2avJ0zFB+NcA7wB2msw9+gO2Bg"
+ "XwwEnwztiOYDUMybDJ7qLs2zv9xyYwdRlHgBlnj5RcEaeg56TdQnTfgSNRc2NpAqyB3cxZfH"
+ "ViWDTpFMtkhiT43Rtn8NgYvZQr7b0gmIx6Lz2hqoNakOrCHHSk7AmFpIRg+ViuIsoCf7Qyy+"
+ "MfgUL6Cd5S9eDJv9pwZnQQulLP4016Zjiww8sXL77+pfBVIrBXMyFx1HgOTYFxSE+HqcQ4HN"
+ "CIAu7Akolsw70UtynG0SKy1Q6sx/neUnpXRw8TM36xKAIRpGfZ2C8FhFlDmegB4gJCpgM451"
+ "wG1YhEVuXD3DuFubP1/GztF5oYiihzRFlatfkoS3SJE2RBH+M1ghGXKo95oxAdpQ0gJOCSbJ"
+ "QKC4spiF6VybeUPBwkaH4VShYXHPRqaP4B5YleQQvtnEeYq9GtD0IEFV4ODecr3n1uGYyvLU"
+ "MFuGswd9SXb3Sx32haKg1+ovNwRciS3R5u+07dWIq3qaM7lM5gJqPSGZASF8qUjQwknXS2ww"
+ "rLmIxAAZXfleBXRT1XIXTWL4PtpXRG592kmIwSeadM4lL1swCmHIt1yJzpOt4j5O6nksw3IM"
+ "S/E4GgvLv7+2cjoCyliahmwPFDV1HsdLz3ruu0YRKiCPoWFhavG4bkgmIaJah6SUGmrWPCN0"
+ "v+GEmGMCRNDnHi9t9Z2iCanv5z9Qiv1k8viWc8gpXvdXJgP8UTCa7HmvvlhBb9bADX7sYYyQ"
+ "/ZoBSrtqsdlO0MCHVSWImUjtH4o4lwlZVrSGUE71YIL6GjthMvqUatlU7CBP0p3v9x6fH1Eu"
+ "Re4UXEUD5KxZiD4Qj1gfKAmkjexlcCNN0bts857RMHkYxHIVqrfe9bZxiIrykkuh6N5FEwK4"
+ "i4OlqTKWYj8hXdJHmZNpsYe0f9nexODEQhZfZL53yZY2adeiWZCElKNnYUT+gzMFkRg5s8+Y"
+ "TyDdHFyk/TMS47k6nAa11Hx+czEcSv3s1qotdIExLUmQ4tIYcb115gUNVNnLqZkaBSt4UvK2"
+ "GOboXhEtRzBQ9vYElojLkVTIJCwoeNESxuBAuCHpRJptq4WYKl36QLJlhohhCAlhnkVCj+CR"
+ "gWz04IFZqcVIdNkpyb5/jjcKnSZupJNT9Jg9+QqREMDZVLN/nDYsYLvL7XROalWqd8ZKTF/g"
+ "I8aNLVdTee9bnVGPSyl/3ETAdeA+/K5NIn3xpLQb8DE1/17vezuza95GYaNr1Nb3JUjl+Gn1"
+ "N/MaV9cJPmec12LnjzbvCBQ/9ko7RW+YKHlOcqV2uNfOPq0UrAYvuuK02qGvpFKXEhyGpeej"
+ "IVLG9IfnGtKlAOzBQRexAU7Gm9VptMIZDAHUxZapAgk5xKofb5srmXFDgYG3JxZUaIxMuHmk"
+ "Q5NZX+v0LDetOyg5co3ABZJp8DnNWK6JsGitkIhbSaA6DFF61gLaCl/aCL+KmDI2qjt5t0sM"
+ "XgVRtZgB6QiMK2MIioeIpUph4N/00frIvhm87JvUlidw0SEW9ghRFxGve5MxcKBrkqzqsa6w"
+ "y1+yxUp2iZBBXHugZmlwpKoO/geH/XaKjJFn1EhKMLzRUjNcCV0l0dvoFM2Gc9+7zb7NaGCb"
+ "8pzIFw313danzoMEoc9CA5oEAbjB/eBbz635/WLrPk87lAB7IoUhsgJ2baDRr10FO+z1ao2P"
+ "jFafUe1oH4NpwWZGSopzekTo573JWwlcyaRp1iMKBnSw5dzYHFQm1uMyEkIKb/iSElxFaB/F"
+ "baxNcEVSzYowi43TLAgD31DpabApuDuX9DXSxvyI6jcavEeJX1hTKyPHcfyil4WOp5NDkJhV"
+ "Vaxj+XayIeeDuzI+TFTLNmPo5XZBiNhCLgiLS3EKImUqrILs6i96cYT5Tvx0b94nBUclrSC0"
+ "D1moP2Bk9ZBatMadp70YveiTS7BUteyOc4tZPo9cy60ZHwcKZV5gda2XxipfVW66vAqolFmE"
+ "9xl/8279Inmkscay61WFShaBkiEj0Duv9TZBpQGnEc7tIS3RTO+H3PNse+hhMwq+O8KjyiCw"
+ "IplZRImBDae+i/csiK2u/EfA0cwnBC8Fb9b27CtXe1GJEy4zKrnQ8aOSEFSotUo5sUc4BYWR"
+ "/95CgHqBQ4+FfsB08SzyZgybFyj5+nDaLyEv5p6OcefnuZFnNxejcTJZ+KfMauMFDwow76Mt"
+ "jme9CSLtpf+FeCwhuhujEpfkrcJFro7mLh+CkN2q5M79RJdm8eY77eUtE5tXdOwh0kSc6FSn"
+ "7to3KaPfPg1VslTNTEWOedJ2LpjPL2VJEWSsM0MgupmseSSjrTi9jbG254X9L/JxLwdaNRyr"
+ "wKX2mcj1etdzh3fEAd5fGGibi9iXP+OcO+AT57wSWwtU6fNVGHgnEO+Ey0eHyfopVTrUQcYx"
+ "j6q4if2gAOgcnwbrXzpy26bsIKvPmfyZI76gpxQbYJqeRYAo1ipNGzebhh+xoHEsbu20S+P7"
+ "+O4Tbzg7PVsFOV+yTixv0Q9Lzic5Bx8f+5XO3ZwC6xgm8AI4lBi0a4IjhySBE4njcPxD8FN6"
+ "mfAIIKJqfpqtpr50u8xhneQdeFsoXVVfLLPHth8FJIPOhOOlkNqY2F71lTTpi0L/T5i3/RmV"
+ "UuR8Jr8dCtdjzbMYRMTgeQQGQNw64Pyst4R7GrE1uvjGy83nrTjNaqhKKV2Pr4HirbN8ssQs"
+ "hXXyJbiNbnFS+reFY2RxQZj9pBINteMsLNIUaPt69Thh8N45Yjs/YrbKiqZ98cn0iWa9TflA"
+ "DrfEQW6kTyHzp3QKo0Jm9t3kGynMyI3+zy1OBq7W3XjMHO96Y0Jpzfgmcr3VjcxI4b/dVmGO"
+ "/VHqgu+LRaO95L/1LcK+g55Qmr+DP3iBgWKvvPCnmyzfk0sZJvQtcn9fLE2Oi8EQ6Ox6iktE"
+ "wriD3oFj/5RK0XQkfIv3mMFxuZSxpAIQF4P0Bkl7Y/Lj6hRMrajSmBLGbkKi4niu7a4lDz/B"
+ "6GPQsIdAOwe3GgUDIvK93PPppum9p/jWAhEjWp0ipEL5l7Z2eVOEwL/9KunbZe91FUO7BMqv"
+ "9/oMdNyfxY/LPoeMeJ3K/LrgiSurCuU4GbzPmG2FndI6ivNeJ7l3VPRUXjchQQ9btv13Aemh"
+ "0kTyaLf3yZszpaZ7g7grWvcQjkiZtI81gnzqilSo22zj3Qp3D2OJ9eSN+/kjcdEC6lNsV61d"
+ "hQwGkqO8jjk7sm9HOeHETMORYtvBgyXHw+omXXfHkWKtc/Ras2B+G1wPqHbL6GTRdmltuhFF"
+ "cWS9jdEbonBANRJs9Cxj/l8uJtptt9ugNMpI4n65l5HHxJ3lGyKrg14M8Me4nG3IzwcQeyz8"
+ "LY/osrvVX0gumW+gclBndpF2+FMtiPFVHAIiZEJJ5oI5tKFTPdyF2GMaetP18iQS2dL+vTE6"
+ "MLspD59ikWGIeDNARprCMGl9gPyvNEfb29w/MJTfAHnffEYIWdLBWCqRicNfvlMhNbFo/GEC"
+ "iWuc6HLLU4nAQSIppPaAZGtB/whCNsJgattcGd/1Q7CDHiHK+EYlb/4oRi2XivUImhPyt+Z3"
+ "VvbMwpy9yLro/2FH8P7vvWZS+//hCNGkzRWcUdLhb2kYUt9RcMCmUgqZnii89kC2D9VbDC0V"
+ "UwgEBdSKLGzICkXeF3iW1F/EyMn0T5RZccxpKXHM0xF0W3lul/vTdQfMXXkvLs9HfW5Xwk/s"
+ "BtkRJQjKdvZDpSqOjsg+WAVR6I884Uzh4wuQXznOkzOU5x3rZqnUrs+Uc3v3E6KW5EICKTvQ"
+ "xtL6zmGNs3l/zDhhaHvC52QD334w8y5J/ZjYWHwcRXfUTcDjc3YsZ3bsbrJcyOxgGF+8tfOH"
+ "UlPCmdABa3Q0gByadsXs9MFAIgZan53MCJ6zjjG0VzuG85XIPbGKxRxCWsUyOILE8Eo7xvOL"
+ "PHxPOJKCm6qMp6Y2Edgz2UvFMl5RS1PARIbx1j5Q3QlQEV7JVMeM9WPsiL2TiHZJUi0JpkFo"
+ "N+0B3+XHTpJP2JykaS2ULTASWFLvOO3Mfs7tsaOT7N6liqpEUYzaRXjpETV+zoD8lrwBue8s"
+ "h+vvi5yYSq9D4SdkNWV9nmiBTS9GuGWhvTMEKGTeQf0uzvYtITLBT8mZdNWK2pPbwXHkRBwR"
+ "TIRZgCbqxM8FKHwNP6B4wP6ohro8AsiOGG5mz00WWQdYB8CbP/McPN7ht7BoeMLw8xBgo5V7"
+ "arT+E6Ym8ouMNDNFjZbNGc2UfXxNmJANprMhxPz1qVdGLKQhl3LXbTuk3vP7529v3JZe4Vov"
+ "U+G+FiniwnVAf5TJWOtd3TEGqSlUS/w+6TlsmdEf788nIbPQDLUrVyhqcBYWXZ7FDnFwL9Iu"
+ "UeweEcM4U6W0DFiY5WpcjFQ9Qn6LEHUoBiOa+L2guCVLrtpr53j9t28Vd4LG872Q3HfqyZwl"
+ "CYkI5Tk5TCCLk9WwcWbWTiANDGtoLr24L23Wa1AmhPofgGyEoszFF1/qWDw4ou9vyI6SMxjm"
+ "ig4vG3ICu4aNimMZXS/uhiKGAhQpExoOEVqKIZoPUlo42GRMHPF+gcoGVT0LtCHrGeUV3wUz"
+ "gYr+5GabLAmTCcJqXqN7tChYXJUdtd0s2EdnUFHsRMGNDMVmFuJfx8dDrel0YYoXcFFrTA8X"
+ "3AUfrwDa6KCl5PMpQRIdMi4723dUFpphiTqxqKYNSP2y60HuVP3Wkn7P3KLaACQIih3Ot3mK"
+ "aDHnKHKDgjVuJIgJ9R5j1KPM/0hXko6s98yVL6iA0vv8w8rfcrTGK5RYCzXzIdZjt4L4iokD"
+ "t6M4GN6L2spTU/VRM8iaWhYDUeFvw6m62Hm21lTChDLALcsiifSSnQi/CDU7PHYVHDYRaVZB"
+ "jKULgDSTZN8f4qOGQi8UIuPbnNaYXlYqgYMGcdNjsyf8WlWie6s2wzySFcCiN1zUT/mFBhPy"
+ "OaZGgHmUb5f+4wTCljSlPlXuVf3imN4/OWJiUBAxrQ/001qfZigYw3RwNClmDFvbUy36HpFD"
+ "znuQM8ZEQNYb1mgVIfIEK5Q9fzIZfjb4gO2TgT4Zko2tE9kSfHqdewKqAaJD8wqp/it/Bljj"
+ "Qk3xiXGbVKXlEa9E4JFzCkH//qAh6w2ufFYOBDIp/JCJcX1vRf/0gg/rXLvDhso+VzkbO15j"
+ "lwNybHRcFoh0XihwXJZUnET9e6YWjXQNIZtyGG/601rkQB9en4h6jdOhT57xVHsNQz2RQuZm"
+ "CRDCmmToZ0YrE0KHXugeyWREDYFngL0zAa7REYhyVuuwi+29FgxzplJ0xJP+4tOEGktFDBpZ"
+ "oKBM5QsMvo5GjG75uQy3DrmFNUsBuZaZG38yfmQ3ad7Ir5wcQC9FW2IngqWFjM7QrBVJTW+Z"
+ "7Wv3N71K6NRUce4PJ9R81L07Em3ZpRUFM6W+nGrWczdM9QmPRabcnd8uXZuQ8qAp73QXWXbS"
+ "kMmMgkpFloPlvBLIGAC6fFpMdafw0yvMUXWViLeACUXAJLDNxqyWtUPOvVQraNY0BHm8PIKc"
+ "QA3MvbzJBWMIh8csP2SHSdJe/dQNxS4TnG1339j+RHXawSRqHbFvxKBvtY2fbxN/oH1+Lqs9"
+ "kmJ85crGplCK986/yfyhNnhPOtY3SYIMY91Wxnvg/vWUjcNGyyVHR3cNCmJuqJD/Q0okmulY"
+ "rCpQaf25/uiu4be8UumJU9whhGrsMjRgLXjzSysErp6SVtpSkuWw8mZlheOIDMQmau+cPit+"
+ "fG0KXq1vEU0t7P0lLKeYbLg+cUrHkLKrXOZa21reTVWkf9JtmZixkbHl2khdh8Q8MC00Lv+W"
+ "n4+5llHeyUihgmO1GeMgV8PhB47XZFaGjdS0sl1E7gkFpkVosqOWUU8qksg+XSdjK2FbEsfm"
+ "sEd1zWX7SZ2DEMcCEv0IfdgPL6MssFXn1K+tyRgizRNdCHyIppRlHxBUAwjpoblha7GGljnA"
+ "CjFAdc7dkWMmi+JAGtVfKU9zuMIjShvGvPyxUpm9A0idHWX7mQ8diqeemFehR8/Wbt/ajNiV"
+ "4hfPax/nPfw4SWJ+wfag+PJSdVhXY63Sr+5rw4sk3an2ny74ZJRmFE0GJgRbcpnjzxlfZa3d"
+ "tQTZTWFuMy/jJP9R3CrdUCthvVHYYj1HgWiJsk55konABzppHmDIla8rJqdsF7Ocg14H1mqc"
+ "Tj7Sb324+pB+j58vLfcR2tgVlrGtUiPD2YNHZqrICHwOFOiWDwooZKXtsfDRZTiqJ62PyYYr"
+ "tpJhkdcGKCZt1K0kLjjHV+/4y1QHiDCoQpQQfhCCltlOKO9saJuTuZA/862dPGGYuSPnJwPy"
+ "TU7AwBIbGXyqFszmpnzZ28/4OlULayemXEltMlfzc8RlDO7Lj6hjcJtkIpcyGRq8ow0F9wAT"
+ "SUrWxGIpEJgAg3Ae+gL9i3MRC1wHjoqjeU8c/U9ZKW4FHUvqYZVQN5d29QRe4uURtwZ5MdxO"
+ "ST6KCgITvQgzo/pasfmfe8TgAL48y9EJdl2D2tspwJGhUKpaMlPgKFqmLgSChXg379mAAceG"
+ "PTP1W0y9mUnCaNUWg5er8w8dDhMVKPu6iV9LLnNg2BDfK6wYpQ6lrsQyupd/W3Kdj/qJL37x"
+ "DkW2EMXh+pf0CwMy7CbvJs+rJBC4AYRBGLrdSOAuZl+8wVNSoe9Sjfh6u8caxAxjc2Kh8BNq"
+ "QSqkHEws9CWfwX0VQ+o36jUhSokFttwVyBtxE47z7jwVCaDRf0TqOTsSqhSSFIoUakYBZAEU"
+ "hvX67ot3He+g/xaiW3eqJw1xT3yclpIerfD3rvI+AUx1wa1WVCbQWBIo8z+1HwR8TkY50jSv"
+ "ux51cNWM9axmJ+B4QKUJ1Vkn8g3r/G3lhYZGYIbFQIxl9Bhv0pVO80m5WaHjPbR3LdQLTOuF"
+ "Dmn8oVBx1Cwza86K8H57dx2cV8doqceasNSqoaIE0mCKNvDOVpHle0MNPCIe5jLU2Kji1vS3"
+ "jds2gcoNkr0wWhZMMfh4ZPnEw605JUDfzyXMjdudi60zzOsv0bcQi0Y+emRx6Jr/pdbW8TjL"
+ "OJICc5DfmOcUuWqT2Cexebvabghj/IvYrl5KZbRzi7CunWH0b7N7xHesmJ89Ij/oT7zZSekG"
+ "1F9/KYP9e0nxhdJcletvuaoJyE/yQOPqAuVYv2OwSjjA76jUM75rmojwtHoIApjcuHZvw2mn"
+ "QO2HGPeY+AGJlNobpgRWfRYGt0alhQmmv6Jk9EpwLCUiz4q1NmVYr2nIhSkNatxEQqG6WDh1"
+ "nAuurlPcmo/63D0kbqJTW9EPlTH29/vYvH7KI0OpiLi/zmXwbl3ao/vcOuwcM7oGIOGrg03u"
+ "qIhRC2WzZE9F5Fh2KrqLcCrwbgj5P+NLazMJrP3hXLSGXnqrvoupoNhtTkM6nSqqKim/vO5u"
+ "vAJavozzzus+fCHFB1QSRYuMVcIcsQrrfYRx3rvM6rsQpx17gJc68rvfdRhXfn0K9vk0XU+V"
+ "UZeCZGAejHK9pmtl7b0kL0iqFQ4KkPamV9PuN3HOQcGl3cbJxXJbQeNV5LtC1BQDYaRVR99U"
+ "Acm6OrJ9MfH/VLzFwdLg2k2Xf3AtB3Q1pXmTO2qUuzNXbZdBVBxKensuHN7L95UWHlR9WLEB"
+ "RswU4EoNGm1GMs6SJyTXXCdH6RQjC1DZXbuyl7w/VCroxGJXXjNagTa/WjVIjMGwkvP7+Bm/"
+ "/X38dJEUTN/+ce+S61ALlmr51b/MvrtF5CcufmxLUqPwpvVEG+ubrjVs0TLN3/O+VkGpr0ZX"
+ "YbpyIoFxO4YhineX9KbsRRe0L7OypHKfcxcZ5V3UN7xo2UYGLuX3SlLdjkOMtL4Eyzi+HmIt"
+ "Orv0jZLP58auzxvtRgAQDlUVlvE0dG7fa8TvmtUhuxPDnxz/vAWK6RbuucTrr3zS4l2Y1+aA"
+ "uYrOor0ea0/uElnO/ml76UWq4lZDCbI3UJlRVJdGORpX2m9vMLSCV1QaxJHZQG0H7dls/+S9"
+ "H5dn8uoX7OP2HemR4iv+v/H6TPs5H/H7+33kqnIod0dz7BRagqzPRagCadsh9eM0xD3dQfLq"
+ "FA7aF4in7VHhvrk2xJJeZJQ9NDA8E9JUnT+UoI0RhrMyKEYV/iiXi3SpGat1rQlQ+2sXcYhI"
+ "MxcK0MvagY/P5LDgXhOhfabOv1LiYrdMBPP7/wfW8f3vXZmL/h/W6Qo37st5OuSY80IgLenx"
+ "gSF0+4qh4BBhdLqIB4MOLJW+V4mh5xZBCLDVeRwODr7Rb0BD8bNdf/4MHUIhdjLuNdnQybmo"
+ "Werl3I+CgQ0/xKVOOSqtmS2OtVR7RCNXfDCg+b4oPErzwT66XaPCJCdqpmbFkLH4zRP3q1c1"
+ "NUdzbU67vC/nAv/L9CWOAxDPmK6dOSfG8rCz3B4osu+wUd5fCzmxYFX2UPn60mFQlzQzAHky"
+ "3AzhWhNAGiKUoQ51MJCmFvAudq9AdUFIuiiqX34fbzxc/BgJKuwgcKnJC/r4cX64+iAYsK5L"
+ "MVZNQaJ5chgHePXuMM/0HIvaUEL79RK32BATVNHjzOOVOesLv2q4xiAU0fJMbZk3QH/mZK05"
+ "tiS1KxuXDR/KW31g1/hN5l2RESi/pmI1sspH15MGhNyrgNoHjcD+5vOHU9XCWKUPM6u7kcHS"
+ "SPgK/LErkDpEfdWgOlabEz0DKob1gtyzLxcMahrNFdQYdeuGJBjWwqHJm41I9NsQGfosItmN"
+ "j/ZUBUQvRxgxQPs3EAJf2rQROxPtgdLu1TnVyl1JfkIUK4V4FK01tGHKWCT8L4J7V7+Pcu3W"
+ "G0GDXosWMOyyyebCJ99BQnsE10EbHNzqoldZpOJGU+IeUytklbSCbS6FbogHoUcupyO+kod5"
+ "AqtzLhBmW5Stg+9FmNwMGPwBs3icm9v49o24mkmtQHdVGn/x4CiSQg0mTFfHZEL/24fQr9NJ"
+ "mnAhjtRqhY8A22uEanyszzc3CVw7aBnu7sIbm96sGmv/BUj+vuMio+mJQPr8dXrhvv4JwLmd"
+ "cHVfgFKsLkI6Q2FVr9KP/Q6CyKZGWn99HCmNGmKMBFV+A6iJhgLYAc6q9niuOZwYMe8KGWH6"
+ "tBdckPjIeE2BgNAtEOhxp5p8EXsPdN6jJTuOxfEZBdwjeKiEX0i3w39kYXRKQXaXt4SpjSOu"
+ "Jh4mssQ8DNeGAYLTItTHlam7xfQPrANNffQujoTNnSWi5agI2ZN5bIRQWfmdTLDG9JIHF8WJ"
+ "0JurmMVVbnAx2qutD6/tpX+D68kxADQeVTKHfegH1aj8K94koz2JlShkpdPHPEeYyhHbHv12"
+ "oUaeK+kToZcuk6gKIRwMkOjDxzYjevvzbYyhrLArN2UE9sQ5plIZaOHtrBNpYM+XS0HFTDBP"
+ "IJlewx2KOHzIN9R/Le0ka59vBfwB5qO8khhefPSX83m9Z7ge15G0CUXzX0AA23SEWd+Lt4Qm"
+ "+51AVyuASgCxmUZU2yRzmybqtM92JtqONl8plIKjXCrsWfZqwhRRsYYcluEU+w8ZBjmENnWu"
+ "//AAdxfqLHSIIrVskii0qtgR/iQ9y9pzSYJJ/UlU7s9R3JOl3BP/KkG1RGZ/Pl5zvIaM8yAp"
+ "cSn3X4CFG0mhSlp0dAadbpLutZxpQcf33PuGYnQxqyaxF5TsLwFKLPfdaVS+Y9mW4OTyZDDm"
+ "7w+mQ7yPNCY+MtALjIduEW2hoz89XMb5DkvlPbSZyttNDRze9xo3J3s9r26u75ZdiJQz/+iS"
+ "zrDboBxNSFFB1cQveRHxJlZl72CDNmla7CHQPCouPtTnF/ojZPfeGFtoxjwSyt6EP/gZxRYe"
+ "xSU1ur8qFO7AvVTucU7bIohF2mvrlF2rxiYEGxBbPGh+brca8Vf3Sd5DrU9UTGiI4Lxxgcwo"
+ "zJDrGHDB54ifX+MZCyDn1iecp5mtxld1hqUmpxTGCEwxE6+LafozQKEmrQkILsJDP5T+wgyZ"
+ "5lUWv/wRFJhOUYYLQvrh23H36ECG0H7A1e0scahy5GlB9MdF+gv5Jm0LgL1pHTUEvTzURLZz"
+ "aG1RzIR9tqUSrY4+w5zx+oRtX48LCpUies/CfZRerPP+VLxhwVo34XFFFVLbiYFnoibujK60"
+ "fMWH/L6HR6WFDVUNccbxNvqGrBuElsntVp60Ijr+Yre8cWs2eNPoUR0J/0i8UwU5Oiu6+NMT"
+ "LdoQaWUr21PHAa8q57PynrSjVAwVyIQOZSHufnli3C/jz7hNvoi1ENo2AHx8NKLouBYEvpsZ"
+ "Bke09Mv7udHy7jYkWS+96l8kB2vvb9yHjgf5cJf8JMSIjO1a1RHwCsNS2+iyc5atlcw3EZk3"
+ "RmWQ/90bs31jk+yfiMVjCjSOjkYw095Rx4AuxmIARkuxO++RgKy++hhgA9N4vLuhMEbU2hsW"
+ "pa6eiJxFZn1GEhxljB0XkIpbGpJ+E7z3bVQbnasMUqvcnEf8qJE/De2urCtEMaTlJyUVpJil"
+ "wqe+jf8iCQn3oXcyJXsFiGP2h+lg+SAiS9eYVAzInJJTStU5yJfRQqa+EAkS6hn86y8qdfku"
+ "i7RYWdr3dIITwMB+XISf075PIi+tAD5zvLbWmMfWvAFirAeaUssRZyMy6kYfx/pmnRSo3KjP"
+ "QHPdC8Ii7i5+Jd4LVr3RQxRgMWzp21ZaLC7TNTeS2/e9X5a8TTSoiBiV4+VdWq18Mtbl0IEJ"
+ "rZ/JSVEroCrPIHDLAkmtawgcRQmhnYaZ/JDsoRiOMwyXLCuOrWyE0SustEsTvWbgoRsq9Xm4"
+ "+QKl9mLLKC9U0zHmWjKNpybmh07FVILdo4kw+IoTtdzF9sV5Zo8zXyyCr7fYgGIzLG+u7jd2"
+ "O8OshNAer8iPA9Nr/8TwrP7uFkl7gY2CBYgIxZXUqT6lZC9K9+T/DwAAAP//vdzXMxwAowVw"
+ "ZVl9EZ3V++qd6Fa0jR4RFtEj0TtZvZcoq3dCtISIEmz0XlcJUaML0VeJzvc93bkz9/3+Aeft"
+ "zJyZ38PxUE1IxF+ofZvD1pds5oQbaMKBSkk98dEulnuUzcOfgVBc9Dw1sesttfonH2MxHeMV"
+ "JSfDG6v2Idhj9lP2MpnP641yIVTdwnrd1ymGllO66jwcq5fPdcKFAtNpbvCCltUo8t3bHD2e"
+ "8keK25ReN38hZh/zTNL68oTqDIj8FZ0dC4OWK0UBRouGIoDqhBa40N5k4p7Cpx5tlbkViZz3"
+ "p5+hir3tF6mlOHL+dv9EdRHq+Z2G3Jcm4j8Oy4T+BiWO5XOG1tiUv8zptwkclWTYT2bT4wz8"
+ "5XW2Crk1knb3L4/aYaV+TGNlTKO+5+NOovVqtaPhOKi6mJLZ+dsFLVV1CVEtl5sxx9pRSwqQ"
+ "Ky/E3r4qrgr2JCtX+s0iMYJJVHHSMw+pXE31ltGM8NBK7gGORqv1MMeW7M+sslGPz8SfzPle"
+ "lHJMB6l5d5ClrqroTeGHm6q8NmUdTP7nZ/77+5thDdrLP3FxlG+VZYPXVmSsippcOY6oUEF8"
+ "Ty/foR0c5UqevF2NyfLxLRe3YRz7YsJ3y+joCB2F2ZtRULwI4/rFzeLPzYjDpDfHx7OtJuTL"
+ "mv/Rx4SerbTbmjd0rZMXVC8wG2G2cR/w9bZqsboipnKjVvqkNLj55WHj++7sxLQBLJAluqjE"
+ "XJPucPo7n37py6RpqNTnJ6fOEWFcB4HflBquCuzV63WHRMaECPdsg1NQrmVJeHEhlHydbHfr"
+ "60ljrUlNVqo1ofoq523f+oNaszOpcXSIbdKfEzK7uCUAfw9iRXwp6NWvNguZqjGvALySF9lD"
+ "Fh0UKpYDomW95Mpx0NOxQe2mmrPsA7G4WPSEuD6EFqEKpz57ooru5V3y0nOnBZPuc9Ym/La6"
+ "MdvFXEqZfaa6C+OWl7lcaLavf1wKRITvsHgkrJlDXSuen/W9KT6nWzNlhlMXn+5ZqyvP2ipx"
+ "yClQ25iK9SiHCXUBJ1GFU/Qv7QaPX8+bpYLPgUBozOAxuRBEbIMVUDqUZilCvSZH44dVo/8Q"
+ "+As0qaWQeVabbrl7h+Bnqa9YU34tR6sXe0i9mdoJf4WRUC9OMHwqG+Ot8k/kY4VJJq2Y0hV8"
+ "fZr4lRWXq3Crj/Y1o0UF5GVy4w7LhOG/5U42DXtadVpfqAjnlTx15Dh94UaxyJTxjw3RsF3r"
+ "atBy5wtJRGydkm57WrcZNKjG/4ex4XTTjagnE+emZr+e1Iuyt8uJn6bQP2vVL/BV7JBDBIzs"
+ "HBDMD4b8ax1oR2kHW75oaFsprd0Le8y7sqeEXC4LaboE+FjX3+BFr6ujL3fQ++OfjX6TYSqJ"
+ "arF/sFFaU74vSjgZWczC3dlnnv1R3WAXzzWGZS4ZOSBNhbNY2sWXW6p8GsfMV871qbWkRxIu"
+ "LlkHC4Fpm2nBDMYC9PZYcmSP1FwN72Djs8sx7g2bS38SnZNbxguljRRxLzMxi/KrmT+ffR5M"
+ "FEWRaljU7eKTdBrOKR8RR88rbmDWy19Dwupq84LVRYTCFvygunyJo6LdLwlxGkCMmancJMrF"
+ "sFW2CuqcWuginiXJJn60rS9W6o534CejKGIwm4iISkXaevvshFa756BPfPnlaBXgGD5q6EVP"
+ "3CoJyuBaB4TwJ85Ue8XJdWClWue0p9DWCGXdV8E+LqnySL5j0Rwfn0h6r4j+b3Nyvogn1YSZ"
+ "ZSk415TpCOzETOPNlwQPUNXgNytRt0dYMMyU82vX6ZRkjZP0d0Uur2y64hLjNdsVN1aedMK8"
+ "GQUmAt7zDvXj/bBRJxnNIFX5CgKmDGFERLINu/LreT2ZAkQNN/oobXdi44EVH0mRj2Q5j/z3"
+ "Apopv/7G1lmFE0FFGMv86MlNAOIoozHQvd5KEkgZ/837s6PXgX2/pSxAzUuBu1BzfzsywOxW"
+ "AX/iQBRb+FJ/yHPpqnl32vt1vd6Y1E/kIdP51Ty1MfnuiigAsEOvUlar0myksGa5bLXipFQi"
+ "cqRTZTE3tUjuoe4078BYOrD4RfZfeIil68Gzhb2Hn9/DfDZpHhIR9wAWnAlt5qhRLfCSf6xw"
+ "hmCohAtLfe9qucEM68epxaJpoabs6+niFgSzpBqgjP5IWZ6SUGkR83Q1hVksjPIhOtRoZTMB"
+ "eUnMEquW/XSeCwEcEikvCMJRsUD9eXObuf8loKGp1E0ovHhEAaZO8/sV2IfWmXfAZuUvRTu5"
+ "+G0piVCle/rbFcmqNgV2sTOjtbge5ni0pkOi4XwUvtHtgTVfDbwQtJ/uu3m623VJTCZnIFf3"
+ "ye8EFpL9D/7lKImLJ9So21omkZnQP/IeTzwShXxytqIURebb1DIxy95UK3NYDs9BfFaOPI/1"
+ "huim2BzZs8yt0AZKs2anGOVxV3kJfmWREUawqo18pTGxu8fwkBf9mqPw7roEbR1uymX/8UHU"
+ "iOMUuK5kyy5+KuCLlbeXGHpiT4yhM0uu3kM0Cr5lodGu1l4xE22RgaIqzC1t9SOEPzOl5UU6"
+ "tgi9yE+heE+fNnUQS2wl7e6WLjOhHsjteOpZVazBH+seZGNj6T80NmdnaCvMBKVUpDYyIQgs"
+ "829oaa6TPpmHj56AUwWSmYmpsCcaqmLiIg9E9xpZb/TwRjrY0mwB/+CnLaG1O/GaIw+H7wF8"
+ "LH23j5RO4uEiOCpQWSmQsgjOzzvP/JRorMHeeLIHVER35LtoZ+WnxcL/SvIXu6heOCZZj9jI"
+ "KOZ+yxO+IdvqsiRXQo32W6J+CrhRCKwOLBMUdQia2HuZjb7PvZgN139ZuAglxZENsUAWnvHq"
+ "V18oLIb5BA/VGknBlveYv189OgpNgJxT2pFBVPm4Mp0XdOzpp1e2+5BPK7xkrF3PN6P16RM5"
+ "qJeps8oFBIUbxNUcFkaanNPlcwKRSdl29xHPxvMMr/JWy+rYN8Rt/kGhi9SELJom1NlVIToB"
+ "A11+/KzW6r+s0lgckNzLzefEjBurYSdSbITUYhUibwjo2bx2BS7tgVrZl2uAi2Ybu3WhsmoE"
+ "WUnbwPXTLWANSMNTTVeWiTHHQ42Z3dWwPX9cAkZ+3BxnjLsX3ubo+gCSMLlJrhK1/vx/kET2"
+ "/w9JNP7nGCCJUcs3yk2XCAwnp27ifI2yucPfthCMJcJeuRJFrSqveKjwivGEraMDsXE3gaKT"
+ "qUPqgrcU+VDBxnzywBI6PRlbdOswv1ZNNBZ/jhUyQ2ln+b2rfnyVRc33+O7Meed359S3bWUO"
+ "FEWH7ILNsLefm9RpvomNasF7HMR1PHqNzJaNMn+uDGUz98G27Qb5wPfnJVRKXLL+HGor7U4x"
+ "nudnskpXU1lpIYX2tPhOZzUuE9tsZV4dl7RWzHf2mUuTOuZ9UqySeZ77jinucezW8Di+S+Ey"
+ "xUjZDg5RzhYmSrZWIB8le9xt0PRDpmb27XYUwZHOc+5HMtnNe23FtQSH5XKR/dYwZ4SzlebB"
+ "SbtBbn0rG8Xyhw9FPG3Q/LB9TWg5ipr+bKB/3hVBa2+rjmrLcKiWZjcItf3hwxk+7ic9XRZu"
+ "i8RyPCsK52nIwv9qkgt31qW+GjRTGsrfIuNMGBX0/aUjWItmlVznDHGyk5UjwFJtubtRdYDr"
+ "ungcFvbN6SDYshYttEzEfPrnvv3SABompDKoSRXpVO4cAU5LmJVzc2tCeaS39Z/7Ic43nwyd"
+ "Tb8uWhPyCx6R+XHkH1490v1cx9yRRydBHFtLaoX2nWahdkJnWIJlmW7VM1rG5X/Zeg+4PnCu"
+ "QKZU1q2UEydQk9xLgQ49FMq00HvwlhU7xu1edZ3YdRxO+9/1MGUSV6P0qRR7rIlPuKorwPib"
+ "M8V+da7+5sl3jPHD8MCcxUiwSVsc6TYEjGSdx9OtU0S5moh0FYqW00haQtfhZMF7ZwrMAfKo"
+ "dVw7peG6A8210DC7IY25pW0pmI3yMxSAx/0dhvrSmjNsI+sDC1hKCx6TlaGMaTl5U5eTnn1M"
+ "MDvqxhFn4nNPmnqsOer2g9JgY/YVqzphJKEaQg3flMqxjpL2eVjLt7i5koM7EmFNr/IDUqtd"
+ "0rkPAW6b2cUnXovztRPvnxhYfCfb6Eh7sW4UPs+xgr5q3tYFMnax1MSD3WJeTZO2fXu9s7S9"
+ "xYs3J3kdygNJUeFp7I6l5cUzrxvQhn8g9wNzIyfmCSn38LbQH7RvNtLA8VawcTa4nRkZZ1Ou"
+ "4tzCP64UKr/UDqYDial41gJGMa0RXxzZhZJH4wZcijkHhTznIkut016e/NMTrUduQoD9xL3+"
+ "jsh9fN575m8vnn/PtHvnSNyq27KwruffvSrQYj9xl6T6aSK6AzMpjFZtG4smI0h5mtQ4T8iI"
+ "qJ6PycXZX4M+zYzass4vgihGd7gPYweF+/bevmmo493oJ0p0Okxkt92lFR/UkHcNaHyGHkUX"
+ "Cf/1n0ukJ+X6A+weV+riyrRTXQd2gxnOjuxQze/9w4jCaV2Dfu4Apf9d01xKHd4ROMYXWcZW"
+ "nO1PblnoU3ITZQDw6Mt2xk21jMOD1eqrLD7PPmbUPYk6J/1bW9aJxMIg2F7EIdT/HMdd1Rr1"
+ "mP+mP8f7+3OgQjeusoG/vc6ZucSdLhwqp+KlrNC13ovj4bgg20jRpn4RE4JqbKKcYiobWvHF"
+ "tPz+pibzj/BW3Axw01NvFlUU7NXbcQL0vuSsTU6BJzT0iB6Qf0i/ZK3Uztj6SttmjLQ2FoYh"
+ "9AyttPDCAmJyg5RXzy9yhMexYJ4gJFltTkaZWv9Z1+XakN8D3OA4l+CUwrykHXmO3mSqF8cM"
+ "0UQYuqL8XwLPkYDpNAlnff/6SfRK0afTzDlfLhGCG8GhiNvQpNRXC6OMxJSt6lwOO5wcvXVd"
+ "CXnm/z52Ff5OF2wj33sSSMVJ1s/4XRwuL/JgO2hrT+Ce7DtJN6mG4o9LWkso/SUgThEPa3fg"
+ "fE+/ILoyTe65uBgbhD+xs8u2dTl/TZNsDdNY8j3DTCIjy/V4utG3IiQpQUsVwJSjwVs5Mnmk"
+ "NpM5OdVAgdvU3TGE/yBiP+aj3Q5DhG2xAB3+1kov9nx4i8UEoy+X4NCVNgrSfZvDWR94sh01"
+ "qQ1xqu0Lfo5Pc1PIpkkonoo80b7yk7rJWUWSG5wK3tUbhEeLczZiofCDXtsW+yDIEbSASMki"
+ "DK/qXAE3W8sfYM6q66szPxGsuHKutiP1ByUF7YblVYsIw48HCwLsRJ3Hi9uWS/gZ4fwLyDqu"
+ "N8HKNMrPhUgNCTQ7kDdRrKL+3CUFEd5EGWmZIuX18ZFavOK67yIZGlh2nrBLagOQk5pinJnL"
+ "oj/4ChGekL/TcUSE/iAPEIbqRKPY1jjqkdePFzkPh/nuZOMqYl3jAXrr/bK0OrHavdAwxZQg"
+ "6qB4VoYRXxrCqJJ4qKxzcYX2Ok08Qw257tRoVWfXrQjBIrxSQdjMXVRkxHGo0Gn4kTTY+lZ5"
+ "X51ZtUH54dsi6Vv7g40tVgQI+NrriPXWbBS8bEpCxcLmy8nB/8AqSNCW2Km4nlPozGhwfsMH"
+ "Gbbe3QJXK/1QWR1GX4rESlzsMJUHP3uu4aksfeUV3jXJlPIhD6BWtpRruvAVsLA97reFuCfh"
+ "37tLAmSYZ6+UEl2PQZW4Fz8jNBK/ZN5lQlOktTek7UqhtXb3T3fxGYLIY1I+owVeqNkMki+u"
+ "wdxgeKhfiTMT2XzFtzWHhSkhxR/BnS62MtTFtpUbRZTEj0sKw3t4PO/cUVANoLwNT28GA84x"
+ "RbK+Kvssg/3DlC1CP0vguDaqE3gtHMuFcM2ZVfdRsBx20lN92RLANd8KIh/zgAiUHX5Nl9JU"
+ "llNZ2DNYwWzhl32iKBtM+5m82d1DzErjUgF0moTkmCszZPqNFadoglq/8+IS7IRogW59w5Ks"
+ "gXfrHcQhEprR2xrEXvLZdnyo0IYEuVapIgwdPiI1z2DBxcoJ8sy3m16HFTPkDkbjClnDSU4T"
+ "w/BlY1SqevyTwB4YzjEPXUfsfJM/Jg7qvCcgfvyCcvq/E/Ph42L3YRXkqMxx7OhKNExrld1g"
+ "PawYZk0z/91WXibCfFrS1iawp42VtLrzHVDihZdQrDSuqkB5eP1SxPJPYs1nO/1NqSU3OWc4"
+ "SLUOUJCOTebTiJPmM9pxDLiUDNp8RrDnR2PXe3BK3cqlUtjn/Q3pzrVNnAqJ+wp4aqD50sak"
+ "x72/KpBLXasVeZ1xnk/kgIpUgC0RWHxbbnz+uSxC/VbxbzE2YyPxzGiSivDrwGP29rA/MbuJ"
+ "gyQhMoHWDS7eps8slTfVnVx2o3Sn88D7SYG9pIgcczRaXz6//Ay3uv1xqNEY4yv8GG7cQDvw"
+ "N+l63bZvzWzJ8/wwKj6kvmjU5w3dHDKZ9g/YarmBEsCcEjLrUBzAQyWdiQuf3twkeGb8BN9h"
+ "akDsY3oYZ3LB8cl1LXIyiuO90nDutn7qZ+p4yT7v5dJsx3KuZV7YLH1AzIXcAMec32m3k8j0"
+ "iKC4xmsq85l3xDiJSuODfKkbjsUeB8SWP5UoQZEPZRB8psqK+Ia4fRssNLz41rG8wbjEK7/O"
+ "stzaasjdUh9jW579HHkZEgmJdyi7FHAIA+Pyq8ijJM8QiwzGTb2Fpbi5LHsPAIhSo+3AuAOX"
+ "fLEEE9McEzqL+eP+xFie+7BQY/nB/bwwOtimbleJqpbDIK4mKkWYc2yLLdi0I4VoWkBa6J0S"
+ "Iqf6q91D7hcncN/fSSNX+E3hiXgnW4tPyZTbfkhM8SvVof8mvrdRkl16jLEG/oKsE1EJ+Y2K"
+ "Liz2qZQ2oHqoEpj0NSUI6c+qRheVW3qooqCNf/KOeFye2C6+uVg9qHsyRfJC4kFi/+Auusz5"
+ "EvaWie9xcIYwWtmqZZ0zjEd+V266a0NGFMDGO5/hbuoLkW0QJoG7OzUKm2YM4PXVC+e/0mlN"
+ "8g53oJAWeJmdnm1Fwy2EivZpN5U/9DFudhwQ8Gc7BkOLLLKCHDjVmHsyqWWwzoJIhv34xvKt"
+ "d9zRBEaVc+4v7L9+jIHMye3/5AnEzomTjIl+k+1O97Ch7vVirUHsLlPqFYKqKNdcF29i5v7A"
+ "w2VYdZ/WkqUDyB5q9GApiWXzplCWHBpj+0gbt88zQ+aNi5EARBjGGKAKU1MdhrBKCDJineIO"
+ "iUyqot9VD8H21kSVcit0yRMnlZetphhBzIIqWXCWQ066oGc0ScmsoPyC40/gkRVLgNtmZNwV"
+ "x08jOkE18rsNIVKie9hOW8r2rwRk5vYdiSd26044WpDWmdW49nwsezfDPaDy7Qj2+tvUf1HE"
+ "kR+hv+5KoxRTE600L7X91Ur2ua3mCjvY1V4NM+TPT0tZ25mFW83niOhwCNl/MGYkkeAEP6GO"
+ "s8jS5shVW+Uc1zT/oIgyO1e1O8TVDm4rVYmVNvemHuAQgkiOaZ70gA6IQebfKWCcrPdRkApN"
+ "CuJb1dYSzIFXrwFDUBh2IeWOoe3gTFjuKxPVfqHHtg6W7FLvw7/stGDqEbXdifkAS0LAfSpg"
+ "Ua3qnaw9Y16rWFryES1cL1Ti7MfzUcc3H5jpki21zvVmdIvOsL24HYnoBghnxhkT9kA9hLzu"
+ "/cbDUEzDzWVRy5AgStGQOdWCZGa8SVf3+U16C1afFg5ZzbnRYHHkirVPdyikos5r8IFNfql1"
+ "mpja69/q5zuWnS3EuGaBENt1nxK3kYB57GOf5GxUxO17b4KWruHIYSJxYbFVOtFlP3n8dtVE"
+ "h0c25HlcAqNnOk18pxNF5XEv3ervI3OF8igrM/622f5xCrDdqhgHKf5liexXcIQq9/pbiukn"
+ "c7noUQh9pnnFvqY35Q1opBvS/aojZdIjwOylSifXfvhPN4I26w5yUhWFH1sMJaabswwthhjI"
+ "Ok1M7P4m+QcNurgPDx3kbFZCzC4St8dwY+FKiPsYvkqcpOJ+3xfLXZPwji4ntczeYAuFt7Uq"
+ "hG6bneIcKg1vGOGGI59VZiT8IDkDnrYG2sn+rid/Im2L+c5ddPGICy+MLLPgkuNr2hQh8wL6"
+ "sgbTvVfrUe7Tvbsn9BRLdYUX3Er4/uuxFViGY3RTwk3T6zmBnKxIbGwAyr2Bw+n2qi5iy/ON"
+ "Pd364JCVx40xWTU5estviIpjz78PTBIE/Fhbiavx9DsADJ4hH8JwwmGqtRs3yr3+s3sfQb42"
+ "sAGw8N0Fa7MIgwXd1RP1IaY0ewtBkYuXZKr51nUvAkhemvtYVP2cLQ0xZLSr1Rj69P4xwiCr"
+ "2MVZVlHEv4v8pGeY9LPOn8tEHFCdtpzphSqdki9wcGwLPxZsiYaLboIINoVTx7FFKcE2lzH/"
+ "8q8hTA4RgdejiQuJnT2kQ2DWyrp8UdeFvje4/iWD2/NxZz1X2j0ZMz4+jxltMEqrWP6HqOL8"
+ "tF0xl5bA7o4x4gLkQ8aXqRio8YFFyYYPjYiw2qX2RiEEpi+dVSJViNuoYbfY+LPtEy2qPqQ3"
+ "ekNE3w0kZjNAN/3g06Tp8ECU1ZIfaPwRUnGznxN/XzNbxEKDn+RkZ9WS2GOd8yrhRh/nQigf"
+ "DyUtJLWfDc/G/lUVmnDt8VXOyir7fqFbFptkruC9HqPyM3/0k7OR3CrgzNMYvtwMxJ+vWryo"
+ "N6KQgDrcLUDqh36PIOOXgmytZHpLxrWVKnOcd9hrPfnUGBcHC0113+9yPz0Djl/byoGd1487"
+ "Ym3gq99gdwc2GKOFHwvcIznxj27RXyqTj1bn5yhi/VEkVU0fKdy/xSn1e1w1x6pc2N4DZTb0"
+ "mn/BeBL/t2X8B2rxLZ8=";
+
+/*---------------------------------------------------------------------*/
+/* Deserializer with added processing */
+/*---------------------------------------------------------------------*/
+/*!
+ * \brief l_bootnum_gen4()
+ *
+ * \param[in] nsamp number of samples to retain for each digit
+ * \return pixa of labeled digits
+ *
+ * <pre>
+ * Notes:
+ * (1) The encoded string and the code to generate pixa1 was
+ * automatically generated.
+ * (2) pixa1 is further processed to make the pixa of labelled digits.
+ * </pre>
+ */
+PIXA *
+l_bootnum_gen4(l_int32 nsamp)
+{
+l_uint8 *data1, *data2;
+l_int32 size1;
+size_t size2;
+PIXA *pixa1, *pixa2;
+
+ PROCNAME("l_bootnum_gen4");
+
+ if (nsamp <= 0)
+ return (PIXA *)ERROR_PTR("invalid nsamp\n", procName, NULL);
+
+ /* Unencode selected string, write to file, and read it */
+ data1 = decodeBase64(l_bootnum4, strlen(l_bootnum4), &size1);
+ data2 = zlibUncompress(data1, size1, &size2);
+ pixa1 = pixaReadMem(data2, size2);
+ lept_free(data1);
+ lept_free(data2);
+
+ /* pixa1 has 10 images of mosaic'd digits. Each of these images
+ * must be extracted into a pixa of templates, where each template
+ * is labeled with the digit value, and then selectively
+ * concatenated into an output pixa. */
+ pixa2 = pixaMakeFromTiledPixa(pixa1, 20, 30, nsamp);
+ pixaDestroy(&pixa1);
+ return pixa2;
+}
diff --git a/leptonica/src/boxbasic.c b/leptonica/src/boxbasic.c
new file mode 100644
index 00000000..83a5bc48
--- /dev/null
+++ b/leptonica/src/boxbasic.c
@@ -0,0 +1,2440 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file boxbasic.c
+ * <pre>
+ *
+ * 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 boxGetSideLocations()
+ * l_int32 boxSetSideLocations()
+ * 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()
+ * NUMA *boxaFindInvalidBoxes()
+ * 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()
+ * BOXAA *boxaaReadMem()
+ * l_int32 boxaaWrite()
+ * l_int32 boxaaWriteStream()
+ * l_int32 boxaaWriteMem()
+ *
+ * Boxa serialized I/O
+ * BOXA *boxaRead()
+ * BOXA *boxaReadStream()
+ * BOXA *boxaReadMem()
+ * l_int32 boxaWriteDebug()
+ * l_int32 boxaWrite()
+ * l_int32 boxaWriteStream()
+ * l_int32 boxaWriteStderr()
+ * 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
+ * boxaGetMedianVals() 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
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Bounds on array sizes */
+static const size_t MaxBoxaPtrArraySize = 10000000;
+static const size_t MaxBoxaaPtrArraySize = 1000000;
+static const size_t InitialPtrArraySize = 20; /*!< n'importe quoi */
+
+/*---------------------------------------------------------------------*
+ * Box creation, destruction and copy *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxCreate()
+ *
+ * \param[in] x, y, w, h
+ * \return box, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ }
+
+ box = (BOX *)LEPT_CALLOC(1, sizeof(BOX));
+ boxSetGeometry(box, x, y, w, h);
+ box->refcount = 1;
+ return box;
+}
+
+
+/*!
+ * \brief boxCreateValid()
+ *
+ * \param[in] x, y, w, h
+ * \return box, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This returns NULL if either w = 0 or h = 0.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief boxCopy()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief boxClone()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief boxDestroy()
+ *
+ * \param[in,out] pbox will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Decrements the ref count and, if 0, destroys the box.
+ * (2) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Box accessors *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxGetGeometry()
+ *
+ * \param[in] box
+ * \param[out] px, py, pw, ph [optional] each can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxSetGeometry()
+ *
+ * \param[in] box
+ * \param[in] x, y, w, h [optional] use -1 to leave unchanged
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxGetSideLocations()
+ *
+ * \param[in] box
+ * \param[out] pl, pt, pr, pb [optional] each can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) All returned values are within the box.
+ * </pre>
+ */
+l_ok
+boxGetSideLocations(BOX *box,
+ l_int32 *pl,
+ l_int32 *pr,
+ l_int32 *pt,
+ l_int32 *pb)
+{
+l_int32 x, y, w, h;
+
+ PROCNAME("boxGetSideLocations");
+
+ if (pl) *pl = 0;
+ if (pr) *pr = 0;
+ if (pt) *pt = 0;
+ if (pb) *pb = 0;
+ if (!box)
+ return ERROR_INT("box not defined", procName, 1);
+
+ boxGetGeometry(box, &x, &y, &w, &h);
+ if (pl) *pl = x;
+ if (pr) *pr = x + w - 1;
+ if (pt) *pt = y;
+ if (pb) *pb = y + h - 1;
+ return 0;
+}
+
+
+/*!
+ * \brief boxSetSideLocations()
+ *
+ * \param[in] box
+ * \param[in] l, r, t, b [optional] use -1 to leave unchanged
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+boxSetSideLocations(BOX *box,
+ l_int32 l,
+ l_int32 r,
+ l_int32 t,
+ l_int32 b)
+{
+l_int32 x, y, w, h;
+
+ PROCNAME("boxSetSideLocations");
+
+ if (!box)
+ return ERROR_INT("box not defined", procName, 1);
+ x = (l != -1) ? l : box->x;
+ w = (r != -1) ? r - x + 1 : box->x + box->w - x;
+ y = (t != -1) ? t : box->y;
+ h = (b != -1) ? b - y + 1 : box->y + box->h - y;
+ boxSetGeometry(box, x, y, w, h);
+ return 0;
+}
+
+
+/*!
+ * \brief Return the current reference count of %box
+ *
+ * \param[in] box
+ * \return refcount
+ */
+l_int32
+boxGetRefcount(BOX *box)
+{
+ PROCNAME("boxGetRefcount");
+
+ if (!box)
+ return ERROR_INT("box not defined", procName, UNDEF);
+
+ return box->refcount;
+}
+
+/*!
+ * \brief Adjust the current references count of %box by %delta
+ *
+ * \param[in] box ptr to box
+ * \param[in] delta adjustment, usually -1 or 1
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+boxChangeRefcount(BOX *box,
+ l_int32 delta)
+{
+ PROCNAME("boxChangeRefcount");
+
+ if (!box)
+ return ERROR_INT("box not defined", procName, 1);
+
+ box->refcount += delta;
+ return 0;
+}
+
+
+/*!
+ * \brief boxIsValid()
+ *
+ * \param[in] box
+ * \param[out] pvalid 1 if valid; 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaCreate()
+ *
+ * \param[in] n initial number of ptrs; 0 for default
+ * \return boxa, or NULL on error
+ */
+BOXA *
+boxaCreate(l_int32 n)
+{
+BOXA *boxa;
+
+ PROCNAME("boxaCreate");
+
+ if (n <= 0 || n > MaxBoxaPtrArraySize)
+ n = InitialPtrArraySize;
+
+ boxa = (BOXA *)LEPT_CALLOC(1, sizeof(BOXA));
+ boxa->n = 0;
+ boxa->nalloc = n;
+ boxa->refcount = 1;
+ if ((boxa->box = (BOX **)LEPT_CALLOC(n, sizeof(BOX *))) == NULL) {
+ boxaDestroy(&boxa);
+ return (BOXA *)ERROR_PTR("boxa ptrs not made", procName, NULL);
+ }
+ return boxa;
+}
+
+
+/*!
+ * \brief boxaCopy()
+ *
+ * \param[in] boxa
+ * \param[in] copyflag L_COPY, L_CLONE, L_COPY_CLONE
+ * \return new boxa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pix.h for description of the copyflag.
+ * (2) The copy-clone makes a new boxa that holds clones of each box.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaDestroy()
+ *
+ * \param[in,out] pboxa will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Decrements the ref count and, if 0, destroys the boxa.
+ * (2) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaAddBox()
+ *
+ * \param[in] boxa
+ * \param[in] box to be added
+ * \param[in] copyflag L_INSERT, L_COPY, L_CLONE
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ if (boxaExtendArray(boxa)) {
+ if (copyflag != L_INSERT)
+ boxDestroy(&boxc);
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ }
+ boxa->box[n] = boxc;
+ boxa->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief boxaExtendArray()
+ *
+ * \param[in] boxa
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Reallocs with doubled size of ptr array.
+ * </pre>
+ */
+l_ok
+boxaExtendArray(BOXA *boxa)
+{
+ PROCNAME("boxaExtendArray");
+
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+
+ return boxaExtendArrayToSize(boxa, 2 * boxa->nalloc);
+}
+
+
+/*!
+ * \brief boxaExtendArrayToSize()
+ *
+ * \param[in] boxa
+ * \param[in] size new size of boxa ptr array
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If necessary, reallocs new boxa ptr array to %size.
+ * (2) The max number of box ptrs is 10M.
+ * </pre>
+ */
+l_ok
+boxaExtendArrayToSize(BOXA *boxa,
+ size_t size)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("boxaExtendArrayToSize");
+
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+ if (boxa->nalloc > MaxBoxaPtrArraySize) /* belt & suspenders */
+ return ERROR_INT("boxa has too many ptrs", procName, 1);
+ if (size > MaxBoxaPtrArraySize)
+ return ERROR_INT("size > 10M box ptrs; too large", procName, 1);
+ if (size <= boxa->nalloc) {
+ L_INFO("size too small; no extension\n", procName);
+ return 0;
+ }
+
+ oldsize = boxa->nalloc * sizeof(BOX *);
+ newsize = size * sizeof(BOX *);
+ if ((boxa->box = (BOX **)reallocNew((void **)&boxa->box,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+ boxa->nalloc = size;
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Boxa accessors *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief boxaGetValidCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief boxaGetBox()
+ *
+ * \param[in] boxa
+ * \param[in] index to the index-th box
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief boxaGetValidBox()
+ *
+ * \param[in] boxa
+ * \param[in] index to the index-th box
+ * \param[in] accessflag L_COPY or L_CLONE
+ * \return box, or NULL if box is not valid or on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaFindInvalidBoxes()
+ *
+ * \param[in] boxa
+ * \return na numa of invalid boxes; NULL if there are none or on error
+ */
+NUMA *
+boxaFindInvalidBoxes(BOXA *boxa)
+{
+l_int32 i, n, w, h;
+NUMA *na;
+
+ PROCNAME("boxaFindInvalidBoxes");
+
+ if (!boxa)
+ return (NUMA *)ERROR_PTR("boxa not defined", procName, NULL);
+
+ n = boxaGetCount(boxa);
+ if (boxaGetValidCount(boxa) == n)
+ return NULL;
+
+ na = numaMakeConstant(0, n);
+ for (i = 0; i < n; i++) {
+ boxaGetBoxGeometry(boxa, i, NULL, NULL, &w, &h);
+ if (w == 0 || h == 0)
+ numaSetValue(na, i, 1);
+ }
+ return na;
+}
+
+
+/*!
+ * \brief boxaGetBoxGeometry()
+ *
+ * \param[in] boxa
+ * \param[in] index to the index-th box
+ * \param[out] px, py, pw, ph [optional] each can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaIsFull()
+ *
+ * \param[in] boxa
+ * \param[out] pfull 1 if boxa is full; 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaReplaceBox()
+ *
+ * \param[in] boxa
+ * \param[in] index to the index-th box
+ * \param[in] box insert this box to replace existing one
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) In-place replacement of one box; the input %box is now
+ * owned by the boxa.
+ * (2) The previous box at that location, if any, is destroyed.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaInsertBox()
+ *
+ * \param[in] boxa
+ * \param[in] index location in boxa to insert new value
+ * \param[in] box new box to be inserted; the boxa now owns it
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n);
+ return 1;
+ }
+ if (!box)
+ return ERROR_INT("box not defined", procName, 1);
+
+ if (n >= boxa->nalloc) {
+ if (boxaExtendArray(boxa))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ array = boxa->box;
+ boxa->n++;
+ for (i = n; i > index; i--)
+ array[i] = array[i - 1];
+ array[index] = box;
+ return 0;
+}
+
+
+/*!
+ * \brief boxaRemoveBox()
+ *
+ * \param[in] boxa
+ * \param[in] index of box to be removed and destroyed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+boxaRemoveBox(BOXA *boxa,
+ l_int32 index)
+{
+ return boxaRemoveBoxAndSave(boxa, index, NULL);
+}
+
+
+/*!
+ * \brief boxaRemoveBoxAndSave()
+ *
+ * \param[in] boxa
+ * \param[in] index of box to be removed
+ * \param[out] pbox [optional] removed box
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n - 1);
+ return 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;
+}
+
+
+/*!
+ * \brief boxaSaveValid()
+ *
+ * \param[in] boxas
+ * \param[in] copyflag L_COPY or L_CLONE
+ * \return boxad if OK, NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This makes a copy/clone of each valid box.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaInitFull()
+ *
+ * \param[in] boxa typically empty
+ * \param[in] box [optional] to be replicated into the entire ptr array
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaClear()
+ *
+ * \param[in] boxa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This destroys all boxes in the boxa, setting the ptrs
+ * to null. The number of allocated boxes, n, is set to 0.
+ * </pre>
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief boxaaCreate()
+ *
+ * \param[in] n 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 > MaxBoxaaPtrArraySize)
+ n = InitialPtrArraySize;
+
+ baa = (BOXAA *)LEPT_CALLOC(1, sizeof(BOXAA));
+ if ((baa->boxa = (BOXA **)LEPT_CALLOC(n, sizeof(BOXA *))) == NULL) {
+ boxaaDestroy(&baa);
+ return (BOXAA *)ERROR_PTR("boxa ptr array not made", procName, NULL);
+ }
+ baa->nalloc = n;
+ baa->n = 0;
+ return baa;
+}
+
+
+/*!
+ * \brief boxaaCopy()
+ *
+ * \param[in] baas input boxaa to be copied
+ * \param[in] copyflag L_COPY, L_CLONE
+ * \return baad new boxaa, composed of copies or clones of the boxa
+ * in baas, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) L_COPY makes a copy of each boxa in baas.
+ * L_CLONE makes a clone of each boxa in baas.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaaDestroy()
+ *
+ * \param[in,out] pbaa will be set to null before returning
+ */
+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;
+}
+
+
+
+/*--------------------------------------------------------------------------*
+ * Add Boxa to Boxaa *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief boxaaAddBoxa()
+ *
+ * \param[in] baa
+ * \param[in] ba to be added
+ * \param[in] copyflag L_INSERT, L_COPY, L_CLONE
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ if (boxaaExtendArray(baa))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ baa->boxa[n] = bac;
+ baa->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief boxaaExtendArray()
+ *
+ * \param[in] baa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Doubles the size of the boxa ptr array.
+ * (2) The max number of boxa ptrs is 1 million.
+ * </pre>
+ */
+l_ok
+boxaaExtendArray(BOXAA *baa)
+{
+ PROCNAME("boxaaExtendArray");
+
+ if (!baa)
+ return ERROR_INT("baa not defined", procName, 1);
+
+ return boxaaExtendArrayToSize(baa, 2 * baa->nalloc);
+}
+
+
+/*!
+ * \brief boxaaExtendArrayToSize()
+ *
+ * \param[in] baa
+ * \param[in] size new size of boxa array
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If necessary, reallocs the boxa ptr array to %size.
+ * (2) %size limited to 1M boxa ptrs.
+ * </pre>
+ */
+l_ok
+boxaaExtendArrayToSize(BOXAA *baa,
+ l_int32 size)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("boxaaExtendArrayToSize");
+
+ if (!baa)
+ return ERROR_INT("baa not defined", procName, 1);
+ if (baa->nalloc > MaxBoxaaPtrArraySize) /* belt & suspenders */
+ return ERROR_INT("baa has too many ptrs", procName, 1);
+ if (size > MaxBoxaaPtrArraySize)
+ return ERROR_INT("size > 1M boxa ptrs; too large", procName, 1);
+ if (size <= baa->nalloc) {
+ L_INFO("size too small; no extension\n", procName);
+ return 0;
+ }
+
+ oldsize = baa->nalloc * sizeof(BOXA *);
+ newsize = size * sizeof(BOXA *);
+ if ((baa->boxa = (BOXA **)reallocNew((void **)&baa->boxa,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+ baa->nalloc = size;
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Boxaa accessors *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief boxaaGetCount()
+ *
+ * \param[in] baa
+ * \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;
+}
+
+
+/*!
+ * \brief boxaaGetBoxCount()
+ *
+ * \param[in] baa
+ * \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;
+}
+
+
+/*!
+ * \brief boxaaGetBoxa()
+ *
+ * \param[in] baa
+ * \param[in] index to the index-th boxa
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief boxaaGetBox()
+ *
+ * \param[in] baa
+ * \param[in] iboxa index into the boxa array in the boxaa
+ * \param[in] ibox index into the box array in the boxa
+ * \param[in] 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief boxaaInitFull()
+ *
+ * \param[in] baa typically empty
+ * \param[in] boxa to be replicated into the entire ptr array
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaaExtendWithInit()
+ *
+ * \param[in] baa
+ * \param[in] maxindex
+ * \param[in] boxa to be replicated into the extended ptr array
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+ if (boxaaExtendArrayToSize(baa, maxindex + 1))
+ return ERROR_INT("extension failed", procName, 1);
+
+ /* Fill the new entries with copies of boxa */
+ for (i = n; i <= maxindex; i++)
+ boxaaAddBoxa(baa, boxa, L_COPY);
+ return 0;
+}
+
+
+/*!
+ * \brief boxaaReplaceBoxa()
+ *
+ * \param[in] baa
+ * \param[in] index to the index-th boxa
+ * \param[in] boxa insert and replace any existing one
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaaInsertBoxa()
+ *
+ * \param[in] baa
+ * \param[in] index location in boxaa to insert new boxa
+ * \param[in] boxa new boxa to be inserted
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n);
+ return 1;
+ }
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+
+ if (n >= baa->nalloc) {
+ if (boxaaExtendArray(baa))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ array = baa->boxa;
+ baa->n++;
+ for (i = n; i > index; i--)
+ array[i] = array[i - 1];
+ array[index] = boxa;
+ return 0;
+}
+
+
+/*!
+ * \brief boxaaRemoveBoxa()
+ *
+ * \param[in] baa
+ * \param[in] index of the boxa to be removed and destroyed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaaAddBox()
+ *
+ * \param[in] baa
+ * \param[in] index of boxa with boxaa
+ * \param[in] box to be added
+ * \param[in] accessflag L_INSERT, L_COPY or L_CLONE
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Adds to an existing boxa only.
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaaReadFromFiles()
+ *
+ * \param[in] dirname directory
+ * \param[in] substr [optional] substring filter on filenames; can be NULL
+ * \param[in] first 0-based
+ * \param[in] nfiles use 0 for everything from %first to the end
+ * \return baa, or NULL on error or if no boxa files are found.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaaRead()
+ *
+ * \param[in] 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);
+ baa = boxaaReadStream(fp);
+ fclose(fp);
+ if (!baa)
+ return (BOXAA *)ERROR_PTR("boxaa not read", procName, NULL);
+ return baa;
+}
+
+
+/*!
+ * \brief boxaaReadStream()
+ *
+ * \param[in] fp input file stream
+ * \return boxaa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is OK for the boxaa to be empty (n == 0).
+ * </pre>
+ */
+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 (n < 0)
+ return (BOXAA *)ERROR_PTR("num boxa ptrs < 0", procName, NULL);
+ if (n > MaxBoxaaPtrArraySize)
+ return (BOXAA *)ERROR_PTR("too many boxa ptrs", procName, NULL);
+ if (n == 0) L_INFO("the boxaa is empty\n", procName);
+
+ 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) {
+ boxaaDestroy(&baa);
+ return (BOXAA *)ERROR_PTR("boxa descr not valid", procName, NULL);
+ }
+ if ((boxa = boxaReadStream(fp)) == NULL) {
+ boxaaDestroy(&baa);
+ return (BOXAA *)ERROR_PTR("boxa not made", procName, NULL);
+ }
+ boxaaAddBoxa(baa, boxa, L_INSERT);
+ }
+ return baa;
+}
+
+
+/*!
+ * \brief boxaaReadMem()
+ *
+ * \param[in] data serialization of boxaa; in ascii
+ * \param[in] size of data in bytes; can use strlen to get it
+ * \return baa, or NULL on error
+ */
+BOXAA *
+boxaaReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+BOXAA *baa;
+
+ PROCNAME("boxaaReadMem");
+
+ if (!data)
+ return (BOXAA *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (BOXAA *)ERROR_PTR("stream not opened", procName, NULL);
+
+ baa = boxaaReadStream(fp);
+ fclose(fp);
+ if (!baa) L_ERROR("baa not read\n", procName);
+ return baa;
+}
+
+
+/*!
+ * \brief boxaaWrite()
+ *
+ * \param[in] filename
+ * \param[in] baa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+boxaaWrite(const char *filename,
+ BOXAA *baa)
+{
+l_int32 ret;
+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);
+ ret = boxaaWriteStream(fp, baa);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("baa not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief boxaaWriteStream()
+ *
+ * \param[in] fp output file stream
+ * \param[in] baa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaaWriteMem()
+ *
+ * \param[out] pdata data of serialized boxaa; ascii
+ * \param[out] psize size of returned data
+ * \param[in] baa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a boxaa in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+boxaaWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ BOXAA *baa)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("boxaaWriteMem");
+
+ 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 (!baa)
+ return ERROR_INT("baa not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = boxaaWriteStream(fp, baa);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = boxaaWriteStream(fp, baa);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Boxa serialized I/O *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaRead()
+ *
+ * \param[in] 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);
+ boxa = boxaReadStream(fp);
+ fclose(fp);
+ if (!boxa)
+ return (BOXA *)ERROR_PTR("boxa not read", procName, NULL);
+ return boxa;
+}
+
+
+/*!
+ * \brief boxaReadStream()
+ *
+ * \param[in] fp input file stream
+ * \return boxa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is OK for the boxa to be empty (n == 0).
+ * </pre>
+ */
+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 (n < 0)
+ return (BOXA *)ERROR_PTR("num box ptrs < 0", procName, NULL);
+ if (n > MaxBoxaPtrArraySize)
+ return (BOXA *)ERROR_PTR("too many box ptrs", procName, NULL);
+ if (n == 0) L_INFO("the boxa is empty\n", procName);
+
+ 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) {
+ boxaDestroy(&boxa);
+ return (BOXA *)ERROR_PTR("box descr not valid", procName, NULL);
+ }
+ box = boxCreate(x, y, w, h);
+ boxaAddBox(boxa, box, L_INSERT);
+ }
+ return boxa;
+}
+
+
+/*!
+ * \brief boxaReadMem()
+ *
+ * \param[in] data serialization of boxa; in ascii
+ * \param[in] size of data in bytes; 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);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (BOXA *)ERROR_PTR("stream not opened", procName, NULL);
+
+ boxa = boxaReadStream(fp);
+ fclose(fp);
+ if (!boxa) L_ERROR("boxa not read\n", procName);
+ return boxa;
+}
+
+
+/*!
+ * \brief boxaWriteDebug()
+ *
+ * \param[in] filename
+ * \param[in] boxa
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Debug version, intended for use in the library when writing
+ * to files in a temp directory with names that are compiled in.
+ * This is used instead of boxaWrite() for all such library calls.
+ * (2) The global variable LeptDebugOK defaults to 0, and can be set
+ * or cleared by the function setLeptDebugOK().
+ * </pre>
+ */
+l_ok
+boxaWriteDebug(const char *filename,
+ BOXA *boxa)
+{
+ PROCNAME("boxaWriteDebug");
+
+ if (LeptDebugOK) {
+ return boxaWrite(filename, boxa);
+ } else {
+ L_INFO("write to named temp file %s is disabled\n", procName, filename);
+ return 0;
+ }
+}
+
+
+/*!
+ * \brief boxaWrite()
+ *
+ * \param[in] filename
+ * \param[in] boxa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+boxaWrite(const char *filename,
+ BOXA *boxa)
+{
+l_int32 ret;
+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);
+ ret = boxaWriteStream(fp, boxa);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("boxa not written to stream", procName, 1);
+
+ return 0;
+}
+
+
+/*!
+ * \brief boxaWriteStream()
+ *
+ * \param[in] fp file stream; use NULL for stderr
+ * \param[in] boxa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+boxaWriteStream(FILE *fp,
+ BOXA *boxa)
+{
+l_int32 n, i;
+BOX *box;
+
+ PROCNAME("boxaWriteStream");
+
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+ if (!fp)
+ return boxaWriteStderr(boxa);
+
+ 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;
+}
+
+
+/*!
+ * \brief boxaWriteStderr()
+ *
+ * \param[in] boxa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+boxaWriteStderr(BOXA *boxa)
+{
+l_int32 n, i;
+BOX *box;
+
+ PROCNAME("boxaWriteStderr");
+
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+
+ n = boxaGetCount(boxa);
+ lept_stderr("\nBoxa Version %d\n", BOXA_VERSION_NUMBER);
+ lept_stderr("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);
+ lept_stderr(" Box[%d]: x = %d, y = %d, w = %d, h = %d\n",
+ i, box->x, box->y, box->w, box->h);
+ boxDestroy(&box);
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief boxaWriteMem()
+ *
+ * \param[out] pdata data of serialized boxa; ascii
+ * \param[out] psize size of returned data
+ * \param[in] boxa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a boxa in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+boxaWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ BOXA *boxa)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("boxaWriteMem");
+
+ 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 (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = boxaWriteStream(fp, boxa);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = boxaWriteStream(fp, boxa);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Debug printing *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxPrintStreamInfo()
+ *
+ * \param[in] fp file stream; use NULL for stderr
+ * \param[in] box
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This outputs debug info. Use serialization functions to
+ * write to file if you want to read the data back.
+ * </pre>
+ */
+l_ok
+boxPrintStreamInfo(FILE *fp,
+ BOX *box)
+{
+ PROCNAME("boxPrintStreamInfo");
+
+ if (!box)
+ return ERROR_INT("box not defined", procName, 1);
+
+ if (!fp) { /* output to stderr */
+ lept_stderr(" Box: x = %d, y = %d, w = %d, h = %d\n",
+ box->x, box->y, box->w, box->h);
+ } else {
+ 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/leptonica/src/boxfunc1.c b/leptonica/src/boxfunc1.c
new file mode 100644
index 00000000..45e8995d
--- /dev/null
+++ b/leptonica/src/boxfunc1.c
@@ -0,0 +1,2737 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file boxfunc1.c
+ * <pre>
+ *
+ * Box geometry
+ * l_int32 boxContains()
+ * l_int32 boxIntersects()
+ * BOXA *boxaContainedInBox()
+ * l_int32 boxaContainedInBoxCount()
+ * l_int32 boxaContainedInBoxa()
+ * BOXA *boxaIntersectsBox()
+ * l_int32 boxaIntersectsBoxCount()
+ * BOXA *boxaClipToBox()
+ * BOXA *boxaCombineOverlaps()
+ * l_int32 boxaCombineOverlapsInPair()
+ * BOX *boxOverlapRegion()
+ * BOX *boxBoundingRegion()
+ * l_int32 boxOverlapFraction()
+ * l_int32 boxOverlapArea()
+ * BOXA *boxaHandleOverlaps()
+ * l_int32 boxOverlapDistance()
+ * l_int32 boxSeparationDistance()
+ * l_int32 boxCompareSize()
+ * l_int32 boxContainsPt()
+ * BOX *boxaGetNearestToPt()
+ * BOX *boxaGetNearestToLine()
+ * l_int32 boxaFindNearestBoxes()
+ * l_int32 boxaGetNearestByDirection()
+ * static l_int32 boxHasOverlapInXorY()
+ * static l_int32 boxGetDistanceInXorY()
+ * l_int32 boxIntersectByLine()
+ * l_int32 boxGetCenter()
+ * BOX *boxClipToRectangle()
+ * l_int32 boxClipToRectangleParams()
+ * BOX *boxRelocateOneSide()
+ * BOXA *boxaAdjustSides()
+ * BOXA *boxaAdjustBoxSides()
+ * BOX *boxAdjustSides()
+ * BOXA *boxaSetSide()
+ * l_int32 boxSetSide()
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+static l_int32 boxHasOverlapInXorY(l_int32 c1, l_int32 s1, l_int32 c2,
+ l_int32 s2);
+static l_int32 boxGetDistanceInXorY(l_int32 c1, l_int32 s1, l_int32 c2,
+ l_int32 s2);
+
+
+/*---------------------------------------------------------------------*
+ * Box geometry *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxContains()
+ *
+ * \param[in] box1, box2
+ * \param[out] presult 1 if box2 is entirely contained within box1;
+ * 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+boxContains(BOX *box1,
+ BOX *box2,
+ l_int32 *presult)
+{
+l_int32 x1, y1, w1, h1, x2, y2, w2, h2, valid1, valid2;
+
+ PROCNAME("boxContains");
+
+ if (!presult)
+ return ERROR_INT("&result not defined", procName, 1);
+ *presult = 0;
+ if (!box1 || !box2)
+ return ERROR_INT("boxes not both defined", procName, 1);
+ boxIsValid(box1, &valid1);
+ boxIsValid(box2, &valid2);
+ if (!valid1 || !valid2)
+ return ERROR_INT("boxes not both valid", procName, 1);
+
+ 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;
+ return 0;
+}
+
+
+/*!
+ * \brief boxIntersects()
+ *
+ * \param[in] box1, box2
+ * \param[out] presult 1 if any part of box2 is contained in box1;
+ * 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+boxIntersects(BOX *box1,
+ BOX *box2,
+ l_int32 *presult)
+{
+l_int32 l1, l2, r1, r2, t1, t2, b1, b2, w1, h1, w2, h2, valid1, valid2;
+
+ PROCNAME("boxIntersects");
+
+ if (!presult)
+ return ERROR_INT("&result not defined", procName, 1);
+ *presult = 0;
+ if (!box1 || !box2)
+ return ERROR_INT("boxes not both defined", procName, 1);
+ boxIsValid(box1, &valid1);
+ boxIsValid(box2, &valid2);
+ if (!valid1 || !valid2)
+ return ERROR_INT("boxes not both valid", 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;
+}
+
+
+/*!
+ * \brief boxaContainedInBox()
+ *
+ * \param[in] boxas
+ * \param[in] box for containment
+ * \return boxad boxa with all boxes in boxas that are entirely
+ * contained in box, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) All boxes in %boxas that are entirely outside box are removed.
+ * (2) If %box is not valid, returns an empty boxa.
+ * </pre>
+ */
+BOXA *
+boxaContainedInBox(BOXA *boxas,
+ BOX *box)
+{
+l_int32 i, n, val, valid;
+BOX *box1;
+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);
+ n = boxaGetCount(boxas);
+ boxIsValid(box, &valid);
+ if (n == 0 || !valid)
+ return boxaCreate(1); /* empty */
+
+ boxad = boxaCreate(0);
+ for (i = 0; i < n; i++) {
+ if ((box1 = boxaGetValidBox(boxas, i, L_CLONE)) == NULL)
+ continue;
+ boxContains(box, box1, &val);
+ if (val == 1)
+ boxaAddBox(boxad, box1, L_COPY);
+ boxDestroy(&box1); /* destroy the clone */
+ }
+
+ return boxad;
+}
+
+
+/*!
+ * \brief boxaContainedInBoxCount()
+ *
+ * \param[in] boxa
+ * \param[in] box for selecting contained boxes in %boxa
+ * \param[out] pcount number of boxes intersecting the box
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %box is not valid, returns a zero count.
+ * </pre>
+ */
+l_ok
+boxaContainedInBoxCount(BOXA *boxa,
+ BOX *box,
+ l_int32 *pcount)
+{
+l_int32 i, n, val, valid;
+BOX *box1;
+
+ PROCNAME("boxaContainedInBoxCount");
+
+ if (!pcount)
+ return ERROR_INT("&count not defined", procName, 1);
+ *pcount = 0;
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+ if (!box)
+ return ERROR_INT("box not defined", procName, 1);
+ n = boxaGetCount(boxa);
+ boxIsValid(box, &valid);
+ if (n == 0 || !valid)
+ return 0;
+
+ for (i = 0; i < n; i++) {
+ if ((box1 = boxaGetValidBox(boxa, i, L_CLONE)) == NULL)
+ continue;
+ boxContains(box, box1, &val);
+ if (val == 1)
+ (*pcount)++;
+ boxDestroy(&box1);
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief boxaContainedInBoxa()
+ *
+ * \param[in] boxa1, boxa2
+ * \param[out] pcontained 1 if every box in boxa2 is contained in
+ * some box in boxa1; 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+boxaContainedInBoxa(BOXA *boxa1,
+ BOXA *boxa2,
+ l_int32 *pcontained)
+{
+l_int32 i, j, n1, n2, cont, result;
+BOX *box1, *box2;
+
+ PROCNAME("boxaContainedInBoxa");
+
+ if (!pcontained)
+ return ERROR_INT("&contained not defined", procName, 1);
+ *pcontained = 0;
+ if (!boxa1 || !boxa2)
+ return ERROR_INT("boxa1 and boxa2 not both defined", procName, 1);
+
+ n1 = boxaGetCount(boxa1);
+ n2 = boxaGetCount(boxa2);
+ for (i = 0; i < n2; i++) {
+ if ((box2 = boxaGetValidBox(boxa2, i, L_CLONE)) == NULL)
+ continue;
+ cont = 0;
+ for (j = 0; j < n1; j++) {
+ if ((box1 = boxaGetValidBox(boxa1, j, L_CLONE)) == NULL)
+ continue;
+ boxContains(box1, box2, &result);
+ boxDestroy(&box1);
+ if (result) {
+ cont = 1;
+ break;
+ }
+ }
+ boxDestroy(&box2);
+ if (!cont) return 0;
+ }
+
+ *pcontained = 1;
+ return 0;
+}
+
+
+/*!
+ * \brief boxaIntersectsBox()
+ *
+ * \param[in] boxas
+ * \param[in] box for intersecting
+ * \return boxad boxa with all boxes in boxas that intersect box,
+ * or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) All boxes in boxa that intersect with box (i.e., are completely
+ * or partially contained in box) are retained.
+ * </pre>
+ */
+BOXA *
+boxaIntersectsBox(BOXA *boxas,
+ BOX *box)
+{
+l_int32 i, n, val, valid;
+BOX *box1;
+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);
+ n = boxaGetCount(boxas);
+ boxIsValid(box, &valid);
+ if (n == 0 || !valid)
+ return boxaCreate(1); /* empty */
+
+ boxad = boxaCreate(0);
+ for (i = 0; i < n; i++) {
+ if ((box1 = boxaGetValidBox(boxas, i, L_CLONE)) == NULL)
+ continue;
+ boxIntersects(box, box1, &val);
+ if (val == 1)
+ boxaAddBox(boxad, box1, L_COPY);
+ boxDestroy(&box1); /* destroy the clone */
+ }
+
+ return boxad;
+}
+
+
+/*!
+ * \brief boxaIntersectsBoxCount()
+ *
+ * \param[in] boxa
+ * \param[in] box for selecting intersecting boxes in %boxa
+ * \param[out] pcount number of boxes intersecting the box
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+boxaIntersectsBoxCount(BOXA *boxa,
+ BOX *box,
+ l_int32 *pcount)
+{
+l_int32 i, n, val, valid;
+BOX *box1;
+
+ PROCNAME("boxaIntersectsBoxCount");
+
+ if (!pcount)
+ return ERROR_INT("&count not defined", procName, 1);
+ *pcount = 0;
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+ if (!box)
+ return ERROR_INT("box not defined", procName, 1);
+ n = boxaGetCount(boxa);
+ boxIsValid(box, &valid);
+ if (n == 0 || !valid)
+ return 0;
+
+ for (i = 0; i < n; i++) {
+ if ((box1 = boxaGetValidBox(boxa, i, L_CLONE)) == NULL)
+ continue;
+ boxIntersects(box, box1, &val);
+ if (val == 1)
+ (*pcount)++;
+ boxDestroy(&box1);
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief boxaClipToBox()
+ *
+ * \param[in] boxas
+ * \param[in] box for clipping
+ * \return boxad boxa with boxes in boxas clipped to box, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) All boxes in boxa not intersecting with box are removed, and
+ * the remaining boxes are clipped to box.
+ * </pre>
+ */
+BOXA *
+boxaClipToBox(BOXA *boxas,
+ BOX *box)
+{
+l_int32 i, n, valid;
+BOX *box1, *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);
+ n = boxaGetCount(boxas);
+ boxIsValid(box, &valid);
+ if (n == 0 || !valid)
+ return boxaCreate(1); /* empty */
+
+ boxad = boxaCreate(0);
+ for (i = 0; i < n; i++) {
+ if ((box1 = boxaGetValidBox(boxas, i, L_CLONE)) == NULL)
+ continue;
+ if ((boxo = boxOverlapRegion(box, box1)) != NULL)
+ boxaAddBox(boxad, boxo, L_INSERT);
+ boxDestroy(&box1);
+ }
+
+ return boxad;
+}
+
+
+/*!
+ * \brief boxaCombineOverlaps()
+ *
+ * \param[in] boxas
+ * \param[in,out] pixadb debug output
+ * \return boxad where each set of boxes in boxas that overlap are combined
+ * into a single bounding box in boxad, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) If there are no overlapping boxes, it simply returns a copy
+ * of %boxas.
+ * (2) Input an empty %pixadb, using pixaCreate(0), for debug output.
+ * The output gives 2 visualizations of the boxes per iteration;
+ * boxes in red before, and added boxes in green after. Note that
+ * all pixels in the red boxes are contained in the green ones.
+ * (3) The alternative method of painting each rectangle and finding
+ * the 4-connected components gives a different result in
+ * general, because two non-overlapping (but touching)
+ * rectangles, when rendered, are 4-connected and will be joined.
+ * (4) A bad case computationally 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 bounding boxes of the connected components, even for
+ * thousands of rectangles.
+ * </pre>
+ */
+BOXA *
+boxaCombineOverlaps(BOXA *boxas,
+ PIXA *pixadb)
+{
+l_int32 i, j, w, h, n1, n2, overlap, niters;
+BOX *box1, *box2, *box3;
+BOXA *boxa1, *boxa2;
+PIX *pix1;
+
+ PROCNAME("boxaCombineOverlaps");
+
+ if (!boxas)
+ return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+
+ if (pixadb) boxaGetExtent(boxas, &w, &h, NULL);
+
+ boxa1 = boxaCopy(boxas, L_COPY);
+ n1 = boxaGetCount(boxa1);
+ niters = 0;
+ while (1) { /* loop until no change from previous iteration */
+ niters++;
+ if (pixadb) {
+ pix1 = pixCreate(w + 5, h + 5, 32);
+ pixSetAll(pix1);
+ pixRenderBoxaArb(pix1, boxa1, 2, 255, 0, 0);
+ pixaAddPix(pixadb, pix1, L_COPY);
+ }
+
+ /* Combine overlaps for this iteration */
+ for (i = 0; i < n1; i++) {
+ if ((box1 = boxaGetValidBox(boxa1, i, L_COPY)) == NULL)
+ continue;
+ for (j = i + 1; j < n1; j++) {
+ if ((box2 = boxaGetValidBox(boxa1, j, L_COPY)) == NULL)
+ continue;
+ boxIntersects(box1, box2, &overlap);
+ if (overlap) {
+ box3 = boxBoundingRegion(box1, box2);
+ boxaReplaceBox(boxa1, i, box3);
+ boxaReplaceBox(boxa1, j, boxCreate(0, 0, 0, 0));
+ boxDestroy(&box1);
+ box1 = boxCopy(box3);
+ }
+ boxDestroy(&box2);
+ }
+ boxDestroy(&box1);
+ }
+ boxa2 = boxaSaveValid(boxa1, L_COPY);
+ n2 = boxaGetCount(boxa2);
+ boxaDestroy(&boxa1);
+ boxa1 = boxa2;
+ if (n1 == n2) {
+ if (pixadb) pixDestroy(&pix1);
+ break;
+ }
+ n1 = n2;
+ if (pixadb) {
+ pixRenderBoxaArb(pix1, boxa1, 2, 0, 255, 0);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ }
+ }
+
+ if (pixadb)
+ L_INFO("number of iterations: %d\n", procName, niters);
+ return boxa1;
+}
+
+
+/*!
+ * \brief boxaCombineOverlapsInPair()
+ *
+ * \param[in] boxas1 input boxa1
+ * \param[in] boxas2 input boxa2
+ * \param[out] pboxad1 output boxa1
+ * \param[out] pboxad2 output boxa2
+ * \param[in,out] pixadb debug output
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) One of three things happens to each box in %boxa1 and %boxa2:
+ * * it gets absorbed into a larger box that it overlaps with
+ * * it absorbs a smaller (by area) box that it overlaps with
+ * and gets larger, using the bounding region of the 2 boxes
+ * * it is unchanged (including absorbing smaller boxes that
+ * are contained within it).
+ * (2) If all the boxes from one of the input boxa are absorbed, this
+ * returns an empty boxa.
+ * (3) Input an empty %pixadb, using pixaCreate(0), for debug output
+ * (4) This is useful if different operations are to be carried out
+ * on possibly overlapping rectangular regions, and it is desired
+ * to have only one operation on any rectangular region.
+ * </pre>
+ */
+l_ok
+boxaCombineOverlapsInPair(BOXA *boxas1,
+ BOXA *boxas2,
+ BOXA **pboxad1,
+ BOXA **pboxad2,
+ PIXA *pixadb)
+{
+l_int32 i, j, w, h, w2, h2, n1, n2, n1i, n2i, niters;
+l_int32 overlap, bigger, area1, area2;
+BOX *box1, *box2, *box3;
+BOXA *boxa1, *boxa2, *boxac1, *boxac2;
+PIX *pix1;
+
+ PROCNAME("boxaCombineOverlapsInPair");
+
+ if (pboxad1) *pboxad1 = NULL;
+ if (pboxad2) *pboxad2 = NULL;
+ if (!boxas1 || !boxas2)
+ return ERROR_INT("boxas1 and boxas2 not both defined", procName, 1);
+ if (!pboxad1 || !pboxad2)
+ return ERROR_INT("&boxad1 and &boxad2 not both defined", procName, 1);
+
+ if (pixadb) {
+ boxaGetExtent(boxas1, &w, &h, NULL);
+ boxaGetExtent(boxas2, &w2, &h2, NULL);
+ w = L_MAX(w, w2);
+ h = L_MAX(h, w2);
+ }
+
+ /* Let the boxa with the largest area have first crack at the other */
+ boxaGetArea(boxas1, &area1);
+ boxaGetArea(boxas2, &area2);
+ if (area1 >= area2) {
+ boxac1 = boxaCopy(boxas1, L_COPY);
+ boxac2 = boxaCopy(boxas2, L_COPY);
+ } else {
+ boxac1 = boxaCopy(boxas2, L_COPY);
+ boxac2 = boxaCopy(boxas1, L_COPY);
+ }
+
+ n1i = boxaGetCount(boxac1);
+ n2i = boxaGetCount(boxac2);
+ niters = 0;
+ while (1) {
+ niters++;
+ if (pixadb) {
+ pix1 = pixCreate(w + 5, h + 5, 32);
+ pixSetAll(pix1);
+ pixRenderBoxaArb(pix1, boxac1, 2, 255, 0, 0);
+ pixRenderBoxaArb(pix1, boxac2, 2, 0, 255, 0);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ }
+
+ /* First combine boxes in each set */
+ boxa1 = boxaCombineOverlaps(boxac1, NULL);
+ boxa2 = boxaCombineOverlaps(boxac2, NULL);
+
+ /* Now combine boxes between sets */
+ n1 = boxaGetCount(boxa1);
+ n2 = boxaGetCount(boxa2);
+ for (i = 0; i < n1; i++) { /* 1 eats 2 */
+ if ((box1 = boxaGetValidBox(boxa1, i, L_COPY)) == NULL)
+ continue;
+ for (j = 0; j < n2; j++) {
+ if ((box2 = boxaGetValidBox(boxa2, j, L_COPY)) == NULL)
+ continue;
+ boxIntersects(box1, box2, &overlap);
+ boxCompareSize(box1, box2, L_SORT_BY_AREA, &bigger);
+ if (overlap && (bigger == 1)) {
+ box3 = boxBoundingRegion(box1, box2);
+ boxaReplaceBox(boxa1, i, box3);
+ boxaReplaceBox(boxa2, j, boxCreate(0, 0, 0, 0));
+ boxDestroy(&box1);
+ box1 = boxCopy(box3);
+ }
+ boxDestroy(&box2);
+ }
+ boxDestroy(&box1);
+ }
+ for (i = 0; i < n2; i++) { /* 2 eats 1 */
+ if ((box2 = boxaGetValidBox(boxa2, i, L_COPY)) == NULL)
+ continue;
+ for (j = 0; j < n1; j++) {
+ if ((box1 = boxaGetValidBox(boxa1, j, L_COPY)) == NULL)
+ continue;
+ boxIntersects(box1, box2, &overlap);
+ boxCompareSize(box2, box1, L_SORT_BY_AREA, &bigger);
+ if (overlap && (bigger == 1)) {
+ box3 = boxBoundingRegion(box1, box2);
+ boxaReplaceBox(boxa2, i, box3);
+ boxaReplaceBox(boxa1, j, boxCreate(0, 0, 0, 0));
+ boxDestroy(&box2);
+ box2 = boxCopy(box3);
+ }
+ boxDestroy(&box1);
+ }
+ boxDestroy(&box2);
+ }
+ boxaDestroy(&boxac1);
+ boxaDestroy(&boxac2);
+ boxac1 = boxaSaveValid(boxa1, L_COPY); /* remove invalid boxes */
+ boxac2 = boxaSaveValid(boxa2, L_COPY);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ n1 = boxaGetCount(boxac1);
+ n2 = boxaGetCount(boxac2);
+ if (n1 == n1i && n2 == n2i) break;
+ n1i = n1;
+ n2i = n2;
+ if (pixadb) {
+ pix1 = pixCreate(w + 5, h + 5, 32);
+ pixSetAll(pix1);
+ pixRenderBoxaArb(pix1, boxac1, 2, 255, 0, 0);
+ pixRenderBoxaArb(pix1, boxac2, 2, 0, 255, 0);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ }
+ }
+
+ if (pixadb)
+ L_INFO("number of iterations: %d\n", procName, niters);
+ *pboxad1 = boxac1;
+ *pboxad2 = boxac2;
+ return 0;
+}
+
+
+/*!
+ * \brief boxOverlapRegion()
+ *
+ * \param[in] box1, box2
+ * \return box of overlap region between input boxes;
+ * NULL if no overlap or on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is the geometric intersection of the two rectangles.
+ * </pre>
+ */
+BOX *
+boxOverlapRegion(BOX *box1,
+ BOX *box2)
+{
+l_int32 l1, l2, r1, r2, t1, t2, b1, b2, w1, h1, w2, h2, ld, td, rd, bd;
+l_int32 valid1, valid2;
+
+ PROCNAME("boxOverlapRegion");
+
+ if (!box1 || !box2)
+ return (BOX *)ERROR_PTR("boxes not both defined", procName, NULL);
+ boxIsValid(box1, &valid1);
+ boxIsValid(box2, &valid2);
+ if (!valid1 || !valid2) {
+ L_WARNING("at least one box is invalid\n", procName);
+ return 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);
+}
+
+
+/*!
+ * \brief boxBoundingRegion()
+ *
+ * \param[in] box1, box2
+ * \return box of bounding region containing the input boxes;
+ * NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is the geometric union of the two rectangles.
+ * (2) Invalid boxes are ignored. This returns an invalid box
+ * if both input boxes are invalid.
+ * (3) For the geometric union of a boxa, use boxaGetExtent().
+ * </pre>
+ */
+BOX *
+boxBoundingRegion(BOX *box1,
+ BOX *box2)
+{
+l_int32 l1, l2, r1, r2, t1, t2, b1, b2, w1, h1, w2, h2, ld, td, rd, bd;
+l_int32 valid1, valid2;
+
+ PROCNAME("boxBoundingRegion");
+
+ if (!box1 || !box2)
+ return (BOX *)ERROR_PTR("boxes not both defined", procName, NULL);
+ boxIsValid(box1, &valid1);
+ boxIsValid(box2, &valid2);
+ if (!valid1 && !valid2) {
+ L_WARNING("both boxes are invalid\n", procName);
+ return boxCreate(0, 0, 0, 0);
+ }
+ if (valid1 && !valid2)
+ return boxCopy(box1);
+ if (!valid1 && valid2)
+ return boxCopy(box2);
+
+ 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);
+}
+
+
+/*!
+ * \brief boxOverlapFraction()
+ *
+ * \param[in] box1, box2
+ * \param[out] pfract the fraction of box2 overlapped by box1
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) The result depends on the order of the input boxes,
+ * because the overlap is taken as a fraction of box2.
+ * (2) If at least one box is not valid, there is no overlap.
+ * </pre>
+ */
+l_ok
+boxOverlapFraction(BOX *box1,
+ BOX *box2,
+ l_float32 *pfract)
+{
+l_int32 w2, h2, w, h, valid1, valid2;
+BOX *boxo;
+
+ PROCNAME("boxOverlapFraction");
+
+ if (!pfract)
+ return ERROR_INT("&fract not defined", procName, 1);
+ *pfract = 0.0;
+ if (!box1 || !box2)
+ return ERROR_INT("boxes not both defined", procName, 1);
+ boxIsValid(box1, &valid1);
+ boxIsValid(box2, &valid2);
+ if (!valid1 || !valid2) {
+ L_WARNING("boxes not both valid\n", procName);
+ return 0;
+ }
+
+ 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;
+}
+
+
+/*!
+ * \brief boxOverlapArea()
+ *
+ * \param[in] box1, box2
+ * \param[out] parea the number of pixels in the overlap
+ * \return 0 if OK, 1 on error.
+ */
+l_ok
+boxOverlapArea(BOX *box1,
+ BOX *box2,
+ l_int32 *parea)
+{
+l_int32 w, h, valid1, valid2;
+BOX *box;
+
+ PROCNAME("boxOverlapArea");
+
+ if (!parea)
+ return ERROR_INT("&area not defined", procName, 1);
+ *parea = 0;
+ if (!box1 || !box2)
+ return ERROR_INT("boxes not both defined", procName, 1);
+ boxIsValid(box1, &valid1);
+ boxIsValid(box2, &valid2);
+ if (!valid1 || !valid2)
+ return ERROR_INT("boxes not both valid", 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;
+}
+
+
+/*!
+ * \brief boxaHandleOverlaps()
+ *
+ * \param[in] boxas
+ * \param[in] op L_COMBINE, L_REMOVE_SMALL
+ * \param[in] range forward distance over which overlaps
+ * are checked; > 0
+ * \param[in] min_overlap minimum fraction of smaller box required for
+ * overlap to count; 0.0 to ignore
+ * \param[in] max_ratio maximum fraction of small/large areas for
+ * overlap to count; 1.0 to ignore
+ * \param[out] pnamap [optional] combining map
+ * \return boxad, or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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++) {
+ if ((box1 = boxaGetValidBox(boxas, i, L_CLONE)) == NULL)
+ continue;
+ 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++) {
+ if ((box2 = boxaGetValidBox(boxas, j, L_CLONE)) == NULL)
+ continue;
+ 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 / (l_float32)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 = (l_float32)overlap_area / (l_float32)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;
+}
+
+
+/*!
+ * \brief boxOverlapDistance()
+ *
+ * \param[in] box1, box2 two boxes, in any order
+ * \param[out] ph_ovl [optional] horizontal overlap
+ * \param[out] pv_ovl [optional] vertical overlap
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This measures horizontal and vertical overlap of the
+ * two boxes. Horizontal and vertical overlap are measured
+ * independently. We need to consider several cases to clarify.
+ * (2) A positive horizontal overlap means that there is at least
+ * one point on the the %box1 boundary with the same x-component
+ * as some point on the %box2 boundary. Conversely, with a zero
+ * or negative horizontal overlap, there are no boundary pixels
+ * in %box1 that share an x-component with a boundary pixel in %box2.
+ * (3) For a zero or negative horizontal overlap, o <= 0, the minimum
+ * difference in the x-component between pixels on the boundaries
+ * of the two boxes is d = -o + 1.
+ * (4) Likewise for vertical overlaps.
+ * </pre>
+ */
+l_ok
+boxOverlapDistance(BOX *box1,
+ BOX *box2,
+ l_int32 *ph_ovl,
+ l_int32 *pv_ovl)
+{
+l_int32 l1, t1, w1, h1, r1, b1, l2, t2, w2, h2, r2, b2, valid1, valid2;
+
+ PROCNAME("boxOverlapDistance");
+
+ if (!ph_ovl && !pv_ovl)
+ return ERROR_INT("nothing to do", procName, 1);
+ if (ph_ovl) *ph_ovl = 0;
+ if (pv_ovl) *pv_ovl = 0;
+ if (!box1 || !box2)
+ return ERROR_INT("boxes not both defined", procName, 1);
+ boxIsValid(box1, &valid1);
+ boxIsValid(box2, &valid2);
+ if (!valid1 || !valid2)
+ return ERROR_INT("boxes not both valid", procName, 1);
+
+ if (ph_ovl) {
+ 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_ovl = r1 - l2;
+ else
+ *ph_ovl = r2 - l1;
+ }
+ if (pv_ovl) {
+ 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_ovl = b1 - t2;
+ else
+ *pv_ovl = b2 - t1;
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief boxSeparationDistance()
+ *
+ * \param[in] box1, box2 two boxes, in any order
+ * \param[out] ph_sep horizontal separation
+ * \param[out] pv_sep vertical separation
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This measures the Manhattan distance between the closest points
+ * on the boundaries of the two boxes. When the boxes overlap
+ * (including touching along a line or at a corner), the
+ * horizontal and vertical distances are 0.
+ * (2) The distances represent the horizontal and vertical separation
+ * of the two boxes. The boxes have a nonzero intersection when
+ * both the horizontal and vertical overlaps are positive, and
+ * for that case both horizontal and vertical separation
+ * distances are 0.
+ * (3) If the horizontal overlap of the boxes is positive, the
+ * horizontal separation between nearest points on respective
+ * boundaries is 0, and likewise for the vertical overlap.
+ * (4) If the horizontal overlap ho <= 0, the horizontal
+ * separation between nearest points is d = -ho + 1.
+ * Likewise, if the vertical overlap vo <= 0, the vertical
+ * separation between nearest points is d = -vo + 1.
+ * </pre>
+ */
+l_ok
+boxSeparationDistance(BOX *box1,
+ BOX *box2,
+ l_int32 *ph_sep,
+ l_int32 *pv_sep)
+{
+l_int32 h_ovl, v_ovl, valid1, valid2;
+
+ PROCNAME("boxSeparationDistance");
+
+ if (ph_sep) *ph_sep = 0;
+ if (pv_sep) *pv_sep = 0;
+ if (!ph_sep || !pv_sep)
+ return ERROR_INT("&h_sep and &v_sep not both defined", procName, 1);
+ if (!box1 || !box2)
+ return ERROR_INT("boxes not both defined", procName, 1);
+ boxIsValid(box1, &valid1);
+ boxIsValid(box2, &valid2);
+ if (!valid1 || !valid2)
+ return ERROR_INT("boxes not both valid", procName, 1);
+
+ boxOverlapDistance(box1, box2, &h_ovl, &v_ovl);
+ if (h_ovl <= 0)
+ *ph_sep = -h_ovl + 1;
+ if (v_ovl <= 0)
+ *pv_sep = -v_ovl + 1;
+ return 0;
+}
+
+
+/*!
+ * \brief boxCompareSize()
+ *
+ * \param[in] box1, box2
+ * \param[in] type L_SORT_BY_WIDTH, L_SORT_BY_HEIGHT,
+ * L_SORT_BY_MAX_DIMENSION, L_SORT_BY_PERIMETER,
+ * L_SORT_BY_AREA,
+ * \param[out] prel 1 if box1 > box2, 0 if the same, -1 if box1 < box2
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) We're re-using the SORT enum for these comparisons.
+ * </pre>
+ */
+l_ok
+boxCompareSize(BOX *box1,
+ BOX *box2,
+ l_int32 type,
+ l_int32 *prel)
+{
+l_int32 w1, h1, w2, h2, size1, size2, valid1, valid2;
+
+ PROCNAME("boxCompareSize");
+
+ if (!prel)
+ return ERROR_INT("&rel not defined", procName, 1);
+ *prel = 0;
+ if (!box1 || !box2)
+ return ERROR_INT("boxes not both defined", procName, 1);
+ boxIsValid(box1, &valid1);
+ boxIsValid(box2, &valid2);
+ if (!valid1 || !valid2)
+ return ERROR_INT("boxes not both valid", procName, 1);
+ if (type != L_SORT_BY_WIDTH && type != L_SORT_BY_HEIGHT &&
+ type != L_SORT_BY_MAX_DIMENSION && type != L_SORT_BY_PERIMETER &&
+ type != L_SORT_BY_AREA)
+ return ERROR_INT("invalid compare type", procName, 1);
+
+ boxGetGeometry(box1, NULL, NULL, &w1, &h1);
+ boxGetGeometry(box2, NULL, NULL, &w2, &h2);
+ if (type == L_SORT_BY_WIDTH) {
+ *prel = (w1 > w2) ? 1 : ((w1 == w2) ? 0 : -1);
+ } else if (type == L_SORT_BY_HEIGHT) {
+ *prel = (h1 > h2) ? 1 : ((h1 == h2) ? 0 : -1);
+ } else if (type == L_SORT_BY_MAX_DIMENSION) {
+ size1 = L_MAX(w1, h1);
+ size2 = L_MAX(w2, h2);
+ *prel = (size1 > size2) ? 1 : ((size1 == size2) ? 0 : -1);
+ } else if (type == L_SORT_BY_PERIMETER) {
+ size1 = w1 + h1;
+ size2 = w2 + h2;
+ *prel = (size1 > size2) ? 1 : ((size1 == size2) ? 0 : -1);
+ } else if (type == L_SORT_BY_AREA) {
+ size1 = w1 * h1;
+ size2 = w2 * h2;
+ *prel = (size1 > size2) ? 1 : ((size1 == size2) ? 0 : -1);
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief boxContainsPt()
+ *
+ * \param[in] box
+ * \param[in] x, y a point
+ * \param[out] pcontains 1 if box contains point; 0 otherwise
+ * \return 0 if OK, 1 on error.
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaGetNearestToPt()
+ *
+ * \param[in] boxa
+ * \param[in] x, y point
+ * \return box with centroid closest to the given point [x,y],
+ * or NULL if no boxes in boxa
+ *
+ * <pre>
+ * Notes:
+ * (1) Uses euclidean distance between centroid and point.
+ * </pre>
+ */
+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++) {
+ if ((box = boxaGetValidBox(boxa, i, L_CLONE)) == NULL)
+ continue;
+ 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);
+}
+
+
+/*!
+ * \brief boxaGetNearestToLine()
+ *
+ * \param[in] boxa
+ * \param[in] x, y (y = -1 for vertical line; x = -1 for horiz line)
+ * \return box with centroid closest to the given line,
+ * or NULL if no boxes in boxa
+ *
+ * <pre>
+ * Notes:
+ * (1) For a horizontal line at some value y, get the minimum of the
+ * distance |yc - y| from the box centroid yc value to y;
+ * likewise minimize |xc - x| for a vertical line at x.
+ * (2) Input y < 0, x >= 0 to indicate a vertical line at x, and
+ * x < 0, y >= 0 for a horizontal line at y.
+ * </pre>
+ */
+BOX *
+boxaGetNearestToLine(BOXA *boxa,
+ l_int32 x,
+ l_int32 y)
+{
+l_int32 i, n, minindex;
+l_float32 dist, mindist, cx, cy;
+BOX *box;
+
+ PROCNAME("boxaGetNearestToLine");
+
+ if (!boxa)
+ return (BOX *)ERROR_PTR("boxa not defined", procName, NULL);
+ if ((n = boxaGetCount(boxa)) == 0)
+ return (BOX *)ERROR_PTR("n = 0", procName, NULL);
+ if (y >= 0 && x >= 0)
+ return (BOX *)ERROR_PTR("either x or y must be < 0", procName, NULL);
+ if (y < 0 && x < 0)
+ return (BOX *)ERROR_PTR("either x or y must be >= 0", procName, NULL);
+
+ mindist = 1000000000.;
+ minindex = 0;
+ for (i = 0; i < n; i++) {
+ if ((box = boxaGetValidBox(boxa, i, L_CLONE)) == NULL)
+ continue;
+ boxGetCenter(box, &cx, &cy);
+ if (x >= 0)
+ dist = L_ABS(cx - (l_float32)x);
+ else /* y >= 0 */
+ dist = L_ABS(cy - (l_float32)y);
+ if (dist < mindist) {
+ minindex = i;
+ mindist = dist;
+ }
+ boxDestroy(&box);
+ }
+
+ return boxaGetBox(boxa, minindex, L_COPY);
+}
+
+
+/*!
+ * \brief boxaFindNearestBoxes()
+ *
+ * \param[in] boxa either unsorted, or 2D sorted in LR/TB scan order
+ * \param[in] dist_select L_NON_NEGATIVE, L_ALL
+ * \param[in] range search distance from box i; use 0 to search
+ * entire boxa (e.g., if it's not 2D sorted)
+ * \param[out] pnaaindex for each box in %boxa, contains a numa of 4
+ * box indices (per direction) of the nearest box
+ * \param[out] pnaadist for each box in %boxa, this contains a numa
+ * \return 0 if OK, 1 on error
+ * <pre>
+ * Notes:
+ * (1) See boxaGetNearestByDirection() for usage of %dist_select
+ * and %range.
+ * </pre>
+ */
+l_ok
+boxaFindNearestBoxes(BOXA *boxa,
+ l_int32 dist_select,
+ l_int32 range,
+ NUMAA **pnaaindex,
+ NUMAA **pnaadist)
+{
+l_int32 i, n, index, dist;
+NUMA *nai, *nad;
+NUMAA *naai, *naad;
+
+ PROCNAME("boxaFindNearestBoxes");
+
+ if (pnaaindex) *pnaaindex = NULL;
+ if (pnaadist) *pnaadist = NULL;
+ if (!pnaaindex)
+ return ERROR_INT("&naaindex not defined", procName, 1);
+ if (!pnaadist)
+ return ERROR_INT("&naadist not defined", procName, 1);
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+
+ n = boxaGetCount(boxa);
+ naai = numaaCreate(n);
+ naad = numaaCreate(n);
+ *pnaaindex = naai;
+ *pnaadist = naad;
+ for (i = 0; i < n; i++) {
+ nai = numaCreate(4);
+ nad = numaCreate(4);
+ boxaGetNearestByDirection(boxa, i, L_FROM_LEFT, dist_select,
+ range, &index, &dist);
+ numaAddNumber(nai, index);
+ numaAddNumber(nad, dist);
+ boxaGetNearestByDirection(boxa, i, L_FROM_RIGHT, dist_select,
+ range, &index, &dist);
+ numaAddNumber(nai, index);
+ numaAddNumber(nad, dist);
+ boxaGetNearestByDirection(boxa, i, L_FROM_TOP, dist_select,
+ range, &index, &dist);
+ numaAddNumber(nai, index);
+ numaAddNumber(nad, dist);
+ boxaGetNearestByDirection(boxa, i, L_FROM_BOT, dist_select,
+ range, &index, &dist);
+ numaAddNumber(nai, index);
+ numaAddNumber(nad, dist);
+ numaaAddNuma(naai, nai, L_INSERT);
+ numaaAddNuma(naad, nad, L_INSERT);
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief boxaGetNearestByDirection()
+ *
+ * \param[in] boxa either unsorted, or 2D sorted in LR/TB scan order
+ * \param[in] i box we test against
+ * \param[in] dir direction to look: L_FROM_LEFT, L_FROM_RIGHT,
+ * L_FROM_TOP, L_FROM_BOT
+ * \param[in] dist_select L_NON_NEGATIVE, L_ALL
+ * \param[in] range search distance from box i; use 0 to search
+ * entire boxa (e.g., if it's not 2D sorted)
+ * \param[out] pindex index in boxa of nearest box with overlapping
+ * coordinates in the indicated direction;
+ * -1 if there is no box
+ * \param[out] pdist distance of the nearest box in the indicated
+ * direction; 100000 if no box
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For efficiency, use a LR/TD sorted %boxa, which can be
+ * made by flattening a 2D sorted boxaa. In that case,
+ * %range can be some positive integer like 50.
+ * (2) If boxes overlap, the distance will be < 0. Use %dist_select
+ * to determine if these should count or not. If L_ALL, then
+ * one box will match as the nearest to another in 2 or more
+ * directions.
+ * </pre>
+ */
+l_ok
+boxaGetNearestByDirection(BOXA *boxa,
+ l_int32 i,
+ l_int32 dir,
+ l_int32 dist_select,
+ l_int32 range,
+ l_int32 *pindex,
+ l_int32 *pdist)
+{
+l_int32 j, jmin, jmax, n, mindist, dist, index;
+l_int32 x, y, w, h, bx, by, bw, bh;
+
+ PROCNAME("boxaGetNearestByDirection");
+
+ if (pindex) *pindex = -1;
+ if (pdist) *pdist = 100000;
+ if (!pindex)
+ return ERROR_INT("&index not defined", procName, 1);
+ if (!pdist)
+ return ERROR_INT("&dist not defined", procName, 1);
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+ if (dir != L_FROM_LEFT && dir != L_FROM_RIGHT &&
+ dir != L_FROM_TOP && dir != L_FROM_BOT)
+ return ERROR_INT("invalid dir", procName, 1);
+ if (dist_select != L_NON_NEGATIVE && dist_select != L_ALL)
+ return ERROR_INT("invalid dist_select", procName, 1);
+ n = boxaGetCount(boxa);
+ if (i < 0 || i >= n)
+ return ERROR_INT("invalid box index", procName, 1);
+
+ jmin = (range <= 0) ? 0 : L_MAX(0, i - range);
+ jmax = (range <= 0) ? n - 1 : L_MIN(n -1, i + range);
+ boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h);
+ mindist = 100000;
+ index = -1;
+ if (dir == L_FROM_LEFT || dir == L_FROM_RIGHT) {
+ for (j = jmin; j <= jmax; j++) {
+ if (j == i) continue;
+ boxaGetBoxGeometry(boxa, j, &bx, &by, &bw, &bh);
+ if ((bx >= x && dir == L_FROM_LEFT) || /* not to the left */
+ (x >= bx && dir == L_FROM_RIGHT)) /* not to the right */
+ continue;
+ if (boxHasOverlapInXorY(y, h, by, bh) == 1) {
+ dist = boxGetDistanceInXorY(x, w, bx, bw);
+ if (dist_select == L_NON_NEGATIVE && dist < 0) continue;
+ if (dist < mindist) {
+ mindist = dist;
+ index = j;
+ }
+ }
+ }
+ } else if (dir == L_FROM_TOP || dir == L_FROM_BOT) {
+ for (j = jmin; j <= jmax; j++) {
+ if (j == i) continue;
+ boxaGetBoxGeometry(boxa, j, &bx, &by, &bw, &bh);
+ if ((by >= y && dir == L_FROM_TOP) || /* not above */
+ (y >= by && dir == L_FROM_BOT)) /* not below */
+ continue;
+ if (boxHasOverlapInXorY(x, w, bx, bw) == 1) {
+ dist = boxGetDistanceInXorY(y, h, by, bh);
+ if (dist_select == L_NON_NEGATIVE && dist < 0) continue;
+ if (dist < mindist) {
+ mindist = dist;
+ index = j;
+ }
+ }
+ }
+ }
+ *pindex = index;
+ *pdist = mindist;
+ return 0;
+}
+
+
+/*!
+ * \brief boxHasOverlapInXorY()
+ *
+ * \param[in] c1 left or top coordinate of box1
+ * \param[in] s1 width or height of box1
+ * \param[in] c2 left or top coordinate of box2
+ * \param[in] s2 width or height of box2
+ * \return 0 if no overlap; 1 if any overlap
+ *
+ * <pre>
+ * Notes:
+ * (1) Like boxGetDistanceInXorY(), this is used for overlaps both in
+ * x (which projected vertically) and in y (projected horizontally)
+ * </pre>
+ */
+static l_int32
+boxHasOverlapInXorY(l_int32 c1,
+ l_int32 s1,
+ l_int32 c2,
+ l_int32 s2)
+{
+l_int32 ovlp;
+
+ if (c1 > c2)
+ ovlp = c2 + s2 - 1 - c1;
+ else
+ ovlp = c1 + s1 - 1 - c2;
+ return (ovlp < 0) ? 0 : 1;
+}
+
+
+/*!
+ * \brief boxGetDistanceInXorY()
+ *
+ * \param[in] c1 left or top coordinate of box1
+ * \param[in] s1 width or height of box1
+ * \param[in] c2 left or top coordinate of box2
+ * \param[in] s2 width or height of box2
+ * \return distance between them (if < 0, box2 overlaps box1 in the
+ * dimension considered)
+ */
+static l_int32
+boxGetDistanceInXorY(l_int32 c1,
+ l_int32 s1,
+ l_int32 c2,
+ l_int32 s2)
+{
+l_int32 dist;
+
+ if (c1 > c2)
+ dist = c1 - (c2 + s2 - 1);
+ else
+ dist = c2 - (c1 + s1 - 1);
+ return dist;
+}
+
+
+/*!
+ * \brief boxGetCenter()
+ *
+ * \param[in] box
+ * \param[out] pcx, pcy location of center of box
+ * \return 0 if OK, 1 on error or if box is not valid
+ */
+l_ok
+boxGetCenter(BOX *box,
+ l_float32 *pcx,
+ l_float32 *pcy)
+{
+l_int32 x, y, w, h;
+
+ PROCNAME("boxGetCenter");
+
+ if (pcx) *pcx = 0;
+ if (pcy) *pcy = 0;
+ if (!pcx || !pcy)
+ return ERROR_INT("&cx, &cy not both defined", procName, 1);
+ if (!box)
+ return ERROR_INT("box not defined", procName, 1);
+ boxGetGeometry(box, &x, &y, &w, &h);
+ if (w == 0 || h == 0) return 1;
+ *pcx = (l_float32)(x + 0.5 * w);
+ *pcy = (l_float32)(y + 0.5 * h);
+
+ return 0;
+}
+
+
+/*!
+ * \brief boxIntersectByLine()
+ *
+ * \param[in] box
+ * \param[in] x, y point that line goes through
+ * \param[in] slope of line
+ * \param[out] px1, py1 1st point of intersection with box
+ * \param[out] px2, py2 2nd point of intersection with box
+ * \param[out] pn number of points of intersection
+ * \return 0 if OK, 1 on error or if box is not valid
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) *px1 = 0;
+ if (px2) *px2 = 0;
+ if (py1) *py1 = 0;
+ if (py2) *py2 = 0;
+ if (pn) *pn = 0;
+ if (!px1 || !py1 || !px2 || !py2)
+ return ERROR_INT("&x1, &y1, &x2, &y2 not all defined", procName, 1);
+ if (!pn)
+ return ERROR_INT("&n not defined", procName, 1);
+ if (!box)
+ return ERROR_INT("box not defined", procName, 1);
+ boxGetGeometry(box, &bx, &by, &bw, &bh);
+ if (bw == 0 || bh == 0) return 1;
+
+ 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;
+}
+
+
+/*!
+ * \brief boxClipToRectangle()
+ *
+ * \param[in] box
+ * \param[in] wi, hi rectangle representing image
+ * \return part of box within given rectangle, or NULL on error
+ * or if box is entirely outside the rectangle
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxClipToRectangleParams()
+ *
+ * \param[in] box [optional] requested box; can be null
+ * \param[in] w, h clipping box size; typ. the size of an image
+ * \param[out] pxstart start x coordinate
+ * \param[out] pystart start y coordinate
+ * \param[out] pxend one pixel beyond clipping box
+ * \param[out] pyend one pixel beyond clipping box
+ * \param[out] pbw [optional] clipped width
+ * \param[out] pbh [optional] clipped height
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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++ {
+ * ....
+ * </pre>
+ */
+l_ok
+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) *pxstart = 0;
+ if (pystart) *pystart = 0;
+ if (pxend) *pxend = w;
+ if (pyend) *pyend = h;
+ if (pbw) *pbw = w;
+ if (pbh) *pbh = h;
+ if (!pxstart || !pystart || !pxend || !pyend)
+ return ERROR_INT("invalid ptr input", procName, 1);
+ 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;
+}
+
+
+/*!
+ * \brief boxRelocateOneSide()
+ *
+ * \param[in] boxd [optional]; this can be null, equal to boxs,
+ * or different from boxs;
+ * \param[in] boxs starting box; to have one side relocated
+ * \param[in] loc new location of the side that is changing
+ * \param[in] 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.
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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 (w == 0 || h == 0)
+ return boxd;
+ 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;
+}
+
+
+/*!
+ * \brief boxaAdjustSides()
+ *
+ * \param[in] boxas
+ * \param[in] delleft, delright, deltop, delbot changes in location of
+ * each side for each box
+ * \return boxad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) New box dimensions are cropped at left and top to x >= 0 and y >= 0.
+ * (2) If the width or height of a box goes to 0, we generate a box with
+ * w == 1 and h == 1, as a placeholder.
+ * (3) See boxAdjustSides().
+ * </pre>
+ */
+BOXA *
+boxaAdjustSides(BOXA *boxas,
+ l_int32 delleft,
+ l_int32 delright,
+ l_int32 deltop,
+ l_int32 delbot)
+{
+l_int32 n, i, x, y;
+BOX *box1, *box2;
+BOXA *boxad;
+
+ PROCNAME("boxaAdjustSides");
+
+ if (!boxas)
+ return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+
+ n = boxaGetCount(boxas);
+ boxad = boxaCreate(n);
+ for (i = 0; i < n; i++) {
+ box1 = boxaGetBox(boxas, i, L_COPY);
+ box2 = boxAdjustSides(NULL, box1, delleft, delright, deltop, delbot);
+ if (!box2) {
+ boxGetGeometry(box1, &x, &y, NULL, NULL);
+ box2 = boxCreate(x, y, 1, 1);
+ }
+ boxaAddBox(boxad, box2, L_INSERT);
+ boxDestroy(&box1);
+ }
+
+ return boxad;
+}
+
+
+/*!
+ * \brief boxaAdjustBoxSides()
+ *
+ * \param[in] boxas
+ * \param[in] index
+ * \param[in] delleft, delright, deltop, delbot changes to box side locs
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) In-place operation on a box in a boxa.
+ * (2) New box dimensions are cropped at left and top to x >= 0 and y >= 0.
+ * (3) If a box ends up with no area, an error message is emitted,
+ * but the box dimensions are not changed.
+ * (4) See boxaAdjustSides().
+ * </pre>
+ */
+l_ok
+boxaAdjustBoxSides(BOXA *boxa,
+ l_int32 index,
+ l_int32 delleft,
+ l_int32 delright,
+ l_int32 deltop,
+ l_int32 delbot)
+{
+BOX *box;
+
+ PROCNAME("boxaAdjustBoxSides");
+
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+
+ if ((box = boxaGetBox(boxa, index, L_CLONE)) == NULL)
+ return ERROR_INT("invalid index", procName, 1);
+
+ boxAdjustSides(box, box, delleft, delright, deltop, delbot);
+ boxDestroy(&box); /* the clone */
+ return 0;
+}
+
+
+/*!
+ * \brief boxAdjustSides()
+ *
+ * \param[in] boxd [optional]; this can be null, equal to boxs,
+ * or different from boxs
+ * \param[in] boxs starting box; to have sides adjusted
+ * \param[in] 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.
+ *
+ * <pre>
+ * 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
+ * (3) New box dimensions are cropped at left and top to x >= 0 and y >= 0.
+ * (4) For example, to expand in-place by 20 pixels on each side, use
+ * boxAdjustSides(box, box, -20, 20, -20, 20);
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaSetSide()
+ *
+ * \param[in] boxad use NULL to get a new one; same as boxas for in-place
+ * \param[in] boxas
+ * \param[in] side L_SET_LEFT, L_SET_RIGHT, L_SET_TOP, L_SET_BOT
+ * \param[in] val location to set for given side, for each box
+ * \param[in] thresh min abs difference to cause resetting to %val
+ * \return boxad, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+BOXA *
+boxaSetSide(BOXA *boxad,
+ BOXA *boxas,
+ l_int32 side,
+ l_int32 val,
+ l_int32 thresh)
+{
+l_int32 n, i;
+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);
+ boxSetSide(box, side, val, thresh);
+ boxDestroy(&box); /* the clone */
+ }
+
+ return boxad;
+}
+
+
+/*!
+ * \brief boxSetSide()
+ *
+ * \param[in] boxs
+ * \param[in] side L_SET_LEFT, L_SET_RIGHT, L_SET_TOP, L_SET_BOT
+ * \param[in] val location to set for given side, for each box
+ * \param[in] thresh min abs difference to cause resetting to %val
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) In-place operation.
+ * (2) Use %thresh = 0 to definitely set the side to %val.
+ * </pre>
+ */
+l_ok
+boxSetSide(BOX *boxs,
+ l_int32 side,
+ l_int32 val,
+ l_int32 thresh)
+{
+l_int32 x, y, w, h, diff;
+
+ PROCNAME("boxSetSide");
+
+ if (!boxs)
+ return ERROR_INT("box not defined", procName, 1);
+ if (side != L_SET_LEFT && side != L_SET_RIGHT &&
+ side != L_SET_TOP && side != L_SET_BOT)
+ return ERROR_INT("invalid side", procName, 1);
+ if (val < 0)
+ return ERROR_INT("val < 0", procName, 1);
+
+ boxGetGeometry(boxs, &x, &y, &w, &h);
+ if (side == L_SET_LEFT) {
+ diff = x - val;
+ if (L_ABS(diff) >= thresh)
+ boxSetGeometry(boxs, val, y, w + diff, h);
+ } else if (side == L_SET_RIGHT) {
+ diff = x + w -1 - val;
+ if (L_ABS(diff) >= thresh)
+ boxSetGeometry(boxs, x, y, val - x + 1, h);
+ } else if (side == L_SET_TOP) {
+ diff = y - val;
+ if (L_ABS(diff) >= thresh)
+ boxSetGeometry(boxs, x, val, w, h + diff);
+ } else { /* side == L_SET_BOT */
+ diff = y + h - 1 - val;
+ if (L_ABS(diff) >= thresh)
+ boxSetGeometry(boxs, x, y, w, val - y + 1);
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief boxaAdjustWidthToTarget()
+ *
+ * \param[in] boxad use NULL to get a new one; same as boxas for in-place
+ * \param[in] boxas
+ * \param[in] sides L_ADJUST_LEFT, L_ADJUST_RIGHT, L_ADJUST_LEFT_AND_RIGHT
+ * \param[in] target target width if differs by more than thresh
+ * \param[in] thresh min abs difference in width to cause adjustment
+ * \return boxad, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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++) {
+ if ((box = boxaGetValidBox(boxad, i, L_CLONE)) == NULL)
+ continue;
+ 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;
+}
+
+
+/*!
+ * \brief boxaAdjustHeightToTarget()
+ *
+ * \param[in] boxad use NULL to get a new one
+ * \param[in] boxas
+ * \param[in] sides L_ADJUST_TOP, L_ADJUST_BOT, L_ADJUST_TOP_AND_BOT
+ * \param[in] target target height if differs by more than thresh
+ * \param[in] thresh min abs difference in height to cause adjustment
+ * \return boxad, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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++) {
+ if ((box = boxaGetValidBox(boxad, i, L_CLONE)) == NULL)
+ continue;
+ boxGetGeometry(box, &x, &y, &w, &h);
+ 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;
+}
+
+
+/*!
+ * \brief boxEqual()
+ *
+ * \param[in] box1
+ * \param[in] box2
+ * \param[out] psame 1 if equal; 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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("boxes 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;
+}
+
+
+/*!
+ * \brief boxaEqual()
+ *
+ * \param[in] boxa1
+ * \param[in] boxa2
+ * \param[in] maxdist
+ * \param[out] pnaindex [optional] index array of correspondences
+ * \param[out] psame 1 if equal; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+
+ if ((countarray = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32))) == NULL)
+ return ERROR_INT("calloc fail for countarray", procName, 1);
+ 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;
+}
+
+
+/*!
+ * \brief boxSimilar()
+ *
+ * \param[in] box1
+ * \param[in] box2
+ * \param[in] leftdiff, rightdiff, topdiff, botdiff
+ * \param[out] psimilar 1 if similar; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+boxSimilar(BOX *box1,
+ BOX *box2,
+ l_int32 leftdiff,
+ l_int32 rightdiff,
+ l_int32 topdiff,
+ l_int32 botdiff,
+ l_int32 *psimilar)
+{
+l_int32 l1, l2, r1, r2, t1, t2, b1, b2, valid1, valid2;
+
+ PROCNAME("boxSimilar");
+
+ if (!psimilar)
+ return ERROR_INT("&similar not defined", procName, 1);
+ *psimilar = 0;
+ if (!box1 || !box2)
+ return ERROR_INT("boxes not both defined", procName, 1);
+ boxIsValid(box1, &valid1);
+ boxIsValid(box2, &valid2);
+ if (!valid1 || !valid2)
+ return ERROR_INT("boxes not both valid", procName, 1);
+
+ boxGetSideLocations(box1, &l1, &r1, &t1, &b1);
+ boxGetSideLocations(box2, &l2, &r2, &t2, &b2);
+ if (L_ABS(l1 - l2) > leftdiff)
+ return 0;
+ if (L_ABS(r1 - r2) > rightdiff)
+ return 0;
+ if (L_ABS(t1 - t2) > topdiff)
+ return 0;
+ if (L_ABS(b1 - b2) > botdiff)
+ return 0;
+
+ *psimilar = 1;
+ return 0;
+}
+
+
+/*!
+ * \brief boxaSimilar()
+ *
+ * \param[in] boxa1
+ * \param[in] boxa2
+ * \param[in] leftdiff, rightdiff, topdiff, botdiff
+ * \param[in] debug output details of non-similar boxes
+ * \param[out] psimilar 1 if similar; 0 otherwise
+ * \param[out] pnasim [optional] na containing 1 if similar; else 0
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief boxaJoin()
+ *
+ * \param[in] boxad dest boxa; add to this one
+ * \param[in] boxas source boxa; add from this one
+ * \param[in] istart starting index in boxas
+ * \param[in] iend ending index in boxas; use -1 to cat all
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaaJoin()
+ *
+ * \param[in] baad dest boxaa; add to this one
+ * \param[in] baas source boxaa; add from this one
+ * \param[in] istart starting index in baas
+ * \param[in] iend ending index in baas; use -1 to cat all
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaSplitEvenOdd()
+ *
+ * \param[in] boxa
+ * \param[in] fillflag 1 to put invalid boxes in place; 0 to omit
+ * \param[out] pboxae, pboxao save even and odd boxes in their separate
+ * boxa, setting the other type to invalid boxes.
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+boxaSplitEvenOdd(BOXA *boxa,
+ l_int32 fillflag,
+ BOXA **pboxae,
+ BOXA **pboxao)
+{
+l_int32 i, n;
+BOX *box, *box1;
+
+ PROCNAME("boxaSplitEvenOdd");
+
+ if (pboxae) *pboxae = NULL;
+ if (pboxao) *pboxao = NULL;
+ if (!pboxae || !pboxao)
+ return ERROR_INT("&boxae and &boxao not both defined", procName, 1);
+ 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);
+ box1 = boxCreate(0, 0, 0, 0); /* empty placeholder */
+ if ((i & 1) == 0) {
+ boxaAddBox(*pboxae, box, L_INSERT);
+ boxaAddBox(*pboxao, box1, L_INSERT);
+ } else {
+ boxaAddBox(*pboxae, box1, L_INSERT);
+ boxaAddBox(*pboxao, box, L_INSERT);
+ }
+ }
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief boxaMergeEvenOdd()
+ *
+ * \param[in] boxae boxes to go in even positions in merged boxa
+ * \param[in] boxao boxes to go in odd positions in merged boxa
+ * \param[in] fillflag 1 if there are invalid boxes in placeholders
+ * \return boxad merged, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/boxfunc2.c b/leptonica/src/boxfunc2.c
new file mode 100644
index 00000000..98f2808a
--- /dev/null
+++ b/leptonica/src/boxfunc2.c
@@ -0,0 +1,1933 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file boxfunc2.c
+ * <pre>
+ *
+ * Boxa/Box transform (shift, scale) and orthogonal rotation
+ * BOXA *boxaTransform()
+ * BOX *boxTransform()
+ * BOXA *boxaTransformOrdered()
+ * BOX *boxTransformOrdered()
+ * BOXA *boxaRotateOrth()
+ * BOX *boxRotateOrth()
+ * BOXA *boxaShiftWithPta()
+ *
+ * Boxa sort
+ * BOXA *boxaSort()
+ * BOXA *boxaBinSort()
+ * BOXA *boxaSortByIndex()
+ * BOXAA *boxaSort2d()
+ * BOXAA *boxaSort2dByIndex()
+ *
+ * Boxa statistics
+ * l_int32 boxaGetRankVals()
+ * l_int32 boxaGetMedianVals()
+ * l_int32 boxaGetAverageSize()
+ *
+ * Boxa array extraction
+ * l_int32 boxaExtractAsNuma()
+ * l_int32 boxaExtractAsPta()
+ * PTA *boxaExtractCorners()
+ *
+ * Other Boxaa functions
+ * l_int32 boxaaGetExtent()
+ * BOXA *boxaaFlattenToBoxa()
+ * BOXA *boxaaFlattenAligned()
+ * BOXAA *boxaEncapsulateAligned()
+ * BOXAA *boxaaTranspose()
+ * l_int32 boxaaAlignBox()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 MinCompsForBinSort = 200;
+
+/*---------------------------------------------------------------------*
+ * Boxa/Box transform (shift, scale) and orthogonal rotation *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaTransform()
+ *
+ * \param[in] boxas
+ * \param[in] shiftx
+ * \param[in] shifty
+ * \param[in] scalex
+ * \param[in] scaley
+ * \return boxad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a very simple function that first shifts, then scales.
+ * (2) The UL corner coordinates of all boxes in the output %boxad
+ * (3) For the boxes in the output %boxad, the UL corner coordinates
+ * must be non-negative, and the width and height of valid
+ * boxes must be at least 1.
+ * </pre>
+ */
+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) {
+ boxaDestroy(&boxad);
+ 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;
+}
+
+
+/*!
+ * \brief boxTransform()
+ *
+ * \param[in] box
+ * \param[in] shiftx
+ * \param[in] shifty
+ * \param[in] scalex
+ * \param[in] scaley
+ * \return boxd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * (3) The UL corner coordinates must be non-negative, and the
+ * width and height of valid boxes must be at least 1.
+ * </pre>
+ */
+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)(L_MAX(0, scalex * (box->x + shiftx) + 0.5)),
+ (l_int32)(L_MAX(0, 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)));
+}
+
+
+/*!
+ * \brief boxaTransformOrdered()
+ *
+ * \param[in] boxas
+ * \param[in] shiftx
+ * \param[in] shifty
+ * \param[in] scalex
+ * \param[in] scaley
+ * \param[in] xcen, ycen center of rotation
+ * \param[in] angle in radians; clockwise is positive
+ * \param[in] order one of 6 combinations: L_TR_SC_RO, ...
+ * \return boxd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ boxaDestroy(&boxad);
+ 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;
+}
+
+
+/*!
+ * \brief boxTransformOrdered()
+ *
+ * \param[in] boxs
+ * \param[in] shiftx
+ * \param[in] shifty
+ * \param[in] scalex
+ * \param[in] scaley
+ * \param[in] xcen, ycen center of rotation
+ * \param[in] angle in radians; clockwise is positive
+ * \param[in] order one of 6 combinations: L_TR_SC_RO, ...
+ * \return boxd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaRotateOrth()
+ *
+ * \param[in] boxas
+ * \param[in] w, h of image in which the boxa is embedded
+ * \param[in] rotation 0 = noop, 1 = 90 deg, 2 = 180 deg, 3 = 270 deg;
+ * all rotations are clockwise
+ * \return boxad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See boxRotateOrth() for details.
+ * </pre>
+ */
+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) {
+ boxaDestroy(&boxad);
+ return (BOXA *)ERROR_PTR("boxs not found", procName, NULL);
+ }
+ boxd = boxRotateOrth(boxs, w, h, rotation);
+ boxDestroy(&boxs);
+ boxaAddBox(boxad, boxd, L_INSERT);
+ }
+
+ return boxad;
+}
+
+
+/*!
+ * \brief boxRotateOrth()
+ *
+ * \param[in] box
+ * \param[in] w, h of image in which the box is embedded
+ * \param[in] rotation 0 = noop, 1 = 90 deg, 2 = 180 deg, 3 = 270 deg;
+ * all rotations are clockwise
+ * \return boxd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief boxaShiftWithPta()
+ *
+ * \param[in] boxas
+ * \param[in] pta aligned with the boxes; determines shift amount
+ * \param[in] dir +1 to shift by the values in pta; -1 to shift
+ * by the negative of the values in the pta.
+ * \return boxad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) In use, %pta may come from the UL corners of of a boxa, each
+ * of whose boxes contains the corresponding box of %boxas
+ * within it. The output %boxad is then a boxa in the (global)
+ * coordinates of the containing boxa. So the input %pta
+ * could come from boxaExtractCorners().
+ * (2) The operations with %dir == 1 and %dir == -1 are inverses if
+ * called in order (1, -1). Starting with an input boxa and
+ * calling twice with these values of %dir results in a boxa
+ * identical to the input. However, because box parameters can
+ * never be negative, calling in the order (-1, 1) may result
+ * in clipping at the left side and the top.
+ * </pre>
+ */
+BOXA *
+boxaShiftWithPta(BOXA *boxas,
+ PTA *pta,
+ l_int32 dir)
+{
+l_int32 i, n, x, y, full;
+BOX *box1, *box2;
+BOXA *boxad;
+
+ PROCNAME("boxaShiftWithPta");
+
+ if (!boxas)
+ return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+ boxaIsFull(boxas, &full);
+ if (!full)
+ return (BOXA *)ERROR_PTR("boxas not full", procName, NULL);
+ if (!pta)
+ return (BOXA *)ERROR_PTR("pta not defined", procName, NULL);
+ if (dir != 1 && dir != -1)
+ return (BOXA *)ERROR_PTR("invalid dir", procName, NULL);
+ n = boxaGetCount(boxas);
+ if (n != ptaGetCount(pta))
+ return (BOXA *)ERROR_PTR("boxas and pta not same size", procName, NULL);
+
+ if ((boxad = boxaCreate(n)) == NULL)
+ return (BOXA *)ERROR_PTR("boxad not made", procName, NULL);
+ for (i = 0; i < n; i++) {
+ box1 = boxaGetBox(boxas, i, L_COPY);
+ ptaGetIPt(pta, i, &x, &y);
+ box2 = boxTransform(box1, dir * x, dir * y, 1.0, 1.0);
+ boxaAddBox(boxad, box2, L_INSERT);
+ boxDestroy(&box1);
+ }
+ return boxad;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Boxa sort *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaSort()
+ *
+ * \param[in] boxas
+ * \param[in] 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
+ * \param[in] sortorder L_SORT_INCREASING, L_SORT_DECREASING
+ * \param[out] pnaindex [optional] index of sorted order into
+ * original array
+ * \return boxad sorted version of boxas, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) An empty boxa returns a copy, with a warning.
+ * </pre>
+ */
+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 ((n = boxaGetCount(boxas)) == 0) {
+ L_WARNING("boxas is empty\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ 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 */
+ if (n > MinCompsForBinSort &&
+ ((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 */
+ naindex = numaGetSortIndex(na, sortorder);
+ numaDestroy(&na);
+ if (!naindex)
+ 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);
+ return boxad;
+}
+
+
+/*!
+ * \brief boxaBinSort()
+ *
+ * \param[in] boxas
+ * \param[in] sorttype L_SORT_BY_X, L_SORT_BY_Y, L_SORT_BY_WIDTH,
+ * L_SORT_BY_HEIGHT, L_SORT_BY_PERIMETER
+ * \param[in] sortorder L_SORT_INCREASING, L_SORT_DECREASING
+ * \param[out] pnaindex [optional] index of sorted order into
+ * original array
+ * \return boxad sorted version of boxas, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 ((n = boxaGetCount(boxas)) == 0) {
+ L_WARNING("boxas is empty\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ 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 */
+ 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 */
+ naindex = numaGetBinSortIndex(na, sortorder);
+ numaDestroy(&na);
+ if (!naindex)
+ 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);
+ return boxad;
+}
+
+
+/*!
+ * \brief boxaSortByIndex()
+ *
+ * \param[in] boxas
+ * \param[in] 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 ((n = boxaGetCount(boxas)) == 0) {
+ L_WARNING("boxas is empty\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ if (!naindex)
+ return (BOXA *)ERROR_PTR("naindex not defined", procName, NULL);
+
+ 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;
+}
+
+
+/*!
+ * \brief boxaSort2d()
+ *
+ * \param[in] boxas
+ * \param[out] pnaad [optional] numaa with sorted indices
+ * whose values are the indices of the input array
+ * \param[in] delta1 min separation that permits aggregation of a box
+ * onto a boxa of horizontally-aligned boxes; pass 1
+ * \param[in] delta2 min separation that permits aggregation of a box
+ * onto a boxa of horizontally-aligned boxes; pass 2
+ * \param[in] 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
+ *
+ * <pre>
+ * 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. In fact,
+ * %delta1 is the max distance by which they can miss and still
+ * be aggregated. If %delta1 < 0, the box must have vertical
+ * overlap of at least abs(%delta1) with the boxa before it
+ * can be merged. 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.
+ * (7) Typical values for the input parameters on 300 ppi text are:
+ * delta1 ~ 0
+ * delta2 ~ 0
+ * minh1 ~ 5
+ * </pre>
+ */
+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);
+ if (boxaGetCount(boxas) == 0)
+ return (BOXAA *)ERROR_PTR("boxas is empty", 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);
+ }
+
+
+/* lept_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;
+}
+
+
+/*!
+ * \brief boxaSort2dByIndex()
+ *
+ * \param[in] boxas
+ * \param[in] 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 ((boxtot = boxaGetCount(boxas)) == 0)
+ return (BOXAA *)ERROR_PTR("boxas is empty", procName, NULL);
+ if (!naa)
+ return (BOXAA *)ERROR_PTR("naindex not defined", procName, NULL);
+
+ /* Check counts */
+ ntot = numaaGetNumberCount(naa);
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaExtractAsNuma()
+ *
+ * \param[in] boxa
+ * \param[out] pnal [optional] array of left locations
+ * \param[out] pnat [optional] array of top locations
+ * \param[out] pnar [optional] array of right locations
+ * \param[out] pnab [optional] array of bottom locations
+ * \param[out] pnaw [optional] array of widths
+ * \param[out] pnah [optional] array of heights
+ * \param[in] keepinvalid 1 to keep invalid boxes; 0 to remove them
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaExtractAsPta()
+ *
+ * \param[in] boxa
+ * \param[out] pptal [optional] array of left locations vs. index
+ * \param[out] pptat [optional] array of top locations vs. index
+ * \param[out] pptar [optional] array of right locations vs. index
+ * \param[out] pptab [optional] array of bottom locations vs. index
+ * \param[out] pptaw [optional] array of widths vs. index
+ * \param[out] pptah [optional] array of heights vs. index
+ * \param[in] keepinvalid 1 to keep invalid boxes; 0 to remove them
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * (3) Other boxa --> pta functions are:
+ * * boxaExtractCorners(): extracts any of the four corners as a pta.
+ * * boxaConvertToPta(): extracts sufficient number of corners
+ * to allow reconstruction of the original boxa from the pta.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaExtractCorners()
+ *
+ * \param[in] boxa
+ * \param[in] loc L_UPPER_LEFT, L_UPPER_RIGHT, L_LOWER_LEFT,
+ * L_LOWER_RIGHT, L_BOX_CENTER
+ * \return pta of requested coordinates, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Extracts (0,0) for invalid boxes.
+ * (2) Other boxa --> pta functions are:
+ * * boxaExtractAsPta(): allows extraction of any dimension
+ * and/or side location, with each in a separate pta.
+ * * boxaConvertToPta(): extracts sufficient number of corners
+ * to allow reconstruction of the original boxa from the pta.
+ * </pre>
+ */
+PTA *
+boxaExtractCorners(BOXA *boxa,
+ l_int32 loc)
+{
+l_int32 i, n, left, top, right, bot, w, h;
+PTA *pta;
+
+ PROCNAME("boxaExtractCorners");
+
+ if (!boxa)
+ return (PTA *)ERROR_PTR("boxa not defined", procName, NULL);
+ if (loc != L_UPPER_LEFT && loc != L_UPPER_RIGHT && loc != L_LOWER_LEFT &&
+ loc != L_LOWER_RIGHT && loc != L_BOX_CENTER)
+ return (PTA *)ERROR_PTR("invalid location", 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++) {
+ boxaGetBoxGeometry(boxa, i, &left, &top, &w, &h);
+ right = left + w - 1;
+ bot = top + h - 1;
+ if (w == 0 || h == 0) { /* invalid */
+ left = 0;
+ top = 0;
+ right = 0;
+ bot = 0;
+ }
+ if (loc == L_UPPER_LEFT)
+ ptaAddPt(pta, left, top);
+ else if (loc == L_UPPER_RIGHT)
+ ptaAddPt(pta, right, top);
+ else if (loc == L_LOWER_LEFT)
+ ptaAddPt(pta, left, bot);
+ else if (loc == L_LOWER_RIGHT)
+ ptaAddPt(pta, right, bot);
+ else if (loc == L_BOX_CENTER)
+ ptaAddPt(pta, (left + right) / 2, (top + bot) / 2);
+ }
+
+ return pta;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Boxa statistics *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaGetRankVals()
+ *
+ * \param[in] boxa
+ * \param[in] fract use 0.0 for smallest, 1.0 for largest width and height
+ * \param[out] px [optional] rank value of x (left side)
+ * \param[out] py [optional] rank value of y (top side)
+ * \param[out] pr [optional] rank value of right side
+ * \param[out] pb [optional] rank value of bottom side
+ * \param[out] pw [optional] rank value of width
+ * \param[out] ph [optional] rank value of height
+ * \return 0 if OK, 1 on error or if the boxa is empty or has no valid boxes
+ *
+ * <pre>
+ * Notes:
+ * (1) This function does not assume that all boxes in the boxa are valid
+ * (2) The six 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. (By the same argument, we choose to sort
+ * the r and b sides in increasing order.) In general, the
+ * interpretation of a rank order on x and y (or on r and b)
+ * is highly application dependent. In summary:
+ * ~ x and y are sorted in decreasing order
+ * ~ r and b are sorted in increasing order
+ * ~ w and h are sorted in increasing order
+ * </pre>
+ */
+l_ok
+boxaGetRankVals(BOXA *boxa,
+ l_float32 fract,
+ l_int32 *px,
+ l_int32 *py,
+ l_int32 *pr,
+ l_int32 *pb,
+ l_int32 *pw,
+ l_int32 *ph)
+{
+l_float32 xval, yval, rval, bval, wval, hval;
+NUMA *nax, *nay, *nar, *nab, *naw, *nah;
+
+ PROCNAME("boxaGetRankVals");
+
+ if (px) *px = 0;
+ if (py) *py = 0;
+ if (pr) *pr = 0;
+ if (pb) *pb = 0;
+ if (pw) *pw = 0;
+ if (ph) *ph = 0;
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+ if (fract < 0.0 || fract > 1.0)
+ return ERROR_INT("fract not in [0.0 ... 1.0]", procName, 1);
+ if (boxaGetValidCount(boxa) == 0)
+ return ERROR_INT("no valid boxes in boxa", procName, 1);
+
+ /* Use only the valid boxes */
+ boxaExtractAsNuma(boxa, &nax, &nay, &nar, &nab, &naw, &nah, 0);
+
+ if (px) {
+ numaGetRankValue(nax, 1.0 - fract, NULL, 1, &xval);
+ *px = (l_int32)xval;
+ }
+ if (py) {
+ numaGetRankValue(nay, 1.0 - fract, NULL, 1, &yval);
+ *py = (l_int32)yval;
+ }
+ if (pr) {
+ numaGetRankValue(nar, fract, NULL, 1, &rval);
+ *pr = (l_int32)rval;
+ }
+ if (pb) {
+ numaGetRankValue(nab, fract, NULL, 1, &bval);
+ *pb = (l_int32)bval;
+ }
+ if (pw) {
+ numaGetRankValue(naw, fract, NULL, 1, &wval);
+ *pw = (l_int32)wval;
+ }
+ if (ph) {
+ numaGetRankValue(nah, fract, NULL, 1, &hval);
+ *ph = (l_int32)hval;
+ }
+ numaDestroy(&nax);
+ numaDestroy(&nay);
+ numaDestroy(&nar);
+ numaDestroy(&nab);
+ numaDestroy(&naw);
+ numaDestroy(&nah);
+ return 0;
+}
+
+
+/*!
+ * \brief boxaGetMedianVals()
+ *
+ * \param[in] boxa
+ * \param[out] px [optional] median value of x (left side)
+ * \param[out] py [optional] median value of y (top side)
+ * \param[out] pr [optional] median value of right side
+ * \param[out] pb [optional] median value of bottom side
+ * \param[out] pw [optional] median value of width
+ * \param[out] ph [optional] median value of height
+ * \return 0 if OK, 1 on error or if the boxa is empty or has no valid boxes
+ *
+ * <pre>
+ * Notes:
+ * (1) See boxaGetRankVals()
+ * </pre>
+ */
+l_ok
+boxaGetMedianVals(BOXA *boxa,
+ l_int32 *px,
+ l_int32 *py,
+ l_int32 *pr,
+ l_int32 *pb,
+ l_int32 *pw,
+ l_int32 *ph)
+{
+ PROCNAME("boxaGetMedianVals");
+
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+ if (boxaGetValidCount(boxa) == 0)
+ return ERROR_INT("no valid boxes in boxa", procName, 1);
+
+ return boxaGetRankVals(boxa, 0.5, px, py, pr, pb, pw, ph);
+}
+
+
+/*!
+ * \brief boxaGetAverageSize()
+ *
+ * \param[in] boxa
+ * \param[out] pw [optional] average width
+ * \param[out] ph [optional] average height
+ * \return 0 if OK, 1 on error or if the boxa is empty
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaaGetExtent()
+ *
+ * \param[in] baa
+ * \param[out] pw [optional] width
+ * \param[out] ph [optional] height
+ * \param[out] pbox [optional] minimum box containing all boxa
+ * in boxaa
+ * \param[out] pboxa [optional] boxa containing all boxes in each
+ * boxa in the boxaa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaaFlattenToBoxa()
+ *
+ * \param[in] baa
+ * \param[out] pnaindex [optional] the boxa index in the baa
+ * \param[in] copyflag L_COPY or L_CLONE
+ * \return boxa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaaFlattenAligned()
+ *
+ * \param[in] baa
+ * \param[in] num number extracted from each
+ * \param[in] fillerbox [optional] that fills if necessary
+ * \param[in] copyflag L_COPY or L_CLONE
+ * \return boxa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaEncapsulateAligned()
+ *
+ * \param[in] boxa
+ * \param[in] num number put into each boxa in the baa
+ * \param[in] copyflag L_COPY or L_CLONE
+ * \return baa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaaTranspose()
+ *
+ * \param[in] baas
+ * \return baad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If you think of a boxaa as a 2D array of boxes that is accessed
+ * row major, then each row is represented by one of the boxa.
+ * This function creates a new boxaa related to the input boxaa
+ * as a column major traversal of the input boxaa.
+ * (2) For example, if %baas has 2 boxa, each with 10 boxes, then
+ * %baad will have 10 boxa, each with 2 boxes.
+ * (3) Require for this transpose operation that each boxa in
+ * %baas has the same number of boxes. This operation is useful
+ * when the i-th boxes in each boxa are meaningfully related.
+ * </pre>
+ */
+BOXAA *
+boxaaTranspose(BOXAA *baas)
+{
+l_int32 i, j, ny, nb, nbox;
+BOX *box;
+BOXA *boxa;
+BOXAA *baad;
+
+ PROCNAME("boxaaTranspose");
+
+ if (!baas)
+ return (BOXAA *)ERROR_PTR("baas not defined", procName, NULL);
+ if ((ny = boxaaGetCount(baas)) == 0)
+ return (BOXAA *)ERROR_PTR("baas empty", procName, NULL);
+
+ /* Make sure that each boxa in baas has the same number of boxes */
+ for (i = 0; i < ny; i++) {
+ if ((boxa = boxaaGetBoxa(baas, i, L_CLONE)) == NULL)
+ return (BOXAA *)ERROR_PTR("baas is missing a boxa", procName, NULL);
+ nb = boxaGetCount(boxa);
+ boxaDestroy(&boxa);
+ if (i == 0)
+ nbox = nb;
+ else if (nb != nbox)
+ return (BOXAA *)ERROR_PTR("boxa are not all the same size",
+ procName, NULL);
+ }
+
+ /* baad[i][j] = baas[j][i] */
+ baad = boxaaCreate(nbox);
+ for (i = 0; i < nbox; i++) {
+ boxa = boxaCreate(ny);
+ for (j = 0; j < ny; j++) {
+ box = boxaaGetBox(baas, j, i, L_COPY);
+ boxaAddBox(boxa, box, L_INSERT);
+ }
+ boxaaAddBoxa(baad, boxa, L_INSERT);
+ }
+ return baad;
+}
+
+
+/*!
+ * \brief boxaaAlignBox()
+ *
+ * \param[in] baa
+ * \param[in] box to be aligned with bext boxa in the baa, if possible
+ * \param[in] delta amount by which consecutive components can miss
+ * in overlap and still be included in the array
+ * \param[out] pindex 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
+ *
+ * <pre>
+ * Notes:
+ * (1) This is not greedy. It finds the boxa whose vertical
+ * extent has the closest overlap with the input box.
+ * </pre>
+ */
+l_ok
+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 (pindex) *pindex = 0;
+ 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) {
+ boxaDestroy(&boxa);
+ 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/leptonica/src/boxfunc3.c b/leptonica/src/boxfunc3.c
new file mode 100644
index 00000000..f0da183f
--- /dev/null
+++ b/leptonica/src/boxfunc3.c
@@ -0,0 +1,1629 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file boxfunc3.c
+ * <pre>
+ *
+ * Boxa/Boxaa painting into pix
+ * PIX *pixMaskConnComp()
+ * PIX *pixMaskBoxa()
+ * PIX *pixPaintBoxa()
+ * PIX *pixSetBlackOrWhiteBoxa()
+ * PIX *pixPaintBoxaRandom()
+ * PIX *pixBlendBoxaRandom()
+ * PIX *pixDrawBoxa()
+ * PIX *pixDrawBoxaRandom()
+ * PIX *boxaaDisplay()
+ * PIXA *pixaDisplayBoxaa()
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixMaskConnComp()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] connectivity 4 or 8
+ * \param[out] pboxa [optional] bounding boxes of c.c.
+ * \return pixd 1 bpp mask over the c.c., or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixMaskConnComp(PIX *pixs,
+ l_int32 connectivity,
+ BOXA **pboxa)
+{
+BOXA *boxa;
+PIX *pixd;
+
+ PROCNAME("pixMaskConnComp");
+
+ if (pboxa) *pboxa = NULL;
+ 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;
+}
+
+
+/*!
+ * \brief pixMaskBoxa()
+ *
+ * \param[in] pixd [optional] may be NULL
+ * \param[in] pixs any depth; not cmapped
+ * \param[in] boxa of boxes, to paint
+ * \param[in] op L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return pixd with masking op over the boxes, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixPaintBoxa()
+ *
+ * \param[in] pixs any depth, can be cmapped
+ * \param[in] boxa of boxes, to paint
+ * \param[in] val rgba color to paint
+ * \return pixd with painted boxes, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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)) {
+ pixDestroy(&pixd);
+ 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;
+}
+
+
+/*!
+ * \brief pixSetBlackOrWhiteBoxa()
+ *
+ * \param[in] pixs any depth, can be cmapped
+ * \param[in] boxa [optional] of boxes, to clear or set
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixPaintBoxaRandom()
+ *
+ * \param[in] pixs any depth, can be cmapped
+ * \param[in] boxa of boxes, to paint
+ * \return pixd with painted boxes, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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); /* either 8 or 32 */
+ 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;
+}
+
+
+/*!
+ * \brief pixBlendBoxaRandom()
+ *
+ * \param[in] pixs any depth; can be cmapped
+ * \param[in] boxa of boxes, to blend/paint
+ * \param[in] fract of box color to use
+ * \return pixd 32 bpp, with blend/painted boxes, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixDrawBoxa()
+ *
+ * \param[in] pixs any depth; can be cmapped
+ * \param[in] boxa of boxes, to draw
+ * \param[in] width of lines
+ * \param[in] val rgba color to draw
+ * \return pixd with outlines of boxes added, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixDrawBoxaRandom()
+ *
+ * \param[in] pixs any depth, can be cmapped
+ * \param[in] boxa of boxes, to draw
+ * \param[in] width thickness of line
+ * \return pixd with box outlines drawn, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaaDisplay()
+ *
+ * \param[in] pixs [optional] 1 bpp
+ * \param[in] baa boxaa, typically from a 2d sort
+ * \param[in] linewba line width to display outline of each boxa
+ * \param[in] linewb line width to display outline of each box
+ * \param[in] colorba color to display boxa
+ * \param[in] colorb color to display box
+ * \param[in] w width of outupt pix; use 0 if determined by %pixs or %baa
+ * \param[in] h height of outupt pix; use 0 if determined by %pixs or %baa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %pixs exists, this renders the boxes over an 8 bpp version
+ * of it. Otherwise, it renders the boxes over an empty image
+ * with a white background.
+ * (2) If %pixs exists, the dimensions of %pixd are the same,
+ * and input values of %w and %h are ignored.
+ * If %pixs is NULL, the dimensions of %pixd are determined by
+ * - %w and %h if both are > 0, or
+ * - the minimum size required using all boxes in %baa.
+ *
+ * </pre>
+ */
+PIX *
+boxaaDisplay(PIX *pixs,
+ 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 *pixd;
+PIXCMAP *cmap;
+
+ PROCNAME("boxaaDisplay");
+
+ if (!baa)
+ return (PIX *)ERROR_PTR("baa not defined", procName, NULL);
+
+ if (w <= 0 || h <= 0) {
+ if (pixs)
+ pixGetDimensions(pixs, &w, &h, NULL);
+ else
+ boxaaGetExtent(baa, &w, &h, NULL, NULL);
+ }
+
+ if (pixs) {
+ pixd = pixConvertTo8(pixs, 1);
+ cmap = pixGetColormap(pixd);
+ } else {
+ pixd = pixCreate(w, h, 8);
+ cmap = pixcmapCreate(8);
+ pixSetColormap(pixd, cmap);
+ pixcmapAddColor(cmap, 255, 255, 255);
+ }
+ extractRGBValues(colorb, &rbox, &gbox, &bbox);
+ extractRGBValues(colorba, &rboxa, &gboxa, &bboxa);
+ 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(pixd, box, linewba, rboxa, gboxa, bboxa);
+ boxDestroy(&box);
+ m = boxaGetCount(boxa);
+ for (j = 0; j < m; j++) {
+ box = boxaGetBox(boxa, j, L_CLONE);
+ pixRenderBoxArb(pixd, box, linewb, rbox, gbox, bbox);
+ boxDestroy(&box);
+ }
+ boxaDestroy(&boxa);
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixaDisplayBoxaa()
+ *
+ * \param[in] pixas any depth, can be cmapped
+ * \param[in] baa boxes to draw on input pixa
+ * \param[in] colorflag L_DRAW_RED, L_DRAW_GREEN, etc
+ * \param[in] width thickness of lines
+ * \return pixa with box outlines drawn on each pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) All pix in %pixas that are not rgb are converted to rgb.
+ * (2) Each boxa in %baa contains boxes that will be drawn on
+ * the corresponding pix in %pixas.
+ * (3) The color of the boxes drawn on each pix are selected with
+ * %colorflag:
+ * * For red, green or blue: use L_DRAW_RED, etc.
+ * * For sequential r, g, b: use L_DRAW_RGB
+ * * For random colors: use L_DRAW_RANDOM
+ * </pre>
+ */
+PIXA *
+pixaDisplayBoxaa(PIXA *pixas,
+ BOXAA *baa,
+ l_int32 colorflag,
+ l_int32 width)
+{
+l_int32 i, j, nba, n, nbox, rval, gval, bval;
+l_uint32 color;
+l_uint32 colors[255];
+BOXA *boxa;
+BOX *box;
+PIX *pix;
+PIXA *pixad;
+
+ PROCNAME("pixaDisplayBoxaa");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (!baa)
+ return (PIXA *)ERROR_PTR("baa not defined", procName, NULL);
+ if (width < 1)
+ return (PIXA *)ERROR_PTR("width must be >= 1", procName, NULL);
+ if ((nba = boxaaGetCount(baa)) < 1)
+ return (PIXA *)ERROR_PTR("no boxa in baa", procName, NULL);
+ if ((n = pixaGetCount(pixas)) == 0)
+ return (PIXA *)ERROR_PTR("no pix in pixas", procName, NULL);
+ if (n != nba)
+ return (PIXA *)ERROR_PTR("num pix != num boxa", procName, NULL);
+ if (colorflag == L_DRAW_RED)
+ color = 0xff000000;
+ else if (colorflag == L_DRAW_GREEN)
+ color = 0x00ff0000;
+ else if (colorflag == L_DRAW_BLUE)
+ color = 0x0000ff00;
+ else if (colorflag == L_DRAW_RGB)
+ color = 0x000000ff;
+ else if (colorflag == L_DRAW_RANDOM)
+ color = 0x00000000;
+ else
+ return (PIXA *)ERROR_PTR("invalid colorflag", procName, NULL);
+
+ if (colorflag == L_DRAW_RED || colorflag == L_DRAW_GREEN ||
+ colorflag == L_DRAW_BLUE) {
+ for (i = 0; i < 255; i++)
+ colors[i] = color;
+ } else if (colorflag == L_DRAW_RGB) {
+ for (i = 0; i < 255; i++) {
+ if (i % 3 == L_DRAW_RED)
+ colors[i] = 0xff000000;
+ else if (i % 3 == L_DRAW_GREEN)
+ colors[i] = 0x00ff0000;
+ else /* i % 3 == L_DRAW_BLUE) */
+ colors[i] = 0x0000ff00;
+ }
+ } else if (colorflag == L_DRAW_RANDOM) {
+ for (i = 0; i < 255; i++) {
+ rval = (l_uint32)rand() & 0xff;
+ gval = (l_uint32)rand() & 0xff;
+ bval = (l_uint32)rand() & 0xff;
+ composeRGBPixel(rval, gval, bval, &colors[i]);
+ }
+ }
+
+ pixad = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixas, i, L_COPY);
+ boxa = boxaaGetBoxa(baa, i, L_CLONE);
+ nbox = boxaGetCount(boxa);
+ for (j = 0; j < nbox; j++) {
+ box = boxaGetBox(boxa, j, L_CLONE);
+ extractRGBValues(colors[j % 255], &rval, &gval, &bval);
+ pixRenderBoxArb(pix, box, width, rval, gval, bval);
+ boxDestroy(&box);
+ }
+ boxaDestroy(&boxa);
+ pixaAddPix(pixad, pix, L_INSERT);
+ }
+
+ return pixad;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Split mask components into Boxa *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixSplitIntoBoxa()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] minsum minimum pixels to trigger propagation
+ * \param[in] skipdist distance before computing sum for propagation
+ * \param[in] delta difference required to stop propagation
+ * \param[in] maxbg maximum number of allowed bg pixels in ref scan
+ * \param[in] maxcomps use 0 for unlimited number of subdivided components
+ * \param[in] remainder set to 1 to get b.b. of remaining stuff
+ * \return boxa of rectangles covering the fg of pixs, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixSplitComponentIntoBoxa()
+ *
+ * \param[in] pix 1 bpp
+ * \param[in] box [optional] location of pix w/rt an origin
+ * \param[in] minsum minimum pixels to trigger propagation
+ * \param[in] skipdist distance before computing sum for propagation
+ * \param[in] delta difference required to stop propagation
+ * \param[in] maxbg maximum number of allowed bg pixels in ref scan
+ * \param[in] maxcomps use 0 for unlimited number of subdivided components
+ * \param[in] remainder set to 1 to get b.b. of remaining stuff
+ * \return boxa of rectangles covering the fg of pix, or NULL on error
+ *
+ * <pre>
+ * 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 pix, with respect to an origin that
+ * typically represents the UL corner of an underlying image,
+ * of which pix 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.
+ * </pre>
+ */
+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
+ lept_stderr("Iteration: %d\n", iter);
+ boxPrintStreamInfo(stderr, boxs);
+ boxaWriteStderr(boxat);
+ lept_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;
+}
+
+
+/*!
+ * \brief pixSearchForRectangle()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] boxs current region to investigate
+ * \param[in] minsum minimum pixels to trigger propagation
+ * \param[in] skipdist distance before computing sum for propagation
+ * \param[in] delta difference required to stop propagation
+ * \param[in] maxbg maximum number of allowed bg pixels in ref scan
+ * \param[in] sideflag side to search from
+ * \param[in] boxat add result of rectangular region found here
+ * \param[in] nascore add score for this rectangle here
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief makeMosaicStrips()
+ *
+ * \param[in] w, h
+ * \param[in] direction L_SCAN_HORIZONTAL or L_SCAN_VERTICAL
+ * \param[in] size of strips in the scan direction
+ * \return boxa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaCompareRegions()
+ *
+ * \param[in] boxa1, boxa2
+ * \param[in] areathresh minimum area of boxes to be considered
+ * \param[out] pnsame true if same number of boxes
+ * \param[out] pdiffarea fractional difference in total area
+ * \param[out] pdiffxor [optional] fractional difference in xor of regions
+ * \param[out] ppixdb [optional] debug pix showing two boxa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 (pdiffxor) *pdiffxor = 1.0;
+ if (ppixdb) *ppixdb = NULL;
+ if (pnsame) *pnsame = FALSE;
+ if (pdiffarea) *pdiffarea = 1.0;
+ if (!boxa1 || !boxa2)
+ return ERROR_INT("boxa1 and boxa2 not both defined", procName, 1);
+ if (!pnsame)
+ return ERROR_INT("&nsame not defined", procName, 1);
+ if (!pdiffarea)
+ return ERROR_INT("&diffarea not defined", procName, 1);
+
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixSelectLargeULComp()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] areaslop fraction near but less than 1.0
+ * \param[in] yslop number of pixels in y direction
+ * \param[in] connectivity 4 or 8
+ * \return box, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ boxaDestroy(&boxa1);
+ return NULL;
+ }
+ box = boxaSelectLargeULBox(boxa1, areaslop, yslop);
+ boxaDestroy(&boxa1);
+ return box;
+}
+
+
+/*!
+ * \brief boxaSelectLargeULBox()
+ *
+ * \param[in] boxas 1 bpp
+ * \param[in] areaslop fraction near but less than 1.0
+ * \param[in] yslop number of pixels in y direction
+ * \return box, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See usage notes in pixSelectLargeULComp().
+ * </pre>
+ */
+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/leptonica/src/boxfunc4.c b/leptonica/src/boxfunc4.c
new file mode 100644
index 00000000..9880a51a
--- /dev/null
+++ b/leptonica/src/boxfunc4.c
@@ -0,0 +1,1426 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file boxfunc4.c
+ * <pre>
+ *
+ * Boxa and Boxaa range selection
+ * BOXA *boxaSelectRange()
+ * BOXAA *boxaaSelectRange()
+ *
+ * Boxa size selection
+ * BOXA *boxaSelectBySize()
+ * NUMA *boxaMakeSizeIndicator()
+ * BOXA *boxaSelectByArea()
+ * NUMA *boxaMakeAreaIndicator()
+ * BOXA *boxaSelectByWHRatio()
+ * NUMA *boxaMakeWHRatioIndicator()
+ * BOXA *boxaSelectWithIndicator()
+ *
+ * Boxa permutation
+ * BOXA *boxaPermutePseudorandom()
+ * BOXA *boxaPermuteRandom()
+ * l_int32 boxaSwapBoxes()
+ *
+ * Boxa and box conversions
+ * PTA *boxaConvertToPta()
+ * BOXA *ptaConvertToBoxa()
+ * PTA *boxConvertToPta()
+ * BOX *ptaConvertToBox()
+ *
+ * Miscellaneous boxa functions
+ * l_int32 boxaGetExtent()
+ * l_int32 boxaGetCoverage()
+ * l_int32 boxaaSizeRange()
+ * l_int32 boxaSizeRange()
+ * l_int32 boxaLocationRange()
+ * NUMA *boxaGetSizes()
+ * l_int32 boxaGetArea()
+ * PIX *boxaDisplayTiled()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*
+ * Boxa and boxaa range selection *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaSelectRange()
+ *
+ * \param[in] boxas
+ * \param[in] first use 0 to select from the beginning
+ * \param[in] last use -1 to select to the end
+ * \param[in] copyflag L_COPY, L_CLONE
+ * \return boxad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if ((n = boxaGetCount(boxas)) == 0) {
+ L_WARNING("boxas is empty\n", procName);
+ return boxaCopy(boxas, copyflag);
+ }
+ first = L_MAX(0, first);
+ if (last < 0) last = n - 1;
+ if (first >= n)
+ return (BOXA *)ERROR_PTR("invalid first", procName, NULL);
+ if (last >= n) {
+ L_WARNING("last = %d is beyond max index = %d; adjusting\n",
+ procName, last, n - 1);
+ last = n - 1;
+ }
+ 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;
+}
+
+
+/*!
+ * \brief boxaaSelectRange()
+ *
+ * \param[in] baas
+ * \param[in] first use 0 to select from the beginning
+ * \param[in] last use -1 to select to the end
+ * \param[in] copyflag L_COPY, L_CLONE
+ * \return baad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if ((n = boxaaGetCount(baas)) == 0)
+ return (BOXAA *)ERROR_PTR("empty baas", procName, NULL);
+ first = L_MAX(0, first);
+ if (last < 0) last = n - 1;
+ if (first >= n)
+ return (BOXAA *)ERROR_PTR("invalid first", procName, NULL);
+ if (last >= n) {
+ L_WARNING("last = %d is beyond max index = %d; adjusting\n",
+ procName, last, n - 1);
+ last = n - 1;
+ }
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaSelectBySize()
+ *
+ * \param[in] boxas
+ * \param[in] width, height threshold dimensions
+ * \param[in] type L_SELECT_WIDTH, L_SELECT_HEIGHT,
+ * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH
+ * \param[in] relation L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return boxad filtered set, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The args specify constraints on the size of the
+ * components that are kept.
+ * (2) Uses box copies 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.
+ * </pre>
+ */
+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 (pchanged) *pchanged = FALSE;
+ if (!boxas)
+ return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+ if (boxaGetCount(boxas) == 0) {
+ L_WARNING("boxas is empty\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ 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);
+
+ /* Compute the indicator array for saving components */
+ if ((na =
+ boxaMakeSizeIndicator(boxas, width, height, type, relation)) == NULL)
+ return (BOXA *)ERROR_PTR("na not made", procName, NULL);
+
+ /* Filter to get output */
+ boxad = boxaSelectWithIndicator(boxas, na, pchanged);
+
+ numaDestroy(&na);
+ return boxad;
+}
+
+
+/*!
+ * \brief boxaMakeSizeIndicator()
+ *
+ * \param[in] boxa
+ * \param[in] width, height threshold dimensions
+ * \param[in] type L_SELECT_WIDTH, L_SELECT_HEIGHT,
+ * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 ((n = boxaGetCount(boxa)) == 0)
+ return (NUMA *)ERROR_PTR("boxa is empty", 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);
+
+ 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;
+}
+
+
+/*!
+ * \brief boxaSelectByArea()
+ *
+ * \param[in] boxas
+ * \param[in] area threshold value of width * height
+ * \param[in] relation L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return boxad filtered set, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Uses box copies 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.
+ * </pre>
+ */
+BOXA *
+boxaSelectByArea(BOXA *boxas,
+ l_int32 area,
+ l_int32 relation,
+ l_int32 *pchanged)
+{
+BOXA *boxad;
+NUMA *na;
+
+ PROCNAME("boxaSelectByArea");
+
+ if (pchanged) *pchanged = FALSE;
+ if (!boxas)
+ return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+ if (boxaGetCount(boxas) == 0) {
+ L_WARNING("boxas is empty\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ 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);
+
+ /* 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;
+}
+
+
+/*!
+ * \brief boxaMakeAreaIndicator()
+ *
+ * \param[in] boxa
+ * \param[in] area threshold value of width * height
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 ((n = boxaGetCount(boxa)) == 0)
+ return (NUMA *)ERROR_PTR("boxa is empty", 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);
+
+ 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;
+}
+
+
+/*!
+ * \brief boxaSelectByWHRatio()
+ *
+ * \param[in] boxas
+ * \param[in] ratio width/height threshold value
+ * \param[in] relation L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return boxad filtered set, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Uses box copies in the new boxa.
+ * (2) To keep narrow components, use relation = L_SELECT_IF_LT or
+ * L_SELECT_IF_LTE.
+ * To keep wide components, use relation = L_SELECT_IF_GT or
+ * L_SELECT_IF_GTE.
+ * </pre>
+ */
+BOXA *
+boxaSelectByWHRatio(BOXA *boxas,
+ l_float32 ratio,
+ l_int32 relation,
+ l_int32 *pchanged)
+{
+BOXA *boxad;
+NUMA *na;
+
+ PROCNAME("boxaSelectByWHRatio");
+
+ if (pchanged) *pchanged = FALSE;
+ if (!boxas)
+ return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+ if (boxaGetCount(boxas) == 0) {
+ L_WARNING("boxas is empty\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ 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);
+
+ /* Compute the indicator array for saving components */
+ na = boxaMakeWHRatioIndicator(boxas, ratio, relation);
+
+ /* Filter to get output */
+ boxad = boxaSelectWithIndicator(boxas, na, pchanged);
+
+ numaDestroy(&na);
+ return boxad;
+}
+
+
+/*!
+ * \brief boxaMakeWHRatioIndicator()
+ *
+ * \param[in] boxa
+ * \param[in] ratio width/height threshold value
+ * \param[in] 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
+ *
+ * <pre>
+ * Notes:
+ * (1) To keep narrow components, use relation = L_SELECT_IF_LT or
+ * L_SELECT_IF_LTE.
+ * To keep wide components, use relation = L_SELECT_IF_GT or
+ * L_SELECT_IF_GTE.
+ * </pre>
+ */
+NUMA *
+boxaMakeWHRatioIndicator(BOXA *boxa,
+ l_float32 ratio,
+ l_int32 relation)
+{
+l_int32 i, n, w, h, ival;
+l_float32 whratio;
+NUMA *na;
+
+ PROCNAME("boxaMakeWHRatioIndicator");
+
+ if (!boxa)
+ return (NUMA *)ERROR_PTR("boxa not defined", procName, NULL);
+ if ((n = boxaGetCount(boxa)) == 0)
+ return (NUMA *)ERROR_PTR("boxa is empty", 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);
+
+ na = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ ival = 0;
+ boxaGetBoxGeometry(boxa, i, NULL, NULL, &w, &h);
+ whratio = (l_float32)w / (l_float32)h;
+
+ if ((relation == L_SELECT_IF_LT && whratio < ratio) ||
+ (relation == L_SELECT_IF_GT && whratio > ratio) ||
+ (relation == L_SELECT_IF_LTE && whratio <= ratio) ||
+ (relation == L_SELECT_IF_GTE && whratio >= ratio))
+ ival = 1;
+ numaAddNumber(na, ival);
+ }
+
+ return na;
+}
+
+
+/*!
+ * \brief boxaSelectWithIndicator()
+ *
+ * \param[in] boxas
+ * \param[in] na indicator numa
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return boxad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Returns a copy of the boxa if no components are removed.
+ * (2) Uses box copies in the new boxa.
+ * (3) The indicator numa has values 0 (ignore) and 1 (accept).
+ * (4) If all indicator values are 0, the returned boxa is empty.
+ * </pre>
+ */
+BOXA *
+boxaSelectWithIndicator(BOXA *boxas,
+ NUMA *na,
+ l_int32 *pchanged)
+{
+l_int32 i, n, ival, nsave;
+BOX *box;
+BOXA *boxad;
+
+ PROCNAME("boxaSelectWithIndicator");
+
+ if (pchanged) *pchanged = FALSE;
+ 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_COPY);
+ }
+ 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_COPY);
+ boxaAddBox(boxad, box, L_INSERT);
+ }
+
+ return boxad;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Boxa Permutation *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaPermutePseudorandom()
+ *
+ * \param[in] boxas input boxa
+ * \return boxad with boxes permuted, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaPermuteRandom()
+ *
+ * \param[in] boxad [optional] can be null or equal to boxas
+ * \param[in] boxas input boxa
+ * \return boxad with boxes permuted, or NULL on error
+ *
+ * <pre>
+ * 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) If boxas is empty, return an empty boxad.
+ * (3) 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.
+ * (4) MSVC rand() has MAX_RAND = 2^15 - 1, so it will not do
+ * a proper permutation is the number of boxes exceeds this.
+ * </pre>
+ */
+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);
+ if ((n = boxaGetCount(boxad)) == 0)
+ return 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;
+}
+
+
+/*!
+ * \brief boxaSwapBoxes()
+ *
+ * \param[in] boxa
+ * \param[in] i, j two indices of boxes, that are to be swapped
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaConvertToPta()
+ *
+ * \param[in] boxa
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * (2) Other boxa --> pta functions are:
+ * * boxaExtractAsPta(): allows extraction of any dimension
+ * and/or side location, with each in a separate pta.
+ * * boxaExtractCorners(): extracts any of the four corners as a pta.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaConvertToBoxa()
+ *
+ * \param[in] pta
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxConvertToPta()
+ *
+ * \param[in] box
+ * \param[in] ncorners 2 or 4 for the representation of the box
+ * \return pta with %ncorners points, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaConvertToBox()
+ *
+ * \param[in] pta
+ * \return box minimum containing all points in the pta, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*---------------------------------------------------------------------*
+ * Miscellaneous Boxa functions *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaGetExtent()
+ *
+ * \param[in] boxa
+ * \param[out] pw [optional] width
+ * \param[out] ph [optional] height
+ * \param[out] pbox [optional] minimum box containing all boxes in boxa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This computes the minimum rectangular bounding region
+ * that contains all valid boxes in a boxa.
+ * (2) The returned w and h are the minimum size image
+ * that would contain all boxes untranslated.
+ * (3) 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.
+ * </pre>
+ */
+l_ok
+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 (pw) *pw = 0;
+ if (ph) *ph = 0;
+ if (pbox) *pbox = NULL;
+ 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;
+}
+
+
+/*!
+ * \brief boxaGetCoverage()
+ *
+ * \param[in] boxa
+ * \param[in] wc, hc dimensions of overall clipping rectangle with UL
+ * corner at (0, 0 that is covered by the boxes.
+ * \param[in] exactflag 1 for guaranteeing an exact result; 0 for getting
+ * an exact result only if the boxes do not overlap
+ * \param[out] pfract sum of box area as fraction of w * h
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaaSizeRange()
+ *
+ * \param[in] baa
+ * \param[out] pminw [optional] min width of all boxes
+ * \param[out] pmaxw [optional] max width of all boxes
+ * \param[out] pminh [optional] min height of all boxes
+ * \param[out] pmaxh [optional] max height of all boxes
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 (!pminw && !pmaxw && !pminh && !pmaxh)
+ return ERROR_INT("no data can be returned", procName, 1);
+ if (pminw) *pminw = 0;
+ if (pminh) *pminh = 0;
+ if (pmaxw) *pmaxw = 0;
+ if (pmaxh) *pmaxh = 0;
+ if (!baa)
+ return ERROR_INT("baa not defined", 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;
+ boxaDestroy(&boxa);
+ }
+
+ if (pminw) *pminw = minw;
+ if (pminh) *pminh = minh;
+ if (pmaxw) *pmaxw = maxw;
+ if (pmaxh) *pmaxh = maxh;
+ return 0;
+}
+
+
+/*!
+ * \brief boxaSizeRange()
+ *
+ * \param[in] boxa
+ * \param[out] pminw [optional] min width of all boxes
+ * \param[out] pmaxw [optional] max width of all boxes
+ * \param[out] pminh [optional] min height of all boxes
+ * \param[out] pmaxh [optional] max height of all boxes
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 (!pminw && !pmaxw && !pminh && !pmaxh)
+ return ERROR_INT("no data can be returned", procName, 1);
+ if (pminw) *pminw = 0;
+ if (pminh) *pminh = 0;
+ if (pmaxw) *pmaxw = 0;
+ if (pmaxh) *pmaxh = 0;
+ if (!boxa)
+ return ERROR_INT("boxa not defined", 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;
+}
+
+
+/*!
+ * \brief boxaLocationRange()
+ *
+ * \param[in] boxa
+ * \param[out] pminx [optional] min (UL corner) x value of all boxes
+ * \param[out] pminy [optional] min (UL corner) y value of all boxes
+ * \param[out] pmaxx [optional] max (UL corner) x value of all boxes
+ * \param[out] pmaxy [optional] max (UL corner) y value of all boxes
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 (!pminx && !pminy && !pmaxx && !pmaxy)
+ return ERROR_INT("no data can be returned", procName, 1);
+ if (pminx) *pminx = 0;
+ if (pminy) *pminy = 0;
+ if (pmaxx) *pmaxx = 0;
+ if (pmaxy) *pmaxy = 0;
+ if (!boxa)
+ return ERROR_INT("boxa not defined", 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;
+}
+
+
+/*!
+ * \brief boxaGetSizes()
+ *
+ * \param[in] boxa
+ * \param[out] pnaw [optional] widths of valid boxes
+ * \param[out] pnah [optional] heights of valid boxes
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+boxaGetSizes(BOXA *boxa,
+ NUMA **pnaw,
+ NUMA **pnah)
+{
+l_int32 i, n, w, h;
+BOX *box;
+
+ PROCNAME("boxaGetSizes");
+
+ if (pnaw) *pnaw = NULL;
+ if (pnah) *pnah = NULL;
+ if (!pnaw && !pnah)
+ return ERROR_INT("no output requested", procName, 1);
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+
+ n = boxaGetValidCount(boxa);
+ if (pnaw) *pnaw = numaCreate(n);
+ if (pnah) *pnah = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ box = boxaGetValidBox(boxa, i, L_COPY);
+ if (box) {
+ boxGetGeometry(box, NULL, NULL, &w, &h);
+ if (pnaw) numaAddNumber(*pnaw, w);
+ if (pnah) numaAddNumber(*pnah, h);
+ boxDestroy(&box);
+ }
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief boxaGetArea()
+ *
+ * \param[in] boxa
+ * \param[out] parea total area of all boxes
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Measures the total area of the boxes, without regard to overlaps.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief boxaDisplayTiled()
+ *
+ * \param[in] boxas
+ * \param[in] pixa [optional] background for each box
+ * \param[in] first index of first box
+ * \param[in] last index of last box; use -1 to go to end
+ * \param[in] maxwidth of output image
+ * \param[in] linewidth width of box outlines, before scaling
+ * \param[in] scalefactor applied to every box; use 1.0 for no scaling
+ * \param[in] background 0 for white, 1 for black; this is the color
+ * of the spacing between the images
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of black border added to each image;
+ * use 0 for no border
+ * \return pixd of tiled images of boxes, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+boxaDisplayTiled(BOXA *boxas,
+ PIXA *pixa,
+ l_int32 first,
+ l_int32 last,
+ l_int32 maxwidth,
+ l_int32 linewidth,
+ l_float32 scalefactor,
+ l_int32 background,
+ l_int32 spacing,
+ l_int32 border)
+{
+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);
+ }
+ }
+ first = L_MAX(0, first);
+ if (last < 0) last = n - 1;
+ if (first >= n) {
+ boxaDestroy(&boxa);
+ return (PIX *)ERROR_PTR("invalid first", procName, NULL);
+ }
+ if (last >= n) {
+ L_WARNING("last = %d is beyond max index = %d; adjusting\n",
+ procName, last, n - 1);
+ last = n - 1;
+ }
+ if (first > last) {
+ boxaDestroy(&boxa);
+ return (PIX *)ERROR_PTR("first > last", 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 = bmfCreate(NULL, fontsize);
+
+ pixat = pixaCreate(n);
+ boxaGetExtent(boxa, &w, &h, NULL);
+ for (i = first; i <= last; i++) {
+ box = boxaGetBox(boxa, i, L_CLONE);
+ if (!pixa) {
+ pix1 = pixCreate(w, h, 32);
+ pixSetAll(pix1);
+ } else {
+ pix1 = pixaGetPix(pixa, i, L_COPY);
+ }
+ pixSetBorderVal(pix1, 0, 0, 0, 2, 0x0000ff00);
+ snprintf(buf, sizeof(buf), "%d", i);
+ pix2 = pixAddSingleTextblock(pix1, bmf, buf, 0x00ff0000,
+ L_ADD_BELOW, NULL);
+ 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/leptonica/src/boxfunc5.c b/leptonica/src/boxfunc5.c
new file mode 100644
index 00000000..ac0e7b60
--- /dev/null
+++ b/leptonica/src/boxfunc5.c
@@ -0,0 +1,2212 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file boxfunc5.c
+ * <pre>
+ *
+ * Boxa sequence fitting
+ * BOXA *boxaSmoothSequenceMedian()
+ * BOXA *boxaWindowedMedian()
+ * BOXA *boxaModifyWithBoxa()
+ * BOXA *boxaConstrainSize()
+ * BOXA *boxaReconcileEvenOddHeight()
+ * static l_int32 boxaTestEvenOddHeight()
+ * BOXA *boxaReconcilePairWidth()
+ * l_int32 boxaSizeConsistency1()
+ * l_int32 boxaSizeConsistency2()
+ * BOXA *boxaReconcileAllByMedian()
+ * BOXA *boxaReconcileSidesByMedian()
+ * static void adjustSidePlotName() -- debug
+ * BOXA *boxaReconcileSizeByMedian()
+ * l_int32 boxaPlotSides() [for debugging]
+ * l_int32 boxaPlotSizes() [for debugging]
+ * BOXA *boxaFillSequence()
+ * static l_int32 boxaFillAll()
+ * l_int32 boxaSizeVariation()
+ * l_int32 boxaMedianDimensions()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+static l_int32 boxaTestEvenOddHeight(BOXA *boxa1, BOXA *boxa2, l_int32 start,
+ l_float32 *pdel1, l_float32 *pdel2);
+static l_int32 boxaFillAll(BOXA *boxa);
+
+static void adjustSidePlotName(char *buf, size_t size, const char *preface,
+ l_int32 select);
+
+/*---------------------------------------------------------------------*
+ * Boxa sequence fitting *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief boxaSmoothSequenceMedian()
+ *
+ * \param[in] boxas source boxa
+ * \param[in] halfwin half-width of sliding window; used to find median
+ * \param[in] subflag L_USE_MINSIZE, L_USE_MAXSIZE,
+ * L_SUB_ON_LOC_DIFF, L_SUB_ON_SIZE_DIFF,
+ * L_USE_CAPPED_MIN, L_USE_CAPPED_MAX
+ * \param[in] maxdiff parameter used with L_SUB_ON_LOC_DIFF,
+ * L_SUB_ON_SIZE_DIFF, L_USE_CAPPED_MIN,
+ * L_USE_CAPPED_MAX
+ * \param[in] extrapixels pixels added on all sides (or subtracted
+ * if %extrapixels < 0) when using
+ * L_SUB_ON_LOC_DIFF and L_SUB_ON_SIZE_DIFF
+ * \param[in] debug 1 for debug output
+ * \return boxad fitted boxa, or NULL on error
+ *
+ * <pre>
+ * 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 boxa and the filtered boxa,
+ * depending on %subflag. See boxaModifyWithBoxa() for
+ * details on the use of %subflag, %maxdiff and %extrapixels.
+ * (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,
+ * extrapixels);
+ * boxaDestroy(&boxam);
+ * </pre>
+ */
+BOXA *
+boxaSmoothSequenceMedian(BOXA *boxas,
+ l_int32 halfwin,
+ l_int32 subflag,
+ l_int32 maxdiff,
+ l_int32 extrapixels,
+ l_int32 debug)
+{
+l_int32 n;
+BOXA *boxae, *boxao, *boxamede, *boxamedo, *boxame, *boxamo, *boxad;
+PIX *pix1;
+
+ 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_LOC_DIFF && subflag != L_SUB_ON_SIZE_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("lept/smooth");
+ boxaWriteDebug("/tmp/lept/smooth/boxae.ba", boxae);
+ boxaWriteDebug("/tmp/lept/smooth/boxao.ba", boxao);
+ }
+
+ boxamede = boxaWindowedMedian(boxae, halfwin, debug);
+ boxamedo = boxaWindowedMedian(boxao, halfwin, debug);
+ if (debug) {
+ boxaWriteDebug("/tmp/lept/smooth/boxamede.ba", boxamede);
+ boxaWriteDebug("/tmp/lept/smooth/boxamedo.ba", boxamedo);
+ }
+
+ boxame = boxaModifyWithBoxa(boxae, boxamede, subflag, maxdiff, extrapixels);
+ boxamo = boxaModifyWithBoxa(boxao, boxamedo, subflag, maxdiff, extrapixels);
+ if (debug) {
+ boxaWriteDebug("/tmp/lept/smooth/boxame.ba", boxame);
+ boxaWriteDebug("/tmp/lept/smooth/boxamo.ba", boxamo);
+ }
+
+ boxad = boxaMergeEvenOdd(boxame, boxamo, 0);
+ if (debug) {
+ boxaPlotSides(boxas, NULL, NULL, NULL, NULL, NULL, &pix1);
+ pixWrite("/tmp/lept/smooth/plotsides1.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ boxaPlotSides(boxad, NULL, NULL, NULL, NULL, NULL, &pix1);
+ pixWrite("/tmp/lept/smooth/plotsides2.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ boxaPlotSizes(boxas, NULL, NULL, NULL, &pix1);
+ pixWrite("/tmp/lept/smooth/plotsizes1.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ boxaPlotSizes(boxad, NULL, NULL, NULL, &pix1);
+ pixWrite("/tmp/lept/smooth/plotsizes2.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ }
+
+ boxaDestroy(&boxae);
+ boxaDestroy(&boxao);
+ boxaDestroy(&boxamede);
+ boxaDestroy(&boxamedo);
+ boxaDestroy(&boxame);
+ boxaDestroy(&boxamo);
+ return boxad;
+}
+
+
+/*!
+ * \brief boxaWindowedMedian()
+ *
+ * \param[in] boxas source boxa
+ * \param[in] halfwin half width of window over which the median is found
+ * \param[in] debug 1 for debug output
+ * \return boxad smoothed boxa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+PIX *pix1;
+
+ 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) {
+ lept_mkdir("lept/windowed");
+ boxaPlotSides(boxaf, NULL, NULL, NULL, NULL, NULL, &pix1);
+ pixWrite("/tmp/lept/windowed/plotsides1.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ boxaPlotSides(boxad, NULL, NULL, NULL, NULL, NULL, &pix1);
+ pixWrite("/tmp/lept/windowed/plotsides2.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ boxaPlotSizes(boxaf, NULL, NULL, NULL, &pix1);
+ pixWrite("/tmp/lept/windowed/plotsizes1.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ boxaPlotSizes(boxad, NULL, NULL, NULL, &pix1);
+ pixWrite("/tmp/lept/windowed/plotsizes2.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ }
+
+ boxaDestroy(&boxaf);
+ numaDestroy(&nal);
+ numaDestroy(&nat);
+ numaDestroy(&nar);
+ numaDestroy(&nab);
+ numaDestroy(&naml);
+ numaDestroy(&namt);
+ numaDestroy(&namr);
+ numaDestroy(&namb);
+ return boxad;
+}
+
+
+/*!
+ * \brief boxaModifyWithBoxa()
+ *
+ * \param[in] boxas
+ * \param[in] boxam boxa with boxes used to modify those in boxas
+ * \param[in] subflag L_USE_MINSIZE, L_USE_MAXSIZE,
+ * L_SUB_ON_LOC_DIFF, L_SUB_ON_SIZE_DIFF,
+ * L_USE_CAPPED_MIN, L_USE_CAPPED_MAX
+ * \param[in] maxdiff parameter used with L_SUB_ON_LOC_DIFF,
+ * L_SUB_ON_SIZE_DIFF, L_USE_CAPPED_MIN,
+ * L_USE_CAPPED_MAX
+ * \param[in] extrapixels pixels added on all sides (or subtracted
+ * if %extrapixels < 0) when using
+ * L_SUB_ON_LOC_DIFF and L_SUB_ON_SIZE_DIFF
+ * \return boxad result after adjusting boxes in boxas, or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * * If %subflag == L_SUB_ON_LOC_DIFF: each side of the output box
+ * is found separately from the corresponding side of boxs and boxm.
+ * Use the boxm side, expanded by %extrapixels, if greater than
+ * %maxdiff pixels from the boxs side.
+ * * If %subflag == L_SUB_ON_SIZE_DIFF: the sides of the output box
+ * are determined in pairs from the width and height of boxs
+ * and boxm. If the boxm width differs by more than %maxdiff
+ * pixels from boxs, use the boxm left and right sides,
+ * expanded by %extrapixels. Ditto for the height difference.
+ * 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_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_LOC_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 uses two boxes and does not specify target dimensions.
+ * Additional constraints on the size of each box can be enforced
+ * by following this operation with boxaConstrainSize(), taking
+ * boxad as input.
+ * </pre>
+ */
+BOXA *
+boxaModifyWithBoxa(BOXA *boxas,
+ BOXA *boxam,
+ l_int32 subflag,
+ l_int32 maxdiff,
+ l_int32 extrapixels)
+{
+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_LOC_DIFF && subflag != L_SUB_ON_SIZE_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_LOC_DIFF) {
+ ld = (L_ABS(lm - ls) <= maxdiff) ? ls : lm - extrapixels;
+ td = (L_ABS(tm - ts) <= maxdiff) ? ts : tm - extrapixels;
+ rd = (L_ABS(rm - rs) <= maxdiff) ? rs : rm + extrapixels;
+ bd = (L_ABS(bm - bs) <= maxdiff) ? bs : bm + extrapixels;
+ } else if (subflag == L_SUB_ON_SIZE_DIFF) {
+ ld = (L_ABS(wm - ws) <= maxdiff) ? ls : lm - extrapixels;
+ td = (L_ABS(hm - hs) <= maxdiff) ? ts : tm - extrapixels;
+ rd = (L_ABS(wm - ws) <= maxdiff) ? rs : rm + extrapixels;
+ bd = (L_ABS(hm - hs) <= maxdiff) ? bs : bm + extrapixels;
+ } 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;
+}
+
+
+/*!
+ * \brief boxaConstrainSize()
+ *
+ * \param[in] boxas
+ * \param[in] width force width of all boxes to this size;
+ * input 0 to use the median width
+ * \param[in] widthflag L_ADJUST_SKIP, L_ADJUST_LEFT, L_ADJUST_RIGHT,
+ * or L_ADJUST_LEFT_AND_RIGHT
+ * \param[in] height force height of all boxes to this size;
+ * input 0 to use the median height
+ * \param[in] 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
+ *
+ * <pre>
+ * 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) Not all input boxes need to 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.
+ * (5) On error, a message is issued and a copy of the input boxa
+ * is returned.
+ * </pre>
+ */
+BOXA *
+boxaConstrainSize(BOXA *boxas,
+ l_int32 width,
+ l_int32 widthflag,
+ l_int32 height,
+ l_int32 heightflag)
+{
+l_int32 n, i, x, y, w, h, invalid;
+l_int32 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);
+
+ /* Need median values if requested or if there are invalid boxes */
+ invalid = boxaGetCount(boxas) - boxaGetValidCount(boxas);
+ medbox = NULL;
+ if (width == 0 || height == 0 || invalid > 0) {
+ if (boxaGetMedianVals(boxas, &x, &y, NULL, NULL, &w, &h)) {
+ L_ERROR("median vals not returned", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ medbox = boxCreate(x, y, w, h);
+ if (width == 0) width = w;
+ if (height == 0) height = h;
+ }
+
+ n = boxaGetCount(boxas);
+ boxad = boxaCreate(n);
+ for (i = 0; i < n; i++) {
+ if ((boxs = boxaGetValidBox(boxas, i, L_COPY)) == NULL)
+ 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;
+}
+
+
+/*!
+ * \brief boxaReconcileEvenOddHeight()
+ *
+ * \param[in] boxas containing at least 3 valid boxes in even and odd
+ * \param[in] sides L_ADJUST_TOP, L_ADJUST_BOT, L_ADJUST_TOP_AND_BOT
+ * \param[in] delh threshold on median height difference
+ * \param[in] op L_ADJUST_CHOOSE_MIN, L_ADJUST_CHOOSE_MAX
+ * \param[in] factor > 0.0, typically near 1.0
+ * \param[in] start 0 if pairing (0,1), etc; 1 if pairing (1,2), etc
+ * \return boxad adjusted, or a copy of boxas on error
+ *
+ * <pre>
+ * 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 boxaSmoothSequenceMedian(),
+ * 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.
+ * (6) If the median height is not representative of the boxes
+ * in %boxas, this can make things much worse. In that case,
+ * ignore the value of %op, and force pairwise equality of the
+ * heights, with pairwise maximal vertical extension.
+ * </pre>
+ */
+BOXA *
+boxaReconcileEvenOddHeight(BOXA *boxas,
+ l_int32 sides,
+ l_int32 delh,
+ l_int32 op,
+ l_float32 factor,
+ l_int32 start)
+{
+l_int32 n, he, ho, hmed, doeven;
+l_float32 del1, del2;
+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);
+ }
+
+ /* Get the median heights for each set */
+ boxaGetMedianVals(boxae, NULL, NULL, NULL, NULL, NULL, &he);
+ boxaGetMedianVals(boxao, NULL, NULL, NULL, NULL, NULL, &ho);
+ 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) {
+ boxa1e = boxaAdjustHeightToTarget(NULL, boxae, sides, hmed, delh);
+ boxa1o = boxaCopy(boxao, L_COPY);
+ } else { /* !doeven */
+ boxa1e = boxaCopy(boxae, L_COPY);
+ boxa1o = boxaAdjustHeightToTarget(NULL, boxao, sides, hmed, delh);
+ }
+ } else {
+ boxa1e = boxaCopy(boxae, L_CLONE);
+ boxa1o = boxaCopy(boxao, L_CLONE);
+ }
+ boxaDestroy(&boxae);
+ boxaDestroy(&boxao);
+
+ /* It can happen that the median is not a good measure for an
+ * entire book. In that case, the reconciliation above can do
+ * more harm than good. Sanity check by comparing height and y
+ * differences of adjacent even/odd boxes, before and after
+ * reconciliation. */
+ boxad = boxaMergeEvenOdd(boxa1e, boxa1o, 0);
+ boxaTestEvenOddHeight(boxas, boxad, start, &del1, &del2);
+ boxaDestroy(&boxa1e);
+ boxaDestroy(&boxa1o);
+ if (del2 < del1 + 10.)
+ return boxad;
+
+ /* Using the median made it worse. Skip reconciliation:
+ * forcing all pairs of top and bottom values to have
+ * maximum extent does not improve the situation either. */
+ L_INFO("Got worse: del2 = %f > del1 = %f\n", procName, del2, del1);
+ boxaDestroy(&boxad);
+ return boxaCopy(boxas, L_COPY);
+}
+
+
+/*!
+ * \brief boxaTestEvenOddHeight()
+ *
+ * \param[in] boxa1 input boxa 1
+ * \param[in] boxa2 input boxa 2
+ * \param[in] start 0 if pairing (0,1), etc; 1 if pairing (1,2), etc
+ * \param[out] pdel1 root mean of (dely^2 + delh^2 for boxa1
+ * \param[out] pdel2 root mean of (dely^2 + delh^2 for boxa2
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This compares differences in the y location and height of
+ * adjacent boxes, in each of the input boxa.
+ * </pre>
+ */
+static l_int32
+boxaTestEvenOddHeight(BOXA *boxa1,
+ BOXA *boxa2,
+ l_int32 start,
+ l_float32 *pdel1,
+ l_float32 *pdel2)
+{
+l_int32 i, n, npairs, y1a, y1b, y2a, y2b, h1a, h1b, h2a, h2b;
+l_float32 del1, del2;
+
+ PROCNAME("boxaTestEvenOddHeight");
+
+ if (pdel1) *pdel1 = 0.0;
+ if (pdel2) *pdel2 = 0.0;
+ if (!pdel1 || !pdel2)
+ return ERROR_INT("&del1 and &del2 not both defined", procName, 1);
+ if (!boxa1 || !boxa2)
+ return ERROR_INT("boxa1 and boxa2 not both defined", procName, 1);
+ n = L_MIN(boxaGetCount(boxa1), boxaGetCount(boxa2));
+
+ /* For boxa1 and boxa2 separately, we expect the y and h values
+ * to be similar for adjacent boxes. Get a measure of similarity
+ * by finding the sum of squares of differences between
+ * y values and between h values, and adding them. */
+ del1 = del2 = 0.0;
+ npairs = (n - start) / 2;
+ for (i = start; i < 2 * npairs; i += 2) {
+ boxaGetBoxGeometry(boxa1, i, NULL, &y1a, NULL, &h1a);
+ boxaGetBoxGeometry(boxa1, i + 1, NULL, &y1b, NULL, &h1b);
+ del1 += (l_float32)(y1a - y1b) * (y1a - y1b)
+ + (h1a - h1b) * (h1a - h1b);
+ boxaGetBoxGeometry(boxa2, i, NULL, &y2a, NULL, &h2a);
+ boxaGetBoxGeometry(boxa2, i + 1, NULL, &y2b, NULL, &h2b);
+ del2 += (l_float32)(y2a - y2b) * (y2a - y2b)
+ + (h2a - h2b) * (h2a - h2b);
+ }
+
+ /* Get the root of the average of the sum of square differences */
+ *pdel1 = (l_float32)sqrt((l_float64)del1 / (0.5 * n));
+ *pdel2 = (l_float32)sqrt((l_float64)del2 / (0.5 * n));
+ return 0;
+}
+
+
+/*!
+ * \brief boxaReconcilePairWidth()
+ *
+ * \param[in] boxas
+ * \param[in] delw threshold on adjacent width difference
+ * \param[in] op L_ADJUST_CHOOSE_MIN, L_ADJUST_CHOOSE_MAX
+ * \param[in] factor > 0.0, typically near 1.0
+ * \param[in] na [optional] indicator array allowing change
+ * \return boxad adjusted, or a copy of boxas on error
+ *
+ * <pre>
+ * 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 boxaSmoothSequenceMedian(),
+ * where even and odd boxa have been independently regulated.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaSizeConsistency1()
+ *
+ * \param[in] boxas of size >= 10
+ * \param[in] type L_CHECK_WIDTH, L_CHECK_HEIGHT
+ * \param[in] threshp threshold for pairwise fractional variation
+ * \param[in] threshm threshold for fractional variation from median
+ * \param[out] pfvarp [optional] average fractional pairwise variation
+ * \param[out] pfvarm [optional] average fractional median variation
+ * \param[out] psame decision for uniformity of page size (1, 0, -1)
+ *
+ * <pre>
+ * Notes:
+ * (1) This evaluates a boxa for particular types of dimensional
+ * variation. Select either width or height variation. Then
+ * it returns two numbers: one is based on pairwise (even/odd)
+ * variation; the other is based on the average variation
+ * from the boxa median.
+ * (2) For the pairwise variation, get the fraction of the absolute
+ * difference in dimension of each pair of boxes, and take
+ * the average value. The median variation is simply the
+ * the average of the fractional deviation from the median
+ * of all the boxes.
+ * (3) Use 0 for default values of %threshp and %threshm. They are
+ * threshp: 0.02
+ * threshm: 0.015
+ * (4) The intended application is that the boxes are a sequence of
+ * page regions in a book scan, and we calculate two numbers
+ * that can give an indication if the pages are approximately
+ * the same size. The pairwise variation should be small if
+ * the boxes are correctly calculated. If there are a
+ * significant number of random or systematic outliers, the
+ * pairwise variation will be large, and no decision will be made
+ * (i.e., return same == -1). Here are the possible outcomes:
+ * Pairwise Var Median Var Decision
+ * ------------ ---------- --------
+ * small small same size (1)
+ * small large different size (0)
+ * large small/large unknown (-1)
+ * </pre>
+ */
+l_ok
+boxaSizeConsistency1(BOXA *boxas,
+ l_int32 type,
+ l_float32 threshp,
+ l_float32 threshm,
+ l_float32 *pfvarp,
+ l_float32 *pfvarm,
+ l_int32 *psame)
+{
+l_int32 i, n, bw1, bh1, bw2, bh2, npairs;
+l_float32 ave, fdiff, sumdiff, med, fvarp, fvarm;
+NUMA *na1;
+
+ PROCNAME("boxaSizeConsistency1");
+
+ if (pfvarp) *pfvarp = 0.0;
+ if (pfvarm) *pfvarm = 0.0;
+ if (!psame)
+ return ERROR_INT("&same not defined", procName, 1);
+ *psame = -1;
+ if (!boxas)
+ return ERROR_INT("boxas not defined", procName, 1);
+ if (boxaGetValidCount(boxas) < 6)
+ return ERROR_INT("need a least 6 valid boxes", procName, 1);
+ if (type != L_CHECK_WIDTH && type != L_CHECK_HEIGHT)
+ return ERROR_INT("invalid type", procName, 1);
+ if (threshp < 0.0 || threshp >= 0.5)
+ return ERROR_INT("invalid threshp", procName, 1);
+ if (threshm < 0.0 || threshm >= 0.5)
+ return ERROR_INT("invalid threshm", procName, 1);
+ if (threshp == 0.0) threshp = 0.02f;
+ if (threshm == 0.0) threshm = 0.015f;
+
+ /* Evaluate pairwise variation */
+ n = boxaGetCount(boxas);
+ na1 = numaCreate(0);
+ for (i = 0, npairs = 0, sumdiff = 0; i < n - 1; i += 2) {
+ boxaGetBoxGeometry(boxas, i, NULL, NULL, &bw1, &bh1);
+ boxaGetBoxGeometry(boxas, i + 1, NULL, NULL, &bw2, &bh2);
+ if (bw1 == 0 || bh1 == 0 || bw2 == 0 || bh2 == 0)
+ continue;
+ npairs++;
+ if (type == L_CHECK_WIDTH) {
+ ave = (bw1 + bw2) / 2.0;
+ fdiff = L_ABS(bw1 - bw2) / ave;
+ numaAddNumber(na1, bw1);
+ numaAddNumber(na1, bw2);
+ } else { /* type == L_CHECK_HEIGHT) */
+ ave = (bh1 + bh2) / 2.0;
+ fdiff = L_ABS(bh1 - bh2) / ave;
+ numaAddNumber(na1, bh1);
+ numaAddNumber(na1, bh2);
+ }
+ sumdiff += fdiff;
+ }
+ fvarp = sumdiff / npairs;
+ if (pfvarp) *pfvarp = fvarp;
+
+ /* Evaluate the average abs fractional deviation from the median */
+ numaGetMedian(na1, &med);
+ if (med == 0.0) {
+ L_WARNING("median value is 0\n", procName);
+ } else {
+ numaGetMeanDevFromMedian(na1, med, &fvarm);
+ fvarm /= med;
+ if (pfvarm) *pfvarm = fvarm;
+ }
+ numaDestroy(&na1);
+
+ /* Make decision */
+ if (fvarp < threshp && fvarm < threshm)
+ *psame = 1;
+ else if (fvarp < threshp && fvarm > threshm)
+ *psame = 0;
+ else
+ *psame = -1; /* unknown */
+ return 0;
+}
+
+
+/*!
+ * \brief boxaSizeConsistency2()
+ *
+ * \param[in] boxas of size >= 10
+ * \param[out] pfdevw average fractional deviation from median width
+ * \param[out] pfdevh average fractional deviation from median height
+ * \param[in] debug 1 for debug plot output of input and regularized
+ * width and height
+ *
+ * <pre>
+ * Notes:
+ * (1) This evaluates a boxa for consistency of the box sizes.
+ * The intended application is that the boxes are a sequence of
+ * page regions in a book scan, and the output is a decision
+ * about whether the pages should be approximately the same size.
+ * The determination should be robust to outliers, both random
+ * and (for many cases) systematic.
+ * (2) This differs from boxaSizeConsistency1() in that it attempts
+ * to correct for box dimensional errors before doing the
+ * evaluation. For this reason, it may be less robust.
+ * (3) Adjacent even and odd boxes are expected to be the same size.
+ * Take them pairwise, and assume the minimum height, hmin,
+ * is correct. Then for (the usual case) wmin/hmin > 0.5, assume
+ * the minimum width is correct. If wmin/hmin <= 0.5, assume
+ * the maximum width is correct.
+ * (4) After correcting each pair so that they are the same size,
+ * compute the average fractional deviation, from median width and
+ * height. A deviation of width or height by more than about
+ * 0.02 is evidence that the boxes may be from a non-homogeneous
+ * source, such as a book with significantly different page sizes.
+ * </pre>
+ */
+l_ok
+boxaSizeConsistency2(BOXA *boxas,
+ l_float32 *pfdevw,
+ l_float32 *pfdevh,
+ l_int32 debug)
+{
+l_int32 i, n, bw1, bh1, bw2, bh2, npairs;
+l_float32 medw, medh, devw, devh, minw, maxw, minh, w;
+BOX *box;
+BOXA *boxa1;
+NUMA *naw, *nah;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa;
+
+ PROCNAME("boxaSizeConsistency2");
+
+ if (pfdevw) *pfdevw = 0.0;
+ if (pfdevh) *pfdevh = 0.0;
+ if (!boxas)
+ return ERROR_INT("boxas not defined", procName, 1);
+ if (!pfdevw || !pfdevh)
+ return ERROR_INT("&fdevw and &fdevh not both defined", procName, 1);
+ n = boxaGetCount(boxas);
+ if (n < 10) {
+ L_WARNING("small boxa; assuming OK", procName);
+ return 0;
+ }
+
+ /* Regularize w and h in pairs; skip last box if n is odd */
+ boxa1 = (debug) ? boxaCreate(n) : NULL;
+ naw = numaCreate(0);
+ nah = numaCreate(0);
+ for (i = 0, npairs = 0; i < n - 1; i += 2) {
+ boxaGetBoxGeometry(boxas, i, NULL, NULL, &bw1, &bh1);
+ boxaGetBoxGeometry(boxas, i + 1, NULL, NULL, &bw2, &bh2);
+ if (bw1 == 0 || bh1 == 0 || bw2 == 0 || bh2 == 0)
+ continue;
+ npairs++;
+ minw = (l_float32)L_MIN(bw1, bw2);
+ maxw = (l_float32)L_MAX(bw1, bw2);
+ minh = (l_float32)L_MIN(bh1, bh2);
+ w = (minw / minh > 0.5) ? minw : maxw;
+ numaAddNumber(naw, w);
+ numaAddNumber(nah, minh);
+ if (debug) {
+ box = boxCreate(0, 0, w, minh);
+ boxaAddBox(boxa1, box, L_COPY);
+ boxaAddBox(boxa1, box, L_INSERT);
+ }
+ }
+ if (npairs == 0) {
+ L_WARNING("no valid box pairs\n", procName);
+ numaDestroy(&naw);
+ numaDestroy(&nah);
+ boxaDestroy(&boxa1);
+ }
+
+ /* Get the median value of the regularized sizes, and find
+ * the average absolute fractional deviation from the median. */
+ numaGetMedian(naw, &medw);
+ numaGetMedian(nah, &medh);
+ numaGetMeanDevFromMedian(naw, medw, &devw);
+ numaGetMeanDevFromMedian(nah, medh, &devh);
+ *pfdevw = devw / medw;
+ *pfdevh = devh / medh;
+ if (debug) {
+ lept_stderr("medw = %5.1f, medh = %5.1f\n", medw, medh);
+ lept_stderr("fdevw = %6.3f, fdevh = %6.3f\n", *pfdevw, *pfdevh);
+ boxaPlotSizes(boxas, "input_boxa", NULL, NULL, &pix1);
+ boxaPlotSizes(boxa1, "regularized_boxa", NULL, NULL, &pix2);
+ pixDisplay(pix1, 500, 0);
+ pixDisplay(pix2, 500, 1000);
+ pixa = pixaCreate(2);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pix3 = pixaDisplayTiledInColumns(pixa, 2, 1.0, 3, 2);
+ lept_mkdir("lept/boxa");
+ pixWrite("/tmp/lept/boxa/eval.png", pix3, IFF_PNG);
+ pixDisplay(pix3, 100, 100);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa);
+ boxaDestroy(&boxa1);
+ }
+
+ numaDestroy(&naw);
+ numaDestroy(&nah);
+ return 0;
+}
+
+
+/*!
+ * \brief boxaReconcileAllByMedian()
+ *
+ * \param[in] boxas containing at least 6 valid boxes
+ * \param[in] select1 L_ADJUST_LEFT_AND_RIGHT or L_ADJUST_SKIP
+ * \param[in] select2 L_ADJUST_TOP_AND_BOT or L_ADJUST_SKIP
+ * \param[in] thresh threshold number of pixels to make adjustment
+ * \param[in] extra extra pixels to add beyond median value
+ * \param[in] pixadb use NULL to skip debug output
+ * \return boxad possibly adjusted from boxas; a copy of boxas on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This uses boxaReconcileSidesByMedian() to reconcile
+ * the left-and-right and/or top-and-bottom sides of the
+ * even and odd boxes, separately.
+ * (2) See boxaReconcileSidesByMedian() for use of %thresh and %extra.
+ * (3) If all box sides are within %thresh of the median value,
+ * the returned box will be identical to %boxas.
+ * </pre>
+ */
+BOXA *
+boxaReconcileAllByMedian(BOXA *boxas,
+ l_int32 select1,
+ l_int32 select2,
+ l_int32 thresh,
+ l_int32 extra,
+ PIXA *pixadb)
+ {
+l_int32 ncols;
+BOXA *boxa1e, *boxa1o, *boxa2e, *boxa2o, *boxa3e, *boxa3o, *boxad;
+PIX *pix1;
+
+ PROCNAME("boxaReconcileAllByMedian");
+
+ if (!boxas)
+ return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+ if (select1 != L_ADJUST_LEFT_AND_RIGHT && select1 != L_ADJUST_SKIP) {
+ L_WARNING("invalid select1; returning copy\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ if (select2 != L_ADJUST_TOP_AND_BOT && select2 != L_ADJUST_SKIP) {
+ L_WARNING("invalid select2; returning copy\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ if (thresh < 0) {
+ L_WARNING("thresh must be >= 0; returning copy\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ if (boxaGetValidCount(boxas) < 3) {
+ L_WARNING("need at least 3 valid boxes; returning copy\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+
+ /* Adjust even and odd box sides separately */
+ boxaSplitEvenOdd(boxas, 0, &boxa1e, &boxa1o);
+ ncols = 1;
+ if (select1 == L_ADJUST_LEFT_AND_RIGHT) {
+ ncols += 2;
+ boxa2e = boxaReconcileSidesByMedian(boxa1e, select1, thresh,
+ extra, pixadb);
+ } else {
+ boxa2e = boxaCopy(boxa1e, L_COPY);
+ }
+ if (select2 == L_ADJUST_TOP_AND_BOT) {
+ ncols += 2;
+ boxa3e = boxaReconcileSidesByMedian(boxa2e, select2, thresh,
+ extra, pixadb);
+ } else {
+ boxa3e = boxaCopy(boxa2e, L_COPY);
+ }
+ if (select1 == L_ADJUST_LEFT_AND_RIGHT)
+ boxa2o = boxaReconcileSidesByMedian(boxa1o, select1, thresh,
+ extra, pixadb);
+ else
+ boxa2o = boxaCopy(boxa1o, L_COPY);
+ if (select2 == L_ADJUST_TOP_AND_BOT)
+ boxa3o = boxaReconcileSidesByMedian(boxa2o, select2, thresh,
+ extra, pixadb);
+ else
+ boxa3o = boxaCopy(boxa2o, L_COPY);
+ boxad = boxaMergeEvenOdd(boxa3e, boxa3o, 0);
+
+ /* This generates 2 sets of 3 or 5 plots in a row, depending
+ * on whether select1 and select2 are true (not skipping).
+ * The top row is for even boxes; the bottom row is for odd boxes. */
+ if (pixadb) {
+ lept_mkdir("lept/boxa");
+ pix1 = pixaDisplayTiledInColumns(pixadb, ncols, 1.0, 30, 2);
+ pixWrite("/tmp/lept/boxa/recon_sides.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ }
+
+ boxaDestroy(&boxa1e);
+ boxaDestroy(&boxa1o);
+ boxaDestroy(&boxa2e);
+ boxaDestroy(&boxa2o);
+ boxaDestroy(&boxa3e);
+ boxaDestroy(&boxa3o);
+ return boxad;
+}
+
+
+/*!
+ * \brief boxaReconcileSidesByMedian()
+ *
+ * \param[in] boxas containing at least 3 valid boxes
+ * \param[in] select L_ADJUST_LEFT, L_ADJUST_RIGHT, etc.
+ * \param[in] thresh threshold number of pixels to make adjustment
+ * \param[in] extra extra pixels to add beyond median value
+ * \param[in] pixadb use NULL to skip debug output
+ * \return boxad possibly adjusted from boxas; a copy of boxas on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This modifies individual box sides if their location differs
+ * significantly (>= %thresh) from the median value.
+ * (2) %select specifies which sides are to be checked.
+ * (3) %thresh specifies the tolerance for different side locations.
+ * Any box side that differs from the median by this much will
+ * be set to the median value, plus the %extra amount.
+ * (4) If %extra is positive, the box dimensions are expanded.
+ * For example, for the left side, a positive %extra results in
+ * moving the left side farther to the left (i.e., in a negative
+ * direction).
+ * (5) If all box sides are within %thresh - 1 of the median value,
+ * the returned box will be identical to %boxas.
+ * (6) N.B. If you expect that even and odd box sides should be
+ * significantly different, this function must be called separately
+ * on the even and odd boxes in %boxas. Note also that the
+ * higher level function boxaReconcileAllByMedian() handles the
+ * even and odd box sides separately.
+ * </pre>
+ */
+BOXA *
+boxaReconcileSidesByMedian(BOXA *boxas,
+ l_int32 select,
+ l_int32 thresh,
+ l_int32 extra,
+ PIXA *pixadb)
+ {
+char buf[128];
+l_int32 i, n, diff;
+l_int32 left, right, top, bot, medleft, medright, medtop, medbot;
+BOX *box;
+BOXA *boxa1, *boxad;
+PIX *pix;
+
+ PROCNAME("boxaReconcileSidesByMedian");
+
+ if (!boxas)
+ return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+ if (select != L_ADJUST_LEFT && select != L_ADJUST_RIGHT &&
+ select != L_ADJUST_TOP && select != L_ADJUST_BOT &&
+ select != L_ADJUST_LEFT_AND_RIGHT && select != L_ADJUST_TOP_AND_BOT) {
+ L_WARNING("invalid select; returning copy\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ if (thresh < 0) {
+ L_WARNING("thresh must be >= 0; returning copy\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ if (boxaGetValidCount(boxas) < 3) {
+ L_WARNING("need at least 3 valid boxes; returning copy\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+
+ if (select == L_ADJUST_LEFT_AND_RIGHT) {
+ boxa1 = boxaReconcileSidesByMedian(boxas, L_ADJUST_LEFT, thresh, extra,
+ pixadb);
+ boxad = boxaReconcileSidesByMedian(boxa1, L_ADJUST_RIGHT, thresh, extra,
+ pixadb);
+ boxaDestroy(&boxa1);
+ return boxad;
+ }
+ if (select == L_ADJUST_TOP_AND_BOT) {
+ boxa1 = boxaReconcileSidesByMedian(boxas, L_ADJUST_TOP, thresh, extra,
+ pixadb);
+ boxad = boxaReconcileSidesByMedian(boxa1, L_ADJUST_BOT, thresh, extra,
+ pixadb);
+ boxaDestroy(&boxa1);
+ return boxad;
+ }
+
+ if (pixadb) {
+ l_int32 ndb = pixaGetCount(pixadb);
+ if (ndb == 0 || ndb == 5) { /* first of even and odd box sets */
+ adjustSidePlotName(buf, sizeof(buf), "init", select);
+ boxaPlotSides(boxas, buf, NULL, NULL, NULL, NULL, &pix);
+ pixaAddPix(pixadb, pix, L_INSERT);
+ }
+ }
+
+ n = boxaGetCount(boxas);
+ boxad = boxaCreate(n);
+ if (select == L_ADJUST_LEFT) {
+ boxaGetMedianVals(boxas, &medleft, NULL, NULL, NULL, NULL, NULL);
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxas, i, L_COPY);
+ boxGetSideLocations(box, &left, NULL, NULL, NULL);
+ diff = medleft - left;
+ if (L_ABS(diff) >= thresh)
+ boxAdjustSides(box, box, diff - extra, 0, 0, 0);
+ boxaAddBox(boxad, box, L_INSERT);
+ }
+ } else if (select == L_ADJUST_RIGHT) {
+ boxaGetMedianVals(boxas, NULL, NULL, &medright, NULL, NULL, NULL);
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxas, i, L_COPY);
+ boxGetSideLocations(box, NULL, &right, NULL, NULL);
+ diff = medright - right;
+ if (L_ABS(diff) >= thresh)
+ boxAdjustSides(box, box, 0, diff + extra, 0, 0);
+ boxaAddBox(boxad, box, L_INSERT);
+ }
+ } else if (select == L_ADJUST_TOP) {
+ boxaGetMedianVals(boxas, NULL, &medtop, NULL, NULL, NULL, NULL);
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxas, i, L_COPY);
+ boxGetSideLocations(box, NULL, NULL, &top, NULL);
+ diff = medtop - top;
+ if (L_ABS(diff) >= thresh)
+ boxAdjustSides(box, box, 0, 0, diff - extra, 0);
+ boxaAddBox(boxad, box, L_INSERT);
+ }
+ } else { /* select == L_ADJUST_BOT */
+ boxaGetMedianVals(boxas, NULL, NULL, NULL, &medbot, NULL, NULL);
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxas, i, L_COPY);
+ boxGetSideLocations(box, NULL, NULL, NULL, &bot);
+ diff = medbot - bot;
+ if (L_ABS(diff) >= thresh)
+ boxAdjustSides(box, box, 0, 0, 0, diff + extra);
+ boxaAddBox(boxad, box, L_INSERT);
+ }
+ }
+
+ if (pixadb) {
+ adjustSidePlotName(buf, sizeof(buf), "final", select);
+ boxaPlotSides(boxad, buf, NULL, NULL, NULL, NULL, &pix);
+ pixaAddPix(pixadb, pix, L_INSERT);
+ }
+ return boxad;
+}
+
+
+static void
+adjustSidePlotName(char *buf,
+ size_t size,
+ const char *preface,
+ l_int32 select)
+{
+ stringCopy(buf, preface, size - 8);
+ if (select == L_ADJUST_LEFT)
+ stringCat(buf, size, "-left");
+ else if (select == L_ADJUST_RIGHT)
+ stringCat(buf, size, "-right");
+ else if (select == L_ADJUST_TOP)
+ stringCat(buf, size, "-top");
+ else if (select == L_ADJUST_BOT)
+ stringCat(buf, size, "-bot");
+}
+
+
+/*!
+ * \brief boxaReconcileSizeByMedian()
+ *
+ * \param[in] boxas containing at least 6 valid boxes
+ * \param[in] type L_CHECK_WIDTH, L_CHECK_HEIGHT, L_CHECK_BOTH
+ * \param[in] dfract threshold fraction of dimensional variation from
+ * median; in range (0 ... 1); typ. about 0.05.
+ * \param[in] sfract threshold fraction of side variation from median;
+ * in range (0 ... 1); typ. about 0.04.
+ * \param[in] factor expansion for fixed box beyond median width;
+ * should be near 1.0.
+ * \param[out] pnadelw [optional] diff from median width for boxes
+ * above threshold
+ * \param[out] pnadelh [optional] diff from median height for boxes
+ * above threshold
+ * \param[out] pratiowh [optional] ratio of median width/height of boxas
+ * \return boxad possibly adjusted from boxas; a copy of boxas on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The basic idea is to identify significant differences in box
+ * dimension (either width or height) and modify the outlier boxes.
+ * (2) %type specifies if we are reconciling the width, height or both.
+ * (3) %dfract specifies the tolerance for different dimensions. Any
+ * box with a fractional difference from the median size that
+ * exceeds %dfract will be altered.
+ * (4) %sfract specifies the tolerance for different side locations.
+ * If a box has been marked by (3) for alteration, any side
+ * location that differs from the median side location by
+ * more than %sfract of the median dimension (medw or medh)
+ * will be moved.
+ * (5) Median width and height are found for all valid boxes (i.e.,
+ * for all boxes with width and height > 0.
+ * Median side locations are found separately for even and odd boxes,
+ * using only boxes that are "inliers"; i.e., that have been
+ * found by (3) to be within tolerance for width or height.
+ * (6) If all box dimensions are within threshold of the median size,
+ * just return a copy. Otherwise, box sides of the outliers
+ * will be adjusted.
+ * (7) Using %sfract, sides that are sufficiently far from the median
+ * are first moved to the median value. Then they are moved
+ * together (in or out) so that the final box dimension
+ * is %factor times the median dimension.
+ * (8) The arrays that are the initial deviation from median size
+ * (width and height) are optionally returned. Also optionally
+ * returned is the median w/h asperity ratio of the input %boxas.
+ * </pre>
+ */
+BOXA *
+boxaReconcileSizeByMedian(BOXA *boxas,
+ l_int32 type,
+ l_float32 dfract,
+ l_float32 sfract,
+ l_float32 factor,
+ NUMA **pnadelw,
+ NUMA **pnadelh,
+ l_float32 *pratiowh)
+{
+l_int32 i, n, ne, no, outfound, isvalid, ind, del, maxdel;
+l_int32 medw, medh, bw, bh, left, right, top, bot;
+l_int32 medleft, medlefte, medlefto, medright, medrighte, medrighto;
+l_int32 medtop, medtope, medtopo, medbot, medbote, medboto;
+l_float32 brat;
+BOX *box;
+BOXA *boxa1, *boxae, *boxao, *boxad;
+NUMA *naind, *nadelw, *nadelh;
+
+ PROCNAME("boxaReconcileSizeByMedian");
+
+ if (pnadelw) *pnadelw = NULL;
+ if (pnadelh) *pnadelh = NULL;
+ if (pratiowh) *pratiowh = 0.0;
+ if (!boxas)
+ return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+ if (type != L_CHECK_WIDTH && type != L_CHECK_HEIGHT &&
+ type != L_CHECK_BOTH) {
+ L_WARNING("invalid type; returning copy\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ if (dfract <= 0.0 || dfract >= 0.5) {
+ L_WARNING("invalid dimensional fract; returning copy\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ if (sfract <= 0.0 || sfract >= 0.5) {
+ L_WARNING("invalid side fract; returning copy\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+ if (factor < 0.8 || factor > 1.25)
+ L_WARNING("factor %5.3f is typ. closer to 1.0\n", procName, factor);
+ if (boxaGetValidCount(boxas) < 6) {
+ L_WARNING("need at least 6 valid boxes; returning copy\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+
+ /* If reconciling both width and height, optionally return array of
+ * median deviations and even/odd ratio for width measurements */
+ if (type == L_CHECK_BOTH) {
+ boxa1 = boxaReconcileSizeByMedian(boxas, L_CHECK_WIDTH, dfract, sfract,
+ factor, pnadelw, NULL, pratiowh);
+ boxad = boxaReconcileSizeByMedian(boxa1, L_CHECK_HEIGHT, dfract, sfract,
+ factor, NULL, pnadelh, NULL);
+ boxaDestroy(&boxa1);
+ return boxad;
+ }
+
+ n = boxaGetCount(boxas);
+ naind = numaCreate(n); /* outlier indicator array */
+ boxae = boxaCreate(0); /* even inliers */
+ boxao = boxaCreate(0); /* odd inliers */
+ outfound = FALSE;
+ if (type == L_CHECK_WIDTH) {
+ boxaMedianDimensions(boxas, &medw, &medh, NULL, NULL, NULL, NULL,
+ &nadelw, NULL);
+ if (pratiowh) {
+ *pratiowh = (l_float32)medw / (l_float32)medh;
+ L_INFO("median ratio w/h = %5.3f\n", procName, *pratiowh);
+ }
+ if (pnadelw)
+ *pnadelw = nadelw;
+ else
+ numaDestroy(&nadelw);
+
+ /* Check for outliers; assemble inliers */
+ for (i = 0; i < n; i++) {
+ if ((box = boxaGetValidBox(boxas, i, L_COPY)) == NULL) {
+ numaAddNumber(naind, 0);
+ continue;
+ }
+ boxGetGeometry(box, NULL, NULL, &bw, NULL);
+ brat = (l_float32)bw / (l_float32)medw;
+ if (brat < 1.0 - dfract || brat > 1.0 + dfract) {
+ outfound = TRUE;
+ numaAddNumber(naind, 1);
+ boxDestroy(&box);
+ } else { /* add to inliers */
+ numaAddNumber(naind, 0);
+ if (i % 2 == 0)
+ boxaAddBox(boxae, box, L_INSERT);
+ else
+ boxaAddBox(boxao, box, L_INSERT);
+ }
+ }
+ if (!outfound) { /* nothing to do */
+ numaDestroy(&naind);
+ boxaDestroy(&boxae);
+ boxaDestroy(&boxao);
+ L_INFO("no width outlier boxes found\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+
+ /* Get left/right parameters from inliers. Handle the case
+ * where there are no inliers for one of the sets. For example,
+ * when all the even boxes have a different dimension from
+ * the odd boxes, and the median arbitrarily gets assigned
+ * to the even boxes, there are no odd inliers; in that case,
+ * use the even inliers sides to decide whether to adjust
+ * the left or the right sides of individual outliers. */
+ L_INFO("fixing width of outlier boxes\n", procName);
+ medlefte = medrighte = medlefto = medrighto = 0;
+ if ((ne = boxaGetValidCount(boxae)) > 0)
+ boxaGetMedianVals(boxae, &medlefte, NULL, &medrighte, NULL,
+ NULL, NULL);
+ if ((no = boxaGetValidCount(boxao)) > 0)
+ boxaGetMedianVals(boxao, &medlefto, NULL, &medrighto, NULL,
+ NULL, NULL);
+ if (ne == 0) { /* use odd inliers values for both */
+ medlefte = medlefto;
+ medrighte = medrighto;
+ } else if (no == 0) { /* use even inliers values for both */
+ medlefto = medlefte;
+ medrighto = medrighte;
+ }
+
+ /* Adjust the left and/or right sides of outliers.
+ * For each box that is a dimensional outlier, consider each side.
+ * Any side that differs fractionally from the median value
+ * by more than %sfract times the median width (medw) is set to
+ * the median value for that side. Then both sides are moved
+ * an equal distance in or out to make w = %factor * medw. */
+ boxad = boxaCreate(n);
+ maxdel = (l_int32)(sfract * medw + 0.5);
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxas, i, L_COPY);
+ boxIsValid(box, &isvalid);
+ numaGetIValue(naind, i, &ind);
+ medleft = (i % 2 == 0) ? medlefte : medlefto;
+ medright = (i % 2 == 0) ? medrighte : medrighto;
+ if (ind == 1 && isvalid) { /* adjust sides */
+ boxGetSideLocations(box, &left, &right, NULL, NULL);
+ if (L_ABS(left - medleft) > maxdel) left = medleft;
+ if (L_ABS(right - medright) > maxdel) right = medright;
+ del = (l_int32)(factor * medw - (right - left)) / 2;
+ boxSetSide(box, L_SET_LEFT, left - del, 0);
+ boxSetSide(box, L_SET_RIGHT, right + del, 0);
+ }
+ boxaAddBox(boxad, box, L_INSERT);
+ }
+ } else { /* L_CHECK_HEIGHT */
+ boxaMedianDimensions(boxas, &medw, &medh, NULL, NULL, NULL, NULL,
+ NULL, &nadelh);
+ if (pratiowh) {
+ *pratiowh = (l_float32)medw / (l_float32)medh;
+ L_INFO("median ratio w/h = %5.3f\n", procName, *pratiowh);
+ }
+ if (pnadelh)
+ *pnadelh = nadelh;
+ else
+ numaDestroy(&nadelh);
+
+ /* Check for outliers; assemble inliers */
+ for (i = 0; i < n; i++) {
+ if ((box = boxaGetValidBox(boxas, i, L_COPY)) == NULL) {
+ numaAddNumber(naind, 0);
+ continue;
+ }
+ boxGetGeometry(box, NULL, NULL, NULL, &bh);
+ brat = (l_float32)bh / (l_float32)medh;
+ if (brat < 1.0 - dfract || brat > 1.0 + dfract) {
+ outfound = TRUE;
+ numaAddNumber(naind, 1);
+ boxDestroy(&box);
+ } else { /* add to inliers */
+ numaAddNumber(naind, 0);
+ if (i % 2 == 0)
+ boxaAddBox(boxae, box, L_INSERT);
+ else
+ boxaAddBox(boxao, box, L_INSERT);
+ }
+ }
+ if (!outfound) { /* nothing to do */
+ numaDestroy(&naind);
+ boxaDestroy(&boxae);
+ boxaDestroy(&boxao);
+ L_INFO("no height outlier boxes found\n", procName);
+ return boxaCopy(boxas, L_COPY);
+ }
+
+ /* Get top/bot parameters from inliers. Handle the case
+ * where there are no inliers for one of the sets. For example,
+ * when all the even boxes have a different dimension from
+ * the odd boxes, and the median arbitrarily gets assigned
+ * to the even boxes, there are no odd inliers; in that case,
+ * use the even inlier sides to decide whether to adjust
+ * the top or the bottom sides of individual outliers. */
+ L_INFO("fixing height of outlier boxes\n", procName);
+ medlefte = medtope = medbote = medtopo = medboto = 0;
+ if ((ne = boxaGetValidCount(boxae)) > 0)
+ boxaGetMedianVals(boxae, NULL, &medtope, NULL, &medbote,
+ NULL, NULL);
+ if ((no = boxaGetValidCount(boxao)) > 0)
+ boxaGetMedianVals(boxao, NULL, &medtopo, NULL, &medboto,
+ NULL, NULL);
+ if (ne == 0) { /* use odd inliers values for both */
+ medtope = medtopo;
+ medbote = medboto;
+ } else if (no == 0) { /* use even inliers values for both */
+ medtopo = medtope;
+ medboto = medbote;
+ }
+
+ /* Adjust the top and/or bottom sides of outliers.
+ * For each box that is a dimensional outlier, consider each side.
+ * Any side that differs fractionally from the median value
+ * by more than %sfract times the median height (medh) is
+ * set to the median value for that that side. Then both
+ * sides are moved an equal distance in or out to make
+ * h = %factor * medh). */
+ boxad = boxaCreate(n);
+ maxdel = (l_int32)(sfract * medh + 0.5);
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxas, i, L_COPY);
+ boxIsValid(box, &isvalid);
+ numaGetIValue(naind, i, &ind);
+ medtop = (i % 2 == 0) ? medtope : medtopo;
+ medbot = (i % 2 == 0) ? medbote : medboto;
+ if (ind == 1 && isvalid) { /* adjust sides */
+ boxGetSideLocations(box, NULL, NULL, &top, &bot);
+ if (L_ABS(top - medtop) > maxdel) top = medtop;
+ if (L_ABS(bot - medbot) > maxdel) bot = medbot;
+ del = (l_int32)(factor * medh - (bot - top)) / 2; /* typ > 0 */
+ boxSetSide(box, L_SET_TOP, L_MAX(0, top - del), 0);
+ boxSetSide(box, L_SET_BOT, bot + del, 0);
+ }
+ boxaAddBox(boxad, box, L_INSERT);
+ }
+ }
+ numaDestroy(&naind);
+ boxaDestroy(&boxae);
+ boxaDestroy(&boxao);
+ return boxad;
+}
+
+
+/*!
+ * \brief boxaPlotSides()
+ *
+ * \param[in] boxa source boxa
+ * \param[in] plotname [optional], can be NULL
+ * \param[out] pnal [optional] na of left sides
+ * \param[out] pnat [optional] na of top sides
+ * \param[out] pnar [optional] na of right sides
+ * \param[out] pnab [optional] na of bottom sides
+ * \param[out] ppixd pix of the output plot
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This debugging function shows the progression of the four
+ * sides in the boxa. 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/lept/plots/, and are named
+ * either with %plotname or, if NULL, a default name. If
+ * %plotname is used, make sure it has no whitespace characters.
+ * </pre>
+ */
+l_ok
+boxaPlotSides(BOXA *boxa,
+ const char *plotname,
+ NUMA **pnal,
+ NUMA **pnat,
+ NUMA **pnar,
+ NUMA **pnab,
+ PIX **ppixd)
+{
+char buf[128], titlebuf[128];
+char *dataname;
+static l_int32 plotid = 0;
+l_int32 n, i, w, h, left, top, right, bot;
+l_int32 debugprint = FALSE; /* change to TRUE to spam stderr */
+l_float32 med, dev;
+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 (ppixd) *ppixd = 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);
+ if (!ppixd)
+ return ERROR_INT("&pixd not defined", 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++) {
+ boxaGetBoxGeometry(boxat, i, &left, &top, &w, &h);
+ right = left + w - 1;
+ bot = top + h - 1;
+ numaAddNumber(nal, left);
+ numaAddNumber(nat, top);
+ numaAddNumber(nar, right);
+ numaAddNumber(nab, bot);
+ }
+ boxaDestroy(&boxat);
+
+ lept_mkdir("lept/plots");
+ if (plotname) {
+ snprintf(buf, sizeof(buf), "/tmp/lept/plots/sides.%s", plotname);
+ snprintf(titlebuf, sizeof(titlebuf), "%s: Box sides vs. box index",
+ plotname);
+ } else {
+ snprintf(buf, sizeof(buf), "/tmp/lept/plots/sides.%d", plotid++);
+ snprintf(titlebuf, sizeof(titlebuf), "Box sides vs. box index");
+ }
+ gplot = gplotCreate(buf, GPLOT_PNG, titlebuf,
+ "box index", "side 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");
+ *ppixd = gplotMakeOutputPix(gplot);
+ gplotDestroy(&gplot);
+
+ if (debugprint) {
+ dataname = (plotname) ? stringNew(plotname) : stringNew("no_name");
+ numaGetMedian(nal, &med);
+ numaGetMeanDevFromMedian(nal, med, &dev);
+ lept_stderr("%s left: med = %7.3f, meandev = %7.3f\n",
+ dataname, med, dev);
+ numaGetMedian(nat, &med);
+ numaGetMeanDevFromMedian(nat, med, &dev);
+ lept_stderr("%s top: med = %7.3f, meandev = %7.3f\n",
+ dataname, med, dev);
+ numaGetMedian(nar, &med);
+ numaGetMeanDevFromMedian(nar, med, &dev);
+ lept_stderr("%s right: med = %7.3f, meandev = %7.3f\n",
+ dataname, med, dev);
+ numaGetMedian(nab, &med);
+ numaGetMeanDevFromMedian(nab, med, &dev);
+ lept_stderr("%s bot: med = %7.3f, meandev = %7.3f\n",
+ dataname, med, dev);
+ LEPT_FREE(dataname);
+ }
+
+ 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;
+}
+
+
+/*!
+ * \brief boxaPlotSizes()
+ *
+ * \param[in] boxa source boxa
+ * \param[in] plotname [optional], can be NULL
+ * \param[out] pnaw [optional] na of widths
+ * \param[out] pnah [optional] na of heights
+ * \param[out] ppixd pix of the output plot
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This debugging function shows the progression of box width
+ * and height in the boxa. 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/lept/plots/, and are named
+ * either with %plotname or, if NULL, a default name. If
+ * %plotname is used, make sure it has no whitespace characters.
+ * </pre>
+ */
+l_ok
+boxaPlotSizes(BOXA *boxa,
+ const char *plotname,
+ NUMA **pnaw,
+ NUMA **pnah,
+ PIX **ppixd)
+{
+char buf[128], titlebuf[128];
+static l_int32 plotid = 0;
+l_int32 n, i, w, h;
+BOXA *boxat;
+GPLOT *gplot;
+NUMA *naw, *nah;
+
+ PROCNAME("boxaPlotSizes");
+
+ if (pnaw) *pnaw = NULL;
+ if (pnah) *pnah = NULL;
+ if (ppixd) *ppixd = 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);
+ if (!ppixd)
+ return ERROR_INT("&pixd not defined", procName, 1);
+
+ boxat = boxaFillSequence(boxa, L_USE_ALL_BOXES, 0);
+
+ /* Build the numas for the width and height */
+ naw = numaCreate(n);
+ nah = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ boxaGetBoxGeometry(boxat, i, NULL, NULL, &w, &h);
+ numaAddNumber(naw, w);
+ numaAddNumber(nah, h);
+ }
+ boxaDestroy(&boxat);
+
+ lept_mkdir("lept/plots");
+ if (plotname) {
+ snprintf(buf, sizeof(buf), "/tmp/lept/plots/size.%s", plotname);
+ snprintf(titlebuf, sizeof(titlebuf), "%s: Box size vs. box index",
+ plotname);
+ } else {
+ snprintf(buf, sizeof(buf), "/tmp/lept/plots/size.%d", plotid++);
+ snprintf(titlebuf, sizeof(titlebuf), "Box size vs. box index");
+ }
+ gplot = gplotCreate(buf, GPLOT_PNG, titlebuf,
+ "box index", "box dimension");
+ gplotAddPlot(gplot, NULL, naw, GPLOT_LINES, "width");
+ gplotAddPlot(gplot, NULL, nah, GPLOT_LINES, "height");
+ *ppixd = gplotMakeOutputPix(gplot);
+ gplotDestroy(&gplot);
+
+ if (pnaw)
+ *pnaw = naw;
+ else
+ numaDestroy(&naw);
+ if (pnah)
+ *pnah = nah;
+ else
+ numaDestroy(&nah);
+ return 0;
+}
+
+
+/*!
+ * \brief boxaFillSequence()
+ *
+ * \param[in] boxas with at least 3 boxes
+ * \param[in] useflag L_USE_ALL_BOXES, L_USE_SAME_PARITY_BOXES
+ * \param[in] debug 1 for debug output
+ * \return boxad filled boxa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaFillAll()
+ *
+ * \param[in] boxa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This static function replaces every invalid box with the
+ * nearest valid box. If there are no valid boxes, it
+ * issues a warning.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief boxaSizeVariation()
+ *
+ * \param[in] boxa at least 4 boxes
+ * \param[in] type L_SELECT_WIDTH, L_SELECT_HEIGHT
+ * \param[out] pdel_evenodd [optional] average absolute value of
+ * (even - odd) size pairs
+ * \param[out] prms_even [optional] rms deviation of even boxes
+ * \param[out] prms_odd [optional] rms deviation of odd boxes
+ * \param[out] prms_all [optional] rms deviation of all boxes
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This gives several measures of the smoothness of either the
+ * width or height of a sequence of boxes.
+ * See boxaMedianDimensions() for some other measures.
+ * (2) Statistics can be found separately for even and odd boxes.
+ * Additionally, the average pair-wise difference between
+ * adjacent even and odd boxes can be returned.
+ * (3) The use case is bounding boxes for scanned page images,
+ * where ideally the sizes should have little variance.
+ * </pre>
+ */
+l_ok
+boxaSizeVariation(BOXA *boxa,
+ l_int32 type,
+ l_float32 *pdel_evenodd,
+ l_float32 *prms_even,
+ l_float32 *prms_odd,
+ l_float32 *prms_all)
+{
+l_int32 n, ne, no, nmin, vale, valo, i;
+l_float32 sum;
+BOXA *boxae, *boxao;
+NUMA *nae, *nao, *na_all;
+
+ PROCNAME("boxaSizeVariation");
+
+ if (pdel_evenodd) *pdel_evenodd = 0.0;
+ if (prms_even) *prms_even = 0.0;
+ if (prms_odd) *prms_odd = 0.0;
+ if (prms_all) *prms_all = 0.0;
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+ if (type != L_SELECT_WIDTH && type != L_SELECT_HEIGHT)
+ return ERROR_INT("invalid type", procName, 1);
+ if (!pdel_evenodd && !prms_even && !prms_odd && !prms_all)
+ return ERROR_INT("nothing to do", procName, 1);
+ n = boxaGetCount(boxa);
+ if (n < 4)
+ return ERROR_INT("too few boxes", procName, 1);
+
+ boxaSplitEvenOdd(boxa, 0, &boxae, &boxao);
+ ne = boxaGetCount(boxae);
+ no = boxaGetCount(boxao);
+ nmin = L_MIN(ne, no);
+ if (nmin == 0) {
+ boxaDestroy(&boxae);
+ boxaDestroy(&boxao);
+ return ERROR_INT("either no even or no odd boxes", procName, 1);
+ }
+
+ if (type == L_SELECT_WIDTH) {
+ boxaGetSizes(boxae, &nae, NULL);
+ boxaGetSizes(boxao, &nao, NULL);
+ boxaGetSizes(boxa, &na_all, NULL);
+ } else { /* L_SELECT_HEIGHT) */
+ boxaGetSizes(boxae, NULL, &nae);
+ boxaGetSizes(boxao, NULL, &nao);
+ boxaGetSizes(boxa, NULL, &na_all);
+ }
+
+ if (pdel_evenodd) {
+ sum = 0.0;
+ for (i = 0; i < nmin; i++) {
+ numaGetIValue(nae, i, &vale);
+ numaGetIValue(nao, i, &valo);
+ sum += L_ABS(vale - valo);
+ }
+ *pdel_evenodd = sum / nmin;
+ }
+ if (prms_even)
+ numaSimpleStats(nae, 0, -1, NULL, NULL, prms_even);
+ if (prms_odd)
+ numaSimpleStats(nao, 0, -1, NULL, NULL, prms_odd);
+ if (prms_all)
+ numaSimpleStats(na_all, 0, -1, NULL, NULL, prms_all);
+
+ boxaDestroy(&boxae);
+ boxaDestroy(&boxao);
+ numaDestroy(&nae);
+ numaDestroy(&nao);
+ numaDestroy(&na_all);
+ return 0;
+}
+
+
+/*!
+ * \brief boxaMedianDimensions()
+ *
+ * \param[in] boxas containing at least 3 valid boxes in even and odd
+ * \param[out] pmedw [optional] median width of all boxes
+ * \param[out] pmedh [optional] median height of all boxes
+ * \param[out] pmedwe [optional] median width of even boxes
+ * \param[out] pmedwo [optional] median width of odd boxes
+ * \param[out] pmedhe [optional] median height of even boxes
+ * \param[out] pmedho [optional] median height of odd boxes
+ * \param[out] pnadelw [optional] width diff of each box from median
+ * \param[out] pnadelh [optional] height diff of each box from median
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This provides information that (1) allows identification of
+ * boxes that have unusual (outlier) width or height, and (2) can
+ * be used to regularize the sizes of the outlier boxes, assuming
+ * that the boxes satisfy a fairly regular sequence and should
+ * mostly have the same width and height.
+ * (2) This finds the median width and height, as well as separate
+ * median widths and heights of even and odd boxes. It also
+ * generates arrays that give the difference in width and height
+ * of each box from the median, which can be used to correct
+ * individual boxes.
+ * (3) All return values are optional.
+ * </pre>
+ */
+l_ok
+boxaMedianDimensions(BOXA *boxas,
+ l_int32 *pmedw,
+ l_int32 *pmedh,
+ l_int32 *pmedwe,
+ l_int32 *pmedwo,
+ l_int32 *pmedhe,
+ l_int32 *pmedho,
+ NUMA **pnadelw,
+ NUMA **pnadelh)
+{
+l_int32 i, n, bw, bh, medw, medh, medwe, medwo, medhe, medho;
+BOXA *boxae, *boxao;
+NUMA *nadelw, *nadelh;
+
+ PROCNAME("boxaMedianDimensions");
+
+ if (pmedw) *pmedw = 0;
+ if (pmedh) *pmedh = 0;
+ if (pmedwe) *pmedwe= 0;
+ if (pmedwo) *pmedwo= 0;
+ if (pmedhe) *pmedhe= 0;
+ if (pmedho) *pmedho= 0;
+ if (pnadelw) *pnadelw = NULL;
+ if (pnadelh) *pnadelh = NULL;
+ if (!boxas)
+ return ERROR_INT("boxas not defined", procName, 1);
+ if (boxaGetValidCount(boxas) < 6)
+ return ERROR_INT("need at least 6 valid boxes", procName, 1);
+
+ /* 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 ERROR_INT("don't have 3+ valid boxes of each type", procName, 1);
+ }
+
+ /* Get the relevant median widths and heights */
+ boxaGetMedianVals(boxas, NULL, NULL, NULL, NULL, &medw, &medh);
+ boxaGetMedianVals(boxae, NULL, NULL, NULL, NULL, &medwe, &medhe);
+ boxaGetMedianVals(boxao, NULL, NULL, NULL, NULL, &medwo, &medho);
+ if (pmedw) *pmedw = medw;
+ if (pmedh) *pmedh = medh;
+ if (pmedwe) *pmedwe = medwe;
+ if (pmedwo) *pmedwo = medwo;
+ if (pmedhe) *pmedhe = medhe;
+ if (pmedho) *pmedho = medho;
+
+ /* Find the variation from median dimension for each box */
+ n = boxaGetCount(boxas);
+ nadelw = numaCreate(n);
+ nadelh = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ boxaGetBoxGeometry(boxas, i, NULL, NULL, &bw, &bh);
+ if (bw == 0 || bh == 0) { /* invalid box */
+ numaAddNumber(nadelw, 0);
+ numaAddNumber(nadelh, 0);
+ } else {
+ numaAddNumber(nadelw, bw - medw);
+ numaAddNumber(nadelh, bh - medh);
+ }
+ }
+ if (pnadelw)
+ *pnadelw = nadelw;
+ else
+ numaDestroy(&nadelw);
+ if (pnadelh)
+ *pnadelh = nadelh;
+ else
+ numaDestroy(&nadelh);
+
+ boxaDestroy(&boxae);
+ boxaDestroy(&boxao);
+ return 0;
+}
+
diff --git a/leptonica/src/bytearray.c b/leptonica/src/bytearray.c
new file mode 100644
index 00000000..ccc8dce4
--- /dev/null
+++ b/leptonica/src/bytearray.c
@@ -0,0 +1,653 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file bytearray.c
+ * <pre>
+ *
+ * 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()
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Bounds on array size */
+static const l_uint32 MaxArraySize = 1000000000; /* 10^9 bytes */
+static const l_int32 InitialArraySize = 200; /*!< n'importe quoi */
+
+ /* Static function */
+static l_int32 l_byteaExtendArrayToSize(L_BYTEA *ba, size_t size);
+
+/*---------------------------------------------------------------------*
+ * Creation, copy, clone, destruction *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_byteaCreate()
+ *
+ * \param[in] nbytes determines initial size of data array
+ * \return l_bytea, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The allocated array is n + 1 bytes. This allows room
+ * for null termination.
+ * </pre>
+ */
+L_BYTEA *
+l_byteaCreate(size_t nbytes)
+{
+L_BYTEA *ba;
+
+ PROCNAME("l_byteaCreate");
+
+ if (nbytes <= 0 || nbytes > MaxArraySize)
+ nbytes = InitialArraySize;
+ ba = (L_BYTEA *)LEPT_CALLOC(1, sizeof(L_BYTEA));
+ ba->data = (l_uint8 *)LEPT_CALLOC(nbytes + 1, sizeof(l_uint8));
+ if (!ba->data) {
+ l_byteaDestroy(&ba);
+ return (L_BYTEA *)ERROR_PTR("ba array not made", procName, NULL);
+ }
+ ba->nalloc = nbytes + 1;
+ ba->refcount = 1;
+ return ba;
+}
+
+
+/*!
+ * \brief l_byteaInitFromMem()
+ *
+ * \param[in] data to be copied to the array
+ * \param[in] size amount of data
+ * \return l_bytea, or NULL on error
+ */
+L_BYTEA *
+l_byteaInitFromMem(const 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 (size > MaxArraySize)
+ return (L_BYTEA *)ERROR_PTR("size is too big", 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;
+}
+
+
+/*!
+ * \brief l_byteaInitFromFile()
+ *
+ * \param[in] 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);
+ ba = l_byteaInitFromStream(fp);
+ fclose(fp);
+ if (!ba)
+ return (L_BYTEA *)ERROR_PTR("ba not made", procName, NULL);
+ return ba;
+}
+
+
+/*!
+ * \brief l_byteaInitFromStream()
+ *
+ * \param[in] fp file 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) {
+ LEPT_FREE(data);
+ return (L_BYTEA *)ERROR_PTR("ba not made", procName, NULL);
+ }
+ memcpy(ba->data, data, nbytes);
+ ba->size = nbytes;
+ LEPT_FREE(data);
+ return ba;
+}
+
+
+/*!
+ * \brief l_byteaCopy()
+ *
+ * \param[in] bas source lba
+ * \param[in] copyflag L_COPY, L_CLONE
+ * \return clone or copy of bas, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If cloning, up the refcount and return a ptr to %bas.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief l_byteaDestroy()
+ *
+ * \param[in,out] pba will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Accessors *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_byteaGetSize()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief l_byteaGetData()
+ *
+ * \param[in] ba
+ * \param[out] psize size of data in lba
+ * \return ptr to existing data array, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The returned ptr is owned by %ba. Do not free it!
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_byteaCopyData()
+ *
+ * \param[in] ba
+ * \param[out] psize size of data in lba
+ * \return copy of data in use in the data array, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) The returned data is owned by the caller. The input %ba
+ * still owns the original data array.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_byteaAppendData()
+ *
+ * \param[in] ba
+ * \param[in] newdata byte array to be appended
+ * \param[in] newbytes size of data array
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+l_byteaAppendData(L_BYTEA *ba,
+ const 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) {
+ if (l_byteaExtendArrayToSize(ba, 2 * reqsize))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+
+ memcpy(ba->data + size, newdata, newbytes);
+ ba->size += newbytes;
+ return 0;
+}
+
+
+/*!
+ * \brief l_byteaAppendString()
+ *
+ * \param[in] ba
+ * \param[in] str null-terminated string to be appended
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+l_byteaAppendString(L_BYTEA *ba,
+ const 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) {
+ if (l_byteaExtendArrayToSize(ba, 2 * reqsize))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+
+ memcpy(ba->data + size, str, len);
+ ba->size += len;
+ return 0;
+}
+
+
+/*!
+ * \brief l_byteaExtendArrayToSize()
+ *
+ * \param[in] ba
+ * \param[in] size new size of lba data array
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If necessary, reallocs the byte array to %size.
+ * (2) The max buffer size is 1 GB.
+ * </pre>
+ */
+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 (ba->nalloc > MaxArraySize) /* belt & suspenders */
+ return ERROR_INT("ba has too many ptrs", procName, 1);
+ if (size > MaxArraySize)
+ return ERROR_INT("size > 1 GB; too large", procName, 1);
+ if (size <= ba->nalloc) {
+ L_INFO("size too small; no extension\n", procName);
+ return 0;
+ }
+
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_byteaJoin()
+ *
+ * \param[in] ba1
+ * \param[in,out] pba2 data array is added to the one in ba1;
+ * then ba2 is destroyed and its pointer is nulled.
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is a no-op, not an error, for %ba2 to be null.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_byteaSplit()
+ *
+ * \param[in] ba1 lba to split; array bytes nulled beyond the split loc
+ * \param[in] splitloc location in ba1 to split; ba2 begins there
+ * \param[out] pba2 with data starting at splitloc
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_byteaFindEachSequence()
+ *
+ * \param[in] ba
+ * \param[in] sequence subarray of bytes to find in data
+ * \param[in] seqlen length of sequence, in bytes
+ * \param[out] pda byte positions of each occurrence of %sequence
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+l_byteaFindEachSequence(L_BYTEA *ba,
+ const l_uint8 *sequence,
+ size_t 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_byteaWrite()
+ *
+ * \param[in] fname output file
+ * \param[in] ba
+ * \param[in] startloc first byte to output
+ * \param[in] nbytes number of bytes to write; use 0 to write to
+ * the end of the data array
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+l_byteaWrite(const char *fname,
+ L_BYTEA *ba,
+ size_t startloc,
+ size_t nbytes)
+{
+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, nbytes);
+ fclose(fp);
+ return ret;
+}
+
+
+/*!
+ * \brief l_byteaWriteStream()
+ *
+ * \param[in] fp file stream opened for binary write
+ * \param[in] ba
+ * \param[in] startloc first byte to output
+ * \param[in] nbytes number of bytes to write; use 0 to write to
+ * the end of the data array
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+l_byteaWriteStream(FILE *fp,
+ L_BYTEA *ba,
+ size_t startloc,
+ size_t nbytes)
+{
+l_uint8 *data;
+size_t size, maxbytes;
+
+ 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);
+ maxbytes = size - startloc;
+ nbytes = (nbytes == 0) ? maxbytes : L_MIN(nbytes, maxbytes);
+
+ fwrite(data + startloc, 1, nbytes, fp);
+ return 0;
+}
diff --git a/leptonica/src/ccbord.c b/leptonica/src/ccbord.c
new file mode 100644
index 00000000..dd69b14d
--- /dev/null
+++ b/leptonica/src/ccbord.c
@@ -0,0 +1,2631 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file ccbord.c
+ * <pre>
+ *
+ * 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()
+ * static CCBORD *pixGetCCBorders()
+ * PTAA *pixGetOuterBordersPtaa()
+ * static PTA *pixGetOuterBorderPta()
+ *
+ * Lower-level border location routines
+ * PTAA *pixGetOuterBorder()
+ * static l_int32 pixGetHoleBorder()
+ * static l_int32 findNextBorderPixel()
+ * static 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.
+ *
+ * </pre>
+ */
+
+#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 labeled clockwise
+ * starting from the west:
+ * 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 functions */
+static l_int32 ccbaExtendArray(CCBORDA *ccba);
+static CCBORD *pixGetCCBorders(PIX *pixs, BOX *box);
+static PTA *pixGetOuterBorderPta(PIX *pixs, BOX *box);
+static l_ok pixGetHoleBorder(CCBORD *ccb, PIX *pixs, BOX *box,
+ l_int32 xs, l_int32 ys);
+static 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);
+static void locateOutsideSeedPixel(l_int32 fpx, l_int32 fpy, l_int32 spx,
+ l_int32 spy, l_int32 *pxs, l_int32 *pys);
+
+#ifndef NO_CONSOLE_IO
+#define DEBUG_PRINT 0
+#endif /* NO CONSOLE_IO */
+
+
+/*---------------------------------------------------------------------*
+ * ccba and ccb creation and destruction *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ccbaCreate()
+ *
+ * \param[in] pixs 1 bpp; can be null
+ * \param[in] 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;
+
+ ccba = (CCBORDA *)LEPT_CALLOC(1, sizeof(CCBORDA));
+ 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) {
+ ccbaDestroy(&ccba);
+ return (CCBORDA *)ERROR_PTR("ccba ptrs not made", procName, NULL);
+ }
+ return ccba;
+}
+
+
+/*!
+ * \brief ccbaDestroy()
+ *
+ * \param[in,out] pccba will be set to null befoe returning
+ * \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;
+}
+
+
+/*!
+ * \brief ccbCreate()
+ *
+ * \param[in] pixs [optional]; can be null
+ * \return ccb or NULL on error
+ */
+CCBORD *
+ccbCreate(PIX *pixs)
+{
+BOXA *boxa;
+CCBORD *ccb;
+PTA *start;
+PTAA *local;
+
+ PROCNAME("ccbCreate");
+
+ if (pixs && pixGetDepth(pixs) != 1) /* pixs can be null */
+ return (CCBORD *)ERROR_PTR("pixs defined and not 1bpp", procName, NULL);
+
+ ccb = (CCBORD *)LEPT_CALLOC(1, sizeof(CCBORD));
+ ccb->refcount++;
+ if (pixs)
+ ccb->pix = pixClone(pixs);
+ boxa = boxaCreate(1);
+ ccb->boxa = boxa;
+ start = ptaCreate(1);
+ ccb->start = start;
+ local = ptaaCreate(1);
+ ccb->local = local;
+ return ccb;
+}
+
+
+/*!
+ * \brief ccbDestroy()
+ *
+ * \param[in,out] pccb will be set to null before returning
+ * \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;
+ }
+}
+
+
+/*---------------------------------------------------------------------*
+ * ccba addition *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ccbaAddCcb()
+ *
+ * \param[in] ccba
+ * \param[in] ccb to be added by insertion
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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) {
+ if (ccbaExtendArray(ccba))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ ccba->ccb[n] = ccb;
+ ccba->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief ccbaExtendArray()
+ *
+ * \param[in] 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ccbaGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ccbaGetCcb()
+ *
+ * \param[in] ccba
+ * \param[in] index
+ * \return ccb, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This returns a clone of the ccb; it must be destroyed
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixGetAllCCBorders()
+ *
+ * \param[in] 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) {
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa);
+ return (CCBORDA *)ERROR_PTR("ccba not made", procName, NULL);
+ }
+ for (i = 0; i < n; i++) {
+ if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) {
+ ccbaDestroy(&ccba);
+ pixaDestroy(&pixa);
+ boxaDestroy(&boxa);
+ return (CCBORDA *)ERROR_PTR("pix not found", procName, NULL);
+ }
+ if ((box = pixaGetBox(pixa, i, L_CLONE)) == NULL) {
+ ccbaDestroy(&ccba);
+ pixaDestroy(&pixa);
+ boxaDestroy(&boxa);
+ pixDestroy(&pix);
+ return (CCBORDA *)ERROR_PTR("box not found", procName, NULL);
+ }
+ ccb = pixGetCCBorders(pix, box);
+ pixDestroy(&pix);
+ boxDestroy(&box);
+ if (!ccb) {
+ ccbaDestroy(&ccba);
+ pixaDestroy(&pixa);
+ boxaDestroy(&boxa);
+ return (CCBORDA *)ERROR_PTR("ccb not made", procName, NULL);
+ }
+/* ptaWriteStream(stderr, ccb->local, 1); */
+ ccbaAddCcb(ccba, ccb);
+ }
+
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa);
+ return ccba;
+}
+
+
+/*!
+ * \brief pixGetCCBorders()
+ *
+ * \param[in] pixs 1 bpp, one 8-connected component
+ * \param[in] box of %pixs, in global coords
+ * \return ccbord, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static 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) {
+ ccbDestroy(&ccb);
+ 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) {
+ ccbDestroy(&ccb);
+ pixDestroy(&pixh);
+ return (CCBORD *)ERROR_PTR("boxa not made", procName, NULL);
+ }
+ nh = boxaGetCount(boxa);
+/* lept_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);
+ lept_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;
+}
+
+
+/*!
+ * \brief pixGetOuterBordersPtaa()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixGetOuterBorderPta()
+ *
+ * \param[in] pixs 1 bpp, one 8-connected component
+ * \param[in] box [optional] of %pixs, in global coordinates
+ * \return pta of outer border, in global coords, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixGetOuterBorder()
+ *
+ * \param[in] ccb unfilled
+ * \param[in] pixs for the component at hand
+ * \param[in] box for the component, in global coords
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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)) {
+ pixDestroy(&pixb);
+ 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);
+
+ pta = ptaCreate(0);
+ ptaaAddPta(ccb->local, pta, L_INSERT);
+ ptaAddPt(pta, px - 1, py - 1); /* initial point */
+ pixGetDimensions(pixb, &w, &h, NULL);
+ 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;
+}
+
+
+/*!
+ * \brief pixGetHoleBorder()
+ *
+ * \param[in] ccb the exterior border is already made
+ * \param[in] pixs for the connected component at hand
+ * \param[in] box for the specific hole border, in relative
+ * coordinates to the c.c.
+ * \param[in] xs, ys first pixel on hole border, relative to c.c.
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+static l_ok
+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);
+
+ pta = ptaCreate(0);
+ 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;
+}
+
+
+/*!
+ * \brief findNextBorderPixel()
+ *
+ * \param[in] w, h
+ * \param[in] data, wpl
+ * \param[in] px, py current P
+ * \param[in,out] pqpos input current Q; new Q
+ * \param[out] pnpx, pnpy new P
+ * \return 0 if next pixel found; 1 otherwise
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static 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];
+ if (npx < 0 || npx >= w || npy < 0 || npy >= h)
+ continue;
+ line = data + npy * wpl;
+ val = GET_DATA_BIT(line, npx);
+ if (val) {
+ *pnpx = npx;
+ *pnpy = npy;
+ *pqpos = qpostab[pos];
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+
+/*!
+ * \brief locateOutsideSeedPixel()
+ *
+ * \param[in] fpx, fpy location of first pixel
+ * \param[in] spx, spy location of second pixel
+ * \param[out] pxs, pys seed pixel to be returned
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ccbaGenerateGlobalLocs()
+ *
+ * \param[in] ccba with local chain ptaa of borders computed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) 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.
+ * </pre>
+ */
+l_ok
+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) {
+ ccbDestroy(&ccb);
+ 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 */
+ ptag = ptaCreate(n);
+ 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;
+}
+
+
+/*!
+ * \brief ccbaGenerateStepChains()
+ *
+ * \param[in] ccba with local chain ptaa of borders computed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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) {
+ ccbDestroy(&ccb);
+ 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 */
+ na = numaCreate(n);
+ 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;
+}
+
+
+/*!
+ * \brief ccbaStepChainsToPixCoords()
+ *
+ * \param[in] ccba with step chains numaa of borders
+ * \param[in] coordtype CCB_GLOBAL_COORDS or CCB_LOCAL_COORDS
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) {
+ ccbDestroy(&ccb);
+ return ERROR_INT("step numaa not found", procName, 1);
+ } if ((boxa = ccb->boxa) == NULL) {
+ ccbDestroy(&ccb);
+ return ERROR_INT("boxa not found", procName, 1);
+ } if ((ptas = ccb->start) == NULL) {
+ ccbDestroy(&ccb);
+ 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)) {
+ ccbDestroy(&ccb);
+ 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) {
+ ccbDestroy(&ccb);
+ 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) {
+ ccbDestroy(&ccb);
+ numaDestroy(&na);
+ 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;
+}
+
+
+/*!
+ * \brief ccbaGenerateSPGlobalLocs()
+ *
+ * \param[in] ccba
+ * \param[in] ptsflag CCB_SAVE_ALL_PTS or CCB_SAVE_TURNING_PTS
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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)) {
+ ccbDestroy(&ccb);
+ 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) {
+ ccbDestroy(&ccb);
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ccbaGenerateSinglePath()
+ *
+ * \param[in] ccba
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 hole 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.
+ * </pre>
+ */
+l_ok
+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);
+ ptas = ptaCreate(0);
+ 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;
+ ptaap = ptaaCreate(nb - 1);
+ ptaf = ptaCreate(nb - 1);
+ ptal = ptaCreate(nb - 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) { /* lost the hole */
+ lostholes++;
+/* boxPrintStreamInfo(stderr, boxa->box[0]); */
+ }
+ ptaaAddPta(ptaap, ptac, L_INSERT);
+/* lept_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);
+ ptarp = ptaReverse(ptap, 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_INFO("***** %d lost holes *****\n", procName, lostholes);
+ return 0;
+}
+
+
+/*!
+ * \brief getCutPathForHole()
+ *
+ * \param[in] pix 1 bpp, of c.c.
+ * \param[in] pta of outer border
+ * \param[in] boxinner bounding box of hole path
+ * \param[out] pdir direction (0-3), returned; only needed for debug
+ * \param[out] plen 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
+ *
+ * <pre>
+ * 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, the hole will not be rendered.
+ * Nevertheless, the image can be perfectly reconstructed
+ * from the boundary representation.
+ * </pre>
+ */
+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);
+
+ pixGetDimensions(pix, &w, &h, NULL);
+ ptac = ptaCreate(4);
+ 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;
+ }
+
+ /* Sometimes, there is nothing. */
+ ptaEmpty(ptac);
+ *plen = 0;
+ return ptac;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ * Border rendering *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ccbaDisplayBorder()
+ *
+ * \param[in] ccba
+ * \return pix of border pixels, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Uses global ptaa, which gives each border pixel in
+ * global coordinates, and must be computed in advance
+ * by calling ccbaGenerateGlobalLocs().
+ * </pre>
+ */
+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);
+ ccbDestroy(&ccb);
+ 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;
+}
+
+
+/*!
+ * \brief ccbaDisplaySPBorder()
+ *
+ * \param[in] ccba
+ * \return pix of border pixels, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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);
+ ccbDestroy(&ccb);
+ 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;
+}
+
+
+/*!
+ * \brief ccbaDisplayImage1()
+ *
+ * \param[in] ccba
+ * \return pix of image, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ pixDestroy(&pixd);
+ ccbDestroy(&ccb);
+ 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);
+ ccbDestroy(&ccb);
+ 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) {
+ pixDestroy(&pixd);
+ ccbDestroy(&ccb);
+ 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) {
+ pixDestroy(&pixd);
+ ccbDestroy(&ccb);
+ 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) {
+ pixDestroy(&pixd);
+ pixDestroy(&pixt);
+ ccbDestroy(&ccb);
+ 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 */
+ pixh = pixCreateTemplate(pixt);
+ 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;
+}
+
+
+
+/*!
+ * \brief ccbaDisplayImage2()
+ *
+ * \param[in] ccba
+ * \return pix of image, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ pixDestroy(&pixd);
+ ccbDestroy(&ccb);
+ return (PIX *)ERROR_PTR("boxa not found", procName, NULL);
+ }
+ if (boxaGetBoxGeometry(boxa, 0, &xul, &yul, &w, &h)) {
+ pixDestroy(&pixd);
+ ccbDestroy(&ccb);
+ return (PIX *)ERROR_PTR("b. box not found", procName, NULL);
+ }
+ pixc = pixCreate(w + 2, h + 2, 1);
+ pixs = pixCreateTemplate(pixc);
+
+ if ((ptaa = ccb->local) == NULL) {
+ pixDestroy(&pixc);
+ pixDestroy(&pixs);
+ ccbDestroy(&ccb);
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ccbaWrite()
+ *
+ * \param[in] filename
+ * \param[in] ccba
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+
+/*!
+ * \brief ccbaWriteStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] ccba
+ * \return 0 if OK; 1 on error
+ *
+ * Format:
+ * \code
+ * 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
+ * \endcode
+ */
+l_ok
+ccbaWriteStream(FILE *fp,
+ CCBORDA *ccba)
+{
+#if HAVE_LIBZ /* defined in environ.h */
+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;
+#endif
+
+ 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);
+ snprintf(strbuf, sizeof(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)) {
+ bbufferDestroy(&bbuf);
+ ccbDestroy(&ccb);
+ 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 */
+}
+
+
+/*!
+ * \brief ccbaRead()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ccbaReadStream()
+ *
+ * \param[in] fp file stream
+ * \return ccba, or NULL on error
+ *
+ * \code
+ * 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
+ * \endcode
+ */
+CCBORDA *
+ccbaReadStream(FILE *fp)
+{
+#if HAVE_LIBZ /* defined in environ.h */
+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;
+#endif
+
+ 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);
+ dataout = zlibUncompress(datain, inbytes, &outbytes);
+ LEPT_FREE(datain);
+ if (!dataout)
+ return (CCBORDA *)ERROR_PTR("dataout not made", procName, NULL);
+
+ offset = 18;
+ memcpy(strbuf, dataout, offset);
+ strbuf[17] = '\0';
+ if (memcmp(strbuf, "ccba:", 5) != 0) {
+ LEPT_FREE(dataout);
+ return (CCBORDA *)ERROR_PTR("file not type ccba", procName, NULL);
+ }
+ sscanf(strbuf, "ccba: %7d cc\n", &ncc);
+/* lept_stderr("ncc = %d\n", ncc); */
+ if ((ccba = ccbaCreate(NULL, ncc)) == NULL) {
+ LEPT_FREE(dataout);
+ return (CCBORDA *)ERROR_PTR("ccba not made", procName, NULL);
+ }
+
+ memcpy(&width, dataout + offset, 4);
+ offset += 4;
+ memcpy(&height, dataout + offset, 4);
+ offset += 4;
+ ccba->w = width;
+ ccba->h = height;
+/* lept_stderr("width = %d, height = %d\n", width, height); */
+
+ for (i = 0; i < ncc; i++) { /* should be ncc */
+ ccb = ccbCreate(NULL);
+ ccbaAddCcb(ccba, ccb);
+
+ memcpy(&xoff, dataout + offset, 4);
+ offset += 4;
+ memcpy(&yoff, dataout + offset, 4);
+ offset += 4;
+ memcpy(&w, dataout + offset, 4);
+ offset += 4;
+ memcpy(&h, dataout + offset, 4);
+ offset += 4;
+ box = boxCreate(xoff, yoff, w, h);
+ boxaAddBox(ccb->boxa, box, L_INSERT);
+/* lept_stderr("xoff = %d, yoff = %d, w = %d, h = %d\n",
+ xoff, yoff, w, h); */
+
+ memcpy(&nb, dataout + offset, 4);
+ offset += 4;
+/* lept_stderr("num borders = %d\n", nb); */
+ step = numaaCreate(nb);
+ ccb->step = step;
+
+ for (j = 0; j < nb; j++) { /* should be nb */
+ memcpy(&startx, dataout + offset, 4);
+ offset += 4;
+ memcpy(&starty, dataout + offset, 4);
+ offset += 4;
+ ptaAddPt(ccb->start, startx, starty);
+/* lept_stderr("startx = %d, starty = %d\n", startx, starty); */
+ na = numaCreate(0);
+ 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(dataout);
+ return ccba;
+
+#endif /* !HAVE_LIBZ */
+}
+
+
+/*---------------------------------------------------------------------*
+ * SVG Output *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ccbaWriteSVG()
+ *
+ * \param[in] filename
+ * \param[in] ccba
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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(ccba)) == NULL)
+ return ERROR_INT("svgstr not made", procName, 1);
+
+ l_binaryWrite(filename, "w", svgstr, strlen(svgstr));
+ LEPT_FREE(svgstr);
+
+ return 0;
+}
+
+
+/*!
+ * \brief ccbaWriteSVGString()
+ *
+ * \param[in] ccba
+ * \return string in svg-formatted, that can be written to file,
+ * or NULL on error.
+ */
+char *
+ccbaWriteSVGString(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 (!ccba)
+ return (char *)ERROR_PTR("ccba not defined", procName, NULL);
+
+ sa = sarrayCreate(0);
+ 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) {
+ sarrayDestroy(&sa);
+ return (char *)ERROR_PTR("ccb not found", procName, NULL);
+ }
+ if ((pta = ccb->spglobal) == NULL) {
+ sarrayDestroy(&sa);
+ ccbDestroy(&ccb);
+ 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);
+ snprintf(smallbuf, sizeof(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);
+/* lept_stderr("%s", svgstr); */
+
+ sarrayDestroy(&sa);
+ return svgstr;
+}
diff --git a/leptonica/src/ccbord.h b/leptonica/src/ccbord.h
new file mode 100644
index 00000000..cccef6eb
--- /dev/null
+++ b/leptonica/src/ccbord.h
@@ -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.
+ *====================================================================*/
+
+#ifndef LEPTONICA_CCBORD_H
+#define LEPTONICA_CCBORD_H
+
+/*!
+ * \file ccbord.h
+ *
+ * <pre>
+ * CCBord: represents a single connected component
+ * CCBorda: an array of CCBord
+ * </pre>
+ */
+
+ /*! Use in ccbaStepChainsToPixCoords() */
+/*! CCB Coords */
+enum {
+ CCB_LOCAL_COORDS = 1,
+ CCB_GLOBAL_COORDS = 2
+};
+
+ /*! Use in ccbaGenerateSPGlobalLocs() */
+/*! CCB Points */
+enum {
+ CCB_SAVE_ALL_PTS = 1,
+ CCB_SAVE_TURNING_PTS = 2
+};
+
+
+ /*!
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+
+/*! Array of 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/leptonica/src/ccthin.c b/leptonica/src/ccthin.c
new file mode 100644
index 00000000..968e8620
--- /dev/null
+++ b/leptonica/src/ccthin.c
@@ -0,0 +1,476 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file ccthin.c
+ * <pre>
+ *
+ * PIXA *pixaThinConnected()
+ * PIX *pixThinConnected()
+ * PIX *pixThinConnectedBySet()
+ * SELA *selaMakeThinSets()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* ------------------------------------------------------------
+ * The sels used here (and their rotated counterparts) are the
+ * useful 3x3 Sels for thinning. They are defined in sel2.c,
+ * and the sets are constructed in selaMakeThinSets().
+ * 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
+ * ------------------------------------------------------------ */
+
+/*----------------------------------------------------------------*
+ * CC-preserving thinning *
+ *----------------------------------------------------------------*/
+/*!
+ * \brief pixaThinConnected()
+ *
+ * \param[in] pixas of 1 bpp pix
+ * \param[in] type L_THIN_FG, L_THIN_BG
+ * \param[in] connectivity 4 or 8
+ * \param[in] maxiters max number of iters allowed;
+ * use 0 to iterate until completion
+ * \return pixds, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes in pixThinConnected().
+ * </pre>
+ */
+PIXA *
+pixaThinConnected(PIXA *pixas,
+ l_int32 type,
+ l_int32 connectivity,
+ l_int32 maxiters)
+{
+l_int32 i, n, d, same;
+PIX *pix1, *pix2;
+PIXA *pixad;
+SELA *sela;
+
+ PROCNAME("pixaThinConnected");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (type != L_THIN_FG && type != L_THIN_BG)
+ return (PIXA *)ERROR_PTR("invalid fg/bg type", procName, NULL);
+ if (connectivity != 4 && connectivity != 8)
+ return (PIXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+ if (maxiters == 0) maxiters = 10000;
+
+ pixaVerifyDepth(pixas, &same, &d);
+ if (d != 1)
+ return (PIXA *)ERROR_PTR("pix are not all 1 bpp", procName, NULL);
+
+ if (connectivity == 4)
+ sela = selaMakeThinSets(1, 0);
+ else /* connectivity == 8 */
+ sela = selaMakeThinSets(5, 0);
+
+ n = pixaGetCount(pixas);
+ pixad = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ pix2 = pixThinConnectedBySet(pix1, type, sela, maxiters);
+ pixaAddPix(pixad, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ selaDestroy(&sela);
+ return pixad;
+}
+
+
+/*!
+ * \brief pixThinConnected()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] type L_THIN_FG, L_THIN_BG
+ * \param[in] connectivity 4 or 8
+ * \param[in] maxiters max number of iters allowed;
+ * use 0 to iterate until completion
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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) This is a simple interface for 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) Duality between operations on fg and bg require switching
+ * the connectivity. 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 alternate connectivity gets preserved.
+ * For example, to thicken the fg with 2 rounds of iterations
+ * using 4-c.c., thin the bg using Sels that preserve 8-connectivity:
+ * Pix *pix = pixThinConnected(pixs, L_THIN_BG, 8, 2);
+ * (5) This makes and destroys the sela set each time. It's not a large
+ * overhead, but if you are calling this thousands of times on
+ * very small images, you can avoid the overhead; e.g.
+ * Sela *sela = selaMakeThinSets(1, 0); // for 4-c.c.
+ * Pix *pix = pixThinConnectedBySet(pixs, L_THIN_FG, sela, 0);
+ * using set 1 for 4-c.c. and set 5 for 8-c.c operations.
+ * </pre>
+ */
+PIX *
+pixThinConnected(PIX *pixs,
+ l_int32 type,
+ l_int32 connectivity,
+ l_int32 maxiters)
+{
+PIX *pixd;
+SELA *sela;
+
+ PROCNAME("pixThinConnected");
+
+ 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;
+
+ if (connectivity == 4)
+ sela = selaMakeThinSets(1, 0);
+ else /* connectivity == 8 */
+ sela = selaMakeThinSets(5, 0);
+
+ pixd = pixThinConnectedBySet(pixs, type, sela, maxiters);
+
+ selaDestroy(&sela);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixThinConnectedBySet()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] type L_THIN_FG, L_THIN_BG
+ * \param[in] sela of Sels for parallel composite HMTs
+ * \param[in] maxiters max number of iters allowed;
+ * use 0 to iterate until completion
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes in pixThinConnected().
+ * (2) This takes a sela representing one of 11 sets of HMT Sels.
+ * The HMTs from this set are run in parallel and the result
+ * is OR'd before being subtracted from the source. For each
+ * iteration, this "parallel" thin is performed four times
+ * sequentially, for sels rotated by 90 degrees in all four
+ * directions.
+ * (3) The "parallel" and "sequential" nomenclature is standard
+ * in digital filtering. Here, "parallel" operations work on the
+ * same source (pixd), and accumulate the results in a temp
+ * image before actually applying them to the source (in this
+ * case, using an in-place subtraction). "Sequential" operations
+ * operate directly on the source (pixd) to produce the result
+ * (in this case, with four sequential thinning operations, one
+ * from each of four directions).
+ * </pre>
+ */
+PIX *
+pixThinConnectedBySet(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 *pix1, *pix2, *pixd;
+SEL *sel, *selr;
+
+ PROCNAME("pixThinConnectedBySet");
+
+ 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++) {
+ pix1 = pixCreateTemplate(pixs);
+ pixaAddPix(pixahmt, pix1, L_INSERT);
+ }
+ pixhmt = pixaGetPixArray(pixahmt);
+ if (!pixhmt) {
+ pixaDestroy(&pixahmt);
+ return (PIX *)ERROR_PTR("pixhmt array not made", procName, NULL);
+ }
+
+ /* 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++) {
+ pix1 = 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, pix1, &same);
+ pixDestroy(&pix1);
+ if (same) {
+/* L_INFO("%d iterations to completion\n", procName, i); */
+ break;
+ }
+ }
+
+ /* This is a bit tricky. If we're thickening the foreground, then
+ * we get a fg border of thickness equal to the number of
+ * iterations. This border is connected to all components that
+ * were initially touching the border, but as it grows, it does
+ * not touch other growing components -- it leaves a 1 pixel wide
+ * background between it and the growing components, and that
+ * thin background prevents the components from growing further.
+ * This border can be entirely removed as follows:
+ * (1) Subtract the original (unthickened) image pixs from the
+ * thickened image. This removes the pixels that were originally
+ * touching the border.
+ * (2) Get all remaining pixels that are connected to the border.
+ * (3) Remove those pixels from the thickened image. */
+ if (type == L_THIN_BG) {
+ pixInvert(pixd, pixd); /* finish with duality */
+ pix1 = pixSubtract(NULL, pixd, pixs);
+ pix2 = pixExtractBorderConnComps(pix1, 4);
+ pixSubtract(pixd, pixd, pix2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+
+ pixaDestroy(&pixahmt);
+ return pixd;
+}
+
+
+/*!
+ * \brief selaMakeThinSets()
+ *
+ * \param[in] index into specific sets
+ * \param[in] debug 1 to output display of sela
+ * \return sela, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) These are specific sets of HMTs to be used in parallel for
+ * for thinning from each of four directions.
+ * (2) The sets are indexed as follows:
+ * For thinning (e.g., run to completion):
+ * index = 1 sel_4_1, sel_4_2, sel_4_3
+ * index = 2 sel_4_1, sel_4_5, sel_4_6
+ * index = 3 sel_4_1, sel_4_7, sel_4_7_rot
+ * index = 4 sel_48_1, sel_48_1_rot, sel_48_2
+ * index = 5 sel_8_2, sel_8_3, sel_8_5, sel_8_6
+ * index = 6 sel_8_2, sel_8_3, sel_48_2
+ * index = 7 sel_8_1, sel_8_5, sel_8_6
+ * index = 8 sel_8_2, sel_8_3, sel_8_8, sel_8_9
+ * index = 9 sel_8_5, sel_8_6, sel_8_7, sel_8_7_rot
+ * For thickening (e.g., just a few iterations):
+ * index = 10 sel_4_2, sel_4_3
+ * index = 11 sel_8_4
+ * (3) For a very smooth skeleton, use set 1 for 4 connected and
+ * set 5 for 8 connected thins.
+ * </pre>
+ */
+SELA *
+selaMakeThinSets(l_int32 index,
+ l_int32 debug)
+{
+SEL *sel;
+SELA *sela1, *sela2, *sela3;
+
+ PROCNAME("selaMakeThinSets");
+
+ if (index < 1 || index > 11)
+ return (SELA *)ERROR_PTR("invalid index", procName, NULL);
+
+ sela2 = selaCreate(4);
+ switch(index)
+ {
+ case 1:
+ sela1 = sela4ccThin(NULL);
+ selaFindSelByName(sela1, "sel_4_1", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_4_2", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_4_3", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ break;
+ case 2:
+ sela1 = sela4ccThin(NULL);
+ selaFindSelByName(sela1, "sel_4_1", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_4_5", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_4_6", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ break;
+ case 3:
+ sela1 = sela4ccThin(NULL);
+ selaFindSelByName(sela1, "sel_4_1", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_4_7", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ sel = selRotateOrth(sel, 1);
+ selaAddSel(sela2, sel, "sel_4_7_rot", L_INSERT);
+ break;
+ case 4:
+ sela1 = sela4and8ccThin(NULL);
+ selaFindSelByName(sela1, "sel_48_1", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ sel = selRotateOrth(sel, 1);
+ selaAddSel(sela2, sel, "sel_48_1_rot", L_INSERT);
+ selaFindSelByName(sela1, "sel_48_2", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ break;
+ case 5:
+ sela1 = sela8ccThin(NULL);
+ selaFindSelByName(sela1, "sel_8_2", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_8_3", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_8_5", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_8_6", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ break;
+ case 6:
+ sela1 = sela8ccThin(NULL);
+ sela3 = sela4and8ccThin(NULL);
+ selaFindSelByName(sela1, "sel_8_2", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_8_3", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela3, "sel_48_2", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaDestroy(&sela3);
+ break;
+ case 7:
+ sela1 = sela8ccThin(NULL);
+ selaFindSelByName(sela1, "sel_8_1", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_8_5", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_8_6", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ break;
+ case 8:
+ sela1 = sela8ccThin(NULL);
+ selaFindSelByName(sela1, "sel_8_2", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_8_3", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_8_8", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_8_9", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ break;
+ case 9:
+ sela1 = sela8ccThin(NULL);
+ selaFindSelByName(sela1, "sel_8_5", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_8_6", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_8_7", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ sel = selRotateOrth(sel, 1);
+ selaAddSel(sela2, sel, "sel_8_7_rot", L_INSERT);
+ break;
+ case 10: /* thicken for this one; use just a few iterations */
+ sela1 = sela4ccThin(NULL);
+ selaFindSelByName(sela1, "sel_4_2", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ selaFindSelByName(sela1, "sel_4_3", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ break;
+ case 11: /* thicken for this one; use just a few iterations */
+ sela1 = sela8ccThin(NULL);
+ selaFindSelByName(sela1, "sel_8_4", NULL, &sel);
+ selaAddSel(sela2, sel, NULL, L_COPY);
+ break;
+ }
+
+ /* Optionally display the sel set */
+ if (debug) {
+ PIX *pix1;
+ char buf[32];
+ lept_mkdir("/lept/sels");
+ pix1 = selaDisplayInPix(sela2, 35, 3, 15, 4);
+ snprintf(buf, sizeof(buf), "/tmp/lept/sels/set%d.png", index);
+ pixWrite(buf, pix1, IFF_PNG);
+ pixDisplay(pix1, 100, 100);
+ pixDestroy(&pix1);
+ }
+
+ selaDestroy(&sela1);
+ return sela2;
+}
diff --git a/leptonica/src/checkerboard.c b/leptonica/src/checkerboard.c
new file mode 100644
index 00000000..4807d7f2
--- /dev/null
+++ b/leptonica/src/checkerboard.c
@@ -0,0 +1,316 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*
+ * \file checkerboard.c
+ * <pre>
+ *
+ * Find the checker corners where 4 squares come together
+ * PIX *pixFindCheckerboardCorners()
+ *
+ * Generate the hit-miss sels
+ * static SELA *makeCheckerboardCornerSela()
+ * static PIXA *makeCheckerboardCornerPixa()
+ *
+ * The functions in this file locate the corners where four squares
+ * in a checkerboard come together. With a perfectly aligned checkerboard,
+ * the solution is trivial: take the union of two hit-miss transforms (HMTs),
+ * each having a simple diagonal structuring element (sel). The two
+ * sels can be generated from strings such as these, using
+ * selCreateFromString():
+ *
+ * static const char *str1 = "o x"
+ * " "
+ * " "
+ * " C "
+ * " "
+ * " "
+ * "x o";
+ * static const char *str2 = "x o"
+ * " "
+ * " "
+ * " C "
+ * " "
+ * " "
+ * "o x";
+ *
+ * A more interesting problem is to consider the checkerboard viewed from
+ * some arbitrary angle and orientation from the normal. The method
+ * developed here works for a camera located within a cone with an opening
+ * half-angle of about 45 degrees, and with its axis along the normal
+ * to the checkerboard.
+ *
+ * See prog/checkerboard_reg.c for usage.
+ *
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Static helpers */
+static SELA *makeCheckerboardCornerSela(l_int32 size, l_int32 dilation,
+ l_int32 nsels, PIXA *pixadb);
+static PIXA *makeCheckerboardCornerPixa(l_int32 size, l_int32 dilation,
+ l_int32 nsels);
+
+static const char selnames[64] = "s_diag1 s_diag2 s_cross1 s_cross2";
+
+/*!
+ * \brief pixFindCheckerboardCorner()
+ *
+ * \param[in] pixs of checkerboard
+ * \param[in] size size of HMT sel; >= 7, typ. 15; 0 for default
+ * \param[in] dilation size of hit and miss squares; typ. 1 or 3; max 5
+ * \param[in] nsels number to use (either 2 or 4)
+ * \param[out] ppix_corners [optional] 1 bpp pix giving corner locations
+ * \param[out] ppta_corners [optional] pta giving corner locations
+ * \param[in] pixadb [optional] pass in pre-allocated
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Use %nsels = 4 if the checkerboard may be rotated by more
+ * than 20 deg.
+ * (2) The values of %size and %dilation that can be used depend on
+ * the square sizes. Nominal values here are for squares of
+ * size 30 to 50. In general, because of the viewing angle
+ * of the camera, the "squares" will appear approximately
+ * as a rotated rectangle.
+ * (3) The outputs pix_corners and pta_corners are optional.
+ * </pre>
+ */
+l_ok
+pixFindCheckerboardCorners(PIX *pixs,
+ l_int32 size,
+ l_int32 dilation,
+ l_int32 nsels,
+ PIX **ppix_corners,
+ PTA **ppta_corners,
+ PIXA *pixadb)
+{
+BOXA *boxa1;
+PIX *pix1, *pix2, *pix3;
+PTA *pta1;
+SEL *sel;
+SELA *sela;
+
+ PROCNAME("pixFindCheckerboardCorners");
+
+ if (ppix_corners) *ppix_corners = NULL;
+ if (ppta_corners) *ppta_corners = NULL;
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+ if (size <= 0) size = 7;
+ if (size < 7)
+ return ERROR_INT("size too small", procName, 1);
+ if (dilation < 1 || dilation > 5)
+ return ERROR_INT("dilation not in [1 ...5]", procName, 1);
+ if (nsels != 2 && nsels != 4)
+ return ERROR_INT("nsels not 2 or 4", procName, 1);
+
+ /* Generate the hit-miss sels for finding corners */
+ sela = makeCheckerboardCornerSela(size, dilation, nsels, pixadb);
+ if (!sela)
+ return ERROR_INT("sela not made", procName, 1);
+ if (pixadb) {
+ pix1 = selaDisplayInPix(sela, 15, 3, 15, 2);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ }
+
+ /* Do the hit-miss transform to find corner locations */
+ pix1 = pixUnionOfMorphOps(pixs, sela, L_MORPH_HMT);
+ if (pixadb) pixaAddPix(pixadb, pix1, L_CLONE);
+ selaDestroy(&sela);
+
+ /* Remove large noise c.c. */
+ pix2 = pixSelectBySize(pix1, size, size, 8, L_SELECT_IF_BOTH,
+ L_SELECT_IF_LTE, NULL);
+ if (pixadb) pixaAddPix(pixadb, pix2, L_CLONE);
+
+ /* Thin remaining c.c. */
+ pix3 = pixThinConnected(pix2, L_THIN_FG, 8, 0);
+ if (pixadb) pixaAddPix(pixadb, pix3, L_CLONE);
+
+ /* Extract the location of the center of each component */
+ boxa1 = pixConnCompBB(pix3, 8);
+ pta1 = boxaExtractCorners(boxa1, L_BOX_CENTER);
+ boxaDestroy(&boxa1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ if (pixadb) { /* show the result as colored plus signs on the input */
+ sel = selMakePlusSign(15, 2);
+ pix1 = pixDisplaySelectedPixels(pixs, pix3, sel, 0xff000000);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ selDestroy(&sel);
+ }
+
+ if (ppix_corners)
+ *ppix_corners = pix3;
+ else
+ pixDestroy(&pix3);
+ if (ppta_corners)
+ *ppta_corners = pta1;
+ else
+ ptaDestroy(&pta1);
+ return 0;
+}
+
+
+/*!
+ * \brief makeCheckerboardCornerSela()
+ *
+ * \param[in] size size of HMT sel; >= 7, typ. 15; 0 for default
+ * \param[in] dilation size of hit and miss squares; typ. 1 or 3; max 5
+ * \param[in] nsels number to use (either 2 or 4)
+ * \param[in] pixadb [optional] pass in pre-allocated
+ * \return sela hit-miss sels for finding corners, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Use 4 sels if the checkerboard may be rotated by more than 20 deg.
+ * </pre>
+ */
+static SELA *
+makeCheckerboardCornerSela(l_int32 size,
+ l_int32 dilation,
+ l_int32 nsels,
+ PIXA *pixadb)
+{
+PIX *pix1;
+PIXA *pixa1;
+SARRAY *sa;
+SELA *sela;
+
+ PROCNAME("makeCheckerboardCornerSela");
+
+ if (size <= 0) size = 7;
+ if (size < 7)
+ return (SELA *)ERROR_PTR("size too small", procName, NULL);
+ if (dilation < 1 || dilation > 5)
+ return (SELA *)ERROR_PTR("dilation not in [1 ...5]", procName, NULL);
+ if (nsels != 2 && nsels != 4)
+ return (SELA *)ERROR_PTR("nsels not 2 or 4", procName, NULL);
+
+ if ((pixa1 = makeCheckerboardCornerPixa(size, dilation, nsels)) == NULL)
+ return (SELA *)ERROR_PTR("pixa for sels not made", procName, NULL);
+ if (pixadb) {
+ pix1 = pixaDisplayTiledInColumns(pixa1, 4, 8.0, 15, 2);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ }
+ sa = sarrayCreateWordsFromString(selnames);
+ sela = selaCreateFromColorPixa(pixa1, sa);
+ pixaDestroy(&pixa1);
+ sarrayDestroy(&sa);
+ if (!sela)
+ return (SELA *)ERROR_PTR("sela not made", procName, NULL);
+ return sela;
+}
+
+
+/*!
+ * \brief makeCheckerboardCornerPixa()
+ *
+ * \param[in] size size of HMT sel; >= 7, typ. 15; 0 for default
+ * \param[in] dilation size of hit and miss squares; typ. 1 or 3; max 5
+ * \param[in] nsels number to use (either 2 or 4)
+ * \return pixa representing hit-miss sels for finding corners, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Each pix can be used to generate a hit-miss sel, using the
+ * function selCreateFromColorPix(). See that function for the
+ * use of color and gray pixels to encode the hits, misses and
+ * center in the structuring element.
+ * </pre>
+ */
+static PIXA *
+makeCheckerboardCornerPixa(l_int32 size,
+ l_int32 dilation,
+ l_int32 nsels)
+{
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa1;
+
+ pixa1 = pixaCreate(4);
+
+ /* Represent diagonal neg slope hits and pos slope misses */
+ pix1 = pixCreate(size, size, 32);
+ pixSetAll(pix1);
+ pix2 = pixCreate(size, size, 1); /* slope -1 line (2 pixel) mask */
+ pixSetPixel(pix2, 1, 1, 1); /* UL corner */
+ pixSetPixel(pix2, size - 2, size - 2, 1); /* LR corner */
+ if (dilation > 1)
+ pixDilateBrick(pix2, pix2, dilation, dilation); /* dilate each pixel */
+ pixSetMasked(pix1, pix2, 0x00ff0000); /* green hit */
+ pix3 = pixRotate90(pix2, 1); /* slope +1 line (2 pixel) mask */
+ pixSetMasked(pix1, pix3, 0xff000000); /* red miss */
+ pixSetRGBPixel(pix1, size / 2, size / 2, 128, 128, 128); /* gray center */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+
+ /* Represent diagonal pos slope hits and neg slope misses */
+ pix1 = pixCreate(size, size, 32);
+ pixSetAll(pix1);
+ pixSetMasked(pix1, pix2, 0xff000000); /* red hit */
+ pixSetMasked(pix1, pix3, 0x00ff0000); /* green miss */
+ pixSetRGBPixel(pix1, size / 2, size / 2, 128, 128, 128); /* gray center */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ if (nsels == 2)
+ return pixa1;
+
+ /* Represent cross: vertical hits and horizontal misses */
+ pix1 = pixCreate(size, size, 32);
+ pixSetAll(pix1);
+ pix2 = pixCreate(size, size, 1); /* vertical line (2 pixel) mask */
+ pixSetPixel(pix2, size / 2, 1, 1);
+ pixSetPixel(pix2, size / 2, size - 2, 1);
+ if (dilation > 1)
+ pixDilateBrick(pix2, pix2, dilation, dilation); /* dilate each pixel */
+ pixSetMasked(pix1, pix2, 0x00ff0000); /* green hit */
+ pix3 = pixRotate90(pix2, 1); /* horizontal line (2 pixel) mask */
+ pixSetMasked(pix1, pix3, 0xff000000); /* red miss */
+ pixSetRGBPixel(pix1, size / 2, size / 2, 128, 128, 128); /* gray center */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+
+ /* Represent cross: horizontal hits and vertical misses */
+ pix1 = pixCreate(size, size, 32);
+ pixSetAll(pix1);
+ pixSetMasked(pix1, pix3, 0x00ff0000); /* green hit */
+ pixSetMasked(pix1, pix2, 0xff000000); /* red miss */
+ pixSetRGBPixel(pix1, size / 2, size / 2, 128, 128, 128); /* gray center */
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ return pixa1;
+}
+
diff --git a/leptonica/src/classapp.c b/leptonica/src/classapp.c
new file mode 100644
index 00000000..5a53144c
--- /dev/null
+++ b/leptonica/src/classapp.c
@@ -0,0 +1,1050 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file classapp.c
+ * <pre>
+ *
+ * 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()
+ *
+ * Extract word and character bounding boxes
+ * l_int32 pixFindWordAndCharacterBoxes()
+ *
+ * Use word bounding boxes to compare page images
+ * NUMAA *boxaExtractSortedPattern()
+ * l_int32 numaaCompareImagesByBoxes()
+ * static l_int32 testLineAlignmentX()
+ * static l_int32 countAlignedMatches()
+ * static void printRowIndices()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+#define L_BUF_SIZE 512 /*!< size of filename buffer */
+static const l_int32 JB_WORDS_MIN_WIDTH = 5; /*!< min. word width in pixels */
+static const l_int32 JB_WORDS_MIN_HEIGHT = 3; /*!< min. word height in 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief jbCorrelation()
+ *
+ * \param[in] dirin directory of input images
+ * \param[in] thresh typically ~0.8
+ * \param[in] weight typically ~0.6
+ * \param[in] components JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS
+ * \param[in] rootname for output files
+ * \param[in] firstpage 0-based
+ * \param[in] npages use 0 for all pages in dirin
+ * \param[in] renderflag 1 to render from templates; 0 to skip
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+l_ok
+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)
+ lept_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);
+ lept_stderr("filename: %s\n", filename);
+ pixWrite(filename, pix, IFF_PNG);
+ pixDestroy(&pix);
+ }
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&safiles);
+ jbClasserDestroy(&classer);
+ jbDataDestroy(&data);
+ return 0;
+}
+
+
+/*!
+ * \brief jbRankHaus()
+ *
+ * \param[in] dirin directory of input images
+ * \param[in] size of Sel used for dilation; typ. 2
+ * \param[in] rank rank value of match; typ. 0.97
+ * \param[in] components JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS
+ * \param[in] rootname for output files
+ * \param[in] firstpage 0-based
+ * \param[in] npages use 0 for all pages in dirin
+ * \param[in] renderflag 1 to render from templates; 0 to skip
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See prog/jbrankhaus for generating more output (e.g.,
+ * for debugging)
+ * </pre>
+ */
+l_ok
+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)
+ lept_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);
+ lept_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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief jbWordsInTextlines()
+ *
+ * \param[in] dirin directory of input pages
+ * \param[in] reduction 1 for full res; 2 for half-res
+ * \param[in] maxwidth of word mask components, to be kept
+ * \param[in] maxheight of word mask components, to be kept
+ * \param[in] thresh on correlation; 0.80 is reasonable
+ * \param[in] weight for handling thick text; 0.6 is reasonable
+ * \param[out] pnatl numa with textline index for each component
+ * \param[in] firstpage 0-based
+ * \param[in] npages use 0 for all pages in dirin
+ * \return classer for the set of pages
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a high-level function. See prog/jbwords for example
+ * of usage.
+ * (2) Typically, use input of 75 - 150 ppi for finding words.
+ * </pre>
+ */
+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 *pix1, *pix2;
+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 ((pix1 = pixRead(fname)) == NULL) {
+ L_WARNING("image file %d not read\n", procName, i);
+ continue;
+ }
+ if (reduction == 1)
+ pix2 = pixClone(pix1);
+ else /* reduction == 2 */
+ pix2 = pixReduceRankBinaryCascade(pix1, 1, 0, 0, 0);
+ pixGetWordsInTextlines(pix2, JB_WORDS_MIN_WIDTH,
+ JB_WORDS_MIN_HEIGHT, maxwidth, maxheight,
+ &boxa, &pixa, &nai);
+ pixGetDimensions(pix2, &w, &h, NULL);
+ classer->w = w;
+ classer->h = h;
+ jbAddPageComponents(classer, pix2, boxa, pixa);
+ numaJoin(natl, nai, 0, -1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ numaDestroy(&nai);
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&safiles);
+ return classer;
+}
+
+
+/*!
+ * \brief pixGetWordsInTextlines()
+ *
+ * \param[in] pixs 1 bpp, typ. 75 - 150 ppi
+ * \param[in] minwidth of saved components; smaller are discarded
+ * \param[in] minheight of saved components; smaller are discarded
+ * \param[in] maxwidth of saved components; larger are discarded
+ * \param[in] maxheight of saved components; larger are discarded
+ * \param[out] pboxad word boxes sorted in textline line order
+ * \param[out] ppixad word images sorted in textline line order
+ * \param[out] pnai index of textline for each word
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The input should be at a resolution of between 75 and 150 ppi.
+ * (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:
+ * ~ 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.
+ * </pre>
+ */
+l_ok
+pixGetWordsInTextlines(PIX *pixs,
+ l_int32 minwidth,
+ l_int32 minheight,
+ l_int32 maxwidth,
+ l_int32 maxheight,
+ BOXA **pboxad,
+ PIXA **ppixad,
+ NUMA **pnai)
+{
+BOXA *boxa1, *boxad;
+BOXAA *baa;
+NUMA *nai;
+NUMAA *naa;
+PIXA *pixa1, *pixad;
+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);
+
+ /* Get the bounding boxes of the words from the word mask. */
+ pixWordBoxesByDilation(pixs, minwidth, minheight, maxwidth, maxheight,
+ &boxa1, NULL, NULL);
+
+ /* Generate a pixa of the word images */
+ pixa1 = pixaCreateFromBoxa(pixs, boxa1, 0, 0, NULL);
+
+ /* 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;
+
+ pixaDestroy(&pixa1);
+ boxaDestroy(&boxa1);
+ boxaaDestroy(&baa);
+ pixaaDestroy(&paa);
+ numaaDestroy(&naa);
+ return 0;
+}
+
+
+/*!
+ * \brief pixGetWordBoxesInTextlines()
+ *
+ * \param[in] pixs 1 bpp, typ. 75 - 150 ppi
+ * \param[in] minwidth of saved components; smaller are discarded
+ * \param[in] minheight of saved components; smaller are discarded
+ * \param[in] maxwidth of saved components; larger are discarded
+ * \param[in] maxheight of saved components; larger are discarded
+ * \param[out] pboxad word boxes sorted in textline line order
+ * \param[out] pnai [optional] index of textline for each word
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The input should be at a resolution of between 75 and 150 ppi.
+ * (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.
+ * </pre>
+ */
+l_ok
+pixGetWordBoxesInTextlines(PIX *pixs,
+ l_int32 minwidth,
+ l_int32 minheight,
+ l_int32 maxwidth,
+ l_int32 maxheight,
+ BOXA **pboxad,
+ NUMA **pnai)
+{
+BOXA *boxa1;
+BOXAA *baa;
+NUMA *nai;
+
+ 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);
+
+ /* Get the bounding boxes of the words from the word mask. */
+ pixWordBoxesByDilation(pixs, minwidth, minheight, maxwidth, maxheight,
+ &boxa1, NULL, 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);
+ boxaDestroy(&boxa1);
+ boxaaDestroy(&baa);
+ return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ * Extract word and character bounding boxes *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixFindWordAndCharacterBoxes()
+ *
+ * \param[in] pixs 2, 4, 8 or 32 bpp; colormap OK; typ. 300 ppi
+ * \param[in] boxs [optional] region to select in pixs
+ * \param[in] thresh binarization threshold (typ. 100 - 150)
+ * \param[out] pboxaw return the word boxes
+ * \param[out] pboxaac return the character boxes
+ * \param[in] debugdir [optional] for debug images; use NULL to skip
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %boxs == NULL, the entire input image is used.
+ * (2) Having an input pix that is not 1bpp is necessary to reduce
+ * touching characters by using a low binarization threshold.
+ * Suggested thresholds are between 100 and 150.
+ * (3) The coordinates in the output boxes are global, with respect
+ * to the input image.
+ * </pre>
+ */
+l_ok
+pixFindWordAndCharacterBoxes(PIX *pixs,
+ BOX *boxs,
+ l_int32 thresh,
+ BOXA **pboxaw,
+ BOXAA **pboxaac,
+ const char *debugdir)
+{
+char *debugfile, *subdir;
+l_int32 i, xs, ys, xb, yb, nb, loc;
+l_float32 scalefact;
+BOX *box1, *box2;
+BOXA *boxa1, *boxa1a, *boxa2, *boxa3, *boxa4, *boxa5, *boxaw;
+BOXAA *boxaac;
+PIX *pix1, *pix2, *pix3, *pix3a, *pix4, *pix5;
+
+ PROCNAME("pixFindWordAndCharacterBoxes");
+
+ if (pboxaw) *pboxaw = NULL;
+ if (pboxaac) *pboxaac = NULL;
+ if (!pboxaw || !pboxaac)
+ return ERROR_INT("&boxaw and &boxaac not defined", procName, 1);
+ if (!pixs || pixGetDepth(pixs) == 1)
+ return ERROR_INT("pixs not defined or 1 bpp", procName, 1);
+ if (thresh > 150)
+ L_WARNING("threshold is %d; may be too high\n", procName, thresh);
+
+ if (boxs) {
+ if ((pix1 = pixClipRectangle(pixs, boxs, NULL)) == NULL)
+ return ERROR_INT("pix1 not made", procName, 1);
+ boxGetGeometry(boxs, &xs, &ys, NULL, NULL);
+ } else {
+ pix1 = pixClone(pixs);
+ xs = ys = 0;
+ }
+
+ /* Convert pix1 to 8 bpp gray if necessary */
+ pix2 = pixConvertTo8(pix1, FALSE);
+
+ /* To find the words and letters, work with 1 bpp images and use
+ * a low threshold to reduce the number of touching characters. */
+ pix3 = pixConvertTo1(pix2, thresh);
+
+ /* Work at about 120 ppi to find the word bounding boxes. */
+ pix3a = pixScaleToResolution(pix3, 120.0, 300.0, &scalefact);
+
+ /* First find the words, removing the very small things like
+ * dots over the 'i' that weren't included in word boxes. */
+ pixGetWordBoxesInTextlines(pix3a, 1, 4, 150, 40, &boxa1a, NULL);
+ boxa1 = boxaTransform(boxa1a, 0, 0, 1.0 / scalefact, 1.0 / scalefact);
+ if (debugdir) {
+ loc = 0;
+ subdir = stringReplaceSubstr(debugdir, "/tmp/", "", &loc, NULL);
+ lept_mkdir(subdir);
+ LEPT_FREE(subdir);
+ pix4 = pixConvertTo32(pix2);
+ pixRenderBoxaArb(pix4, boxa1, 2, 255, 0, 0);
+ debugfile = stringJoin(debugdir, "/words.png");
+ pixWrite(debugfile, pix4, IFF_PNG);
+ pixDestroy(&pix4);
+ LEPT_FREE(debugfile);
+ }
+
+ /* Now find the letters at 300 ppi */
+ nb = boxaGetCount(boxa1);
+ boxaw = boxaCreate(nb);
+ boxaac = boxaaCreate(nb);
+ *pboxaw = boxaw;
+ *pboxaac = boxaac;
+ for (i = 0; i < nb; i++) {
+ box1 = boxaGetBox(boxa1, i, L_COPY);
+ boxGetGeometry(box1, &xb, &yb, NULL, NULL);
+ pix4 = pixClipRectangle(pix3, box1, NULL);
+ /* Join detached parts of characters vertically */
+ pix5 = pixMorphSequence(pix4, "c1.10", 0);
+ /* The connected components should mostly be characters */
+ boxa2 = pixConnCompBB(pix5, 4);
+ /* Remove very small pieces */
+ boxa3 = boxaSelectBySize(boxa2, 2, 5, L_SELECT_IF_BOTH,
+ L_SELECT_IF_GTE, NULL);
+ /* Order left to right */
+ boxa4 = boxaSort(boxa3, L_SORT_BY_X, L_SORT_INCREASING, NULL);
+ /* Express locations with reference to the full input image */
+ boxa5 = boxaTransform(boxa4, xs + xb, ys + yb, 1.0, 1.0);
+ box2 = boxTransform(box1, xs, ys, 1.0, 1.0);
+
+ /* Ignore any boxa with no boxes after size filtering */
+ if (boxaGetCount(boxa5) > 0) {
+ boxaAddBox(boxaw, box2, L_INSERT);
+ boxaaAddBoxa(boxaac, boxa5, L_INSERT);
+ } else {
+ boxDestroy(&box2);
+ boxaDestroy(&boxa5);
+ }
+ boxDestroy(&box1);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+ boxaDestroy(&boxa4);
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix3a);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa1a);
+ if (debugdir) {
+ pix4 = pixConvertTo32(pixs);
+ boxa2 = boxaaFlattenToBoxa(boxaac, NULL, L_COPY);
+ pixRenderBoxaArb(pix4, boxa2, 2, 255, 0, 0);
+ boxa3 = boxaAdjustSides(boxaw, -2, 2, -2, 2);
+ pixRenderBoxaArb(pix4, boxa3, 2, 0, 255, 0);
+ debugfile = stringJoin(debugdir, "/chars.png");
+ pixWrite(debugfile, pix4, IFF_PNG);
+ pixDestroy(&pix4);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+ LEPT_FREE(debugfile);
+ }
+ return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ * Use word bounding boxes to compare page images *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief boxaExtractSortedPattern()
+ *
+ * \param[in] boxa typ. of word bounding boxes, in textline order
+ * \param[in] na index of textline for each box in boxa
+ * \return naa NUMAA, where each numa represents one textline,
+ * or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaaCompareImagesByBoxes()
+ *
+ * \param[in] naa1 for image 1, formatted by boxaExtractSortedPattern()
+ * \param[in] naa2 for image 2, formatted by boxaExtractSortedPattern()
+ * \param[in] nperline number of box regions to be used in each textline
+ * \param[in] nreq number of complete row matches required
+ * \param[in] maxshiftx max allowed x shift between two patterns, in pixels
+ * \param[in] maxshifty max allowed y shift between two patterns, in pixels
+ * \param[in] delx max allowed difference in x data, after alignment
+ * \param[in] dely max allowed difference in y data, after alignment
+ * \param[out] psame 1 if %nreq row matches are found; 0 otherwise
+ * \param[in] debugflag 1 for debug output
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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));
+ if (!line1 || !line2 || !yloc1 || !yloc2 || !xleft1 || !xleft2)
+ return ERROR_INT("callof failure for an array", procName, 1);
+ 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;
+}
+
+
+/*
+ * \brief countAlignedMatches()
+ *
+ * \param[in] nai1, nai2 numas of row pairs for matches
+ * \param[in] nasx, nasy numas of x and y shifts for the matches
+ * \param[in] n1, n2 number of rows in images 1 and 2
+ * \param[in] delx, dely allowed difference in shifts of the match,
+ * compared to the reference match
+ * \param[in] nre1 number of required aligned matches
+ * \param[out] psame return 1 if %nreq row matches are found;
+ * 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static l_ok
+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)); /* watch rows */
+ index2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32));
+ if (!index1 || !index2)
+ return ERROR_INT("calloc fail for array", procName, 1);
+ 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;
+
+ lept_stderr("Index1: ");
+ for (i = 0; i < n1; i++) {
+ if (i && (i % 20 == 0))
+ lept_stderr("\n ");
+ lept_stderr("%3d", index1[i]);
+ }
+ lept_stderr("\n");
+
+ lept_stderr("Index2: ");
+ for (i = 0; i < n2; i++) {
+ if (i && (i % 20 == 0))
+ lept_stderr("\n ");
+ lept_stderr("%3d", index2[i]);
+ }
+ lept_stderr("\n");
+ return;
+}
diff --git a/leptonica/src/colorcontent.c b/leptonica/src/colorcontent.c
new file mode 100644
index 00000000..d2da034c
--- /dev/null
+++ b/leptonica/src/colorcontent.c
@@ -0,0 +1,2051 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file colorcontent.c
+ * <pre>
+ *
+ * Build 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()
+ *
+ * Find 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()
+ *
+ * Find the fraction of pixels with "color" that are not close to black
+ * l_int32 pixColorFraction()
+ *
+ * Do a linear TRC to map colors so that the three input reference
+ * values go to white. These three numbers are typically the median
+ * or average background values.
+ * PIX *pixColorShiftWhitePoint()
+ *
+ * Generate a mask over pixels that have sufficient color and
+ * are not too close to gray pixels.
+ * PIX *pixMaskOverColorPixels()
+ *
+ * Generate a mask over dark pixels with little color
+ * PIX *pixMaskOverGrayPixels()
+ *
+ * Generate mask over pixels within a prescribed cube in RGB space
+ * PIX *pixMaskOverColorRange()
+ *
+ * Determine if there are significant color regions that are
+ * not background in a page image
+ * l_int32 pixFindColorRegions()
+ *
+ * Find the number of perceptually significant gray intensities
+ * in a grayscale image.
+ * l_int32 pixNumSignificantGrayColors()
+ *
+ * Identify images where color quantization will cause posterization
+ * due to the existence of many colors in low-gradient regions.
+ * l_int32 pixColorsForQuantization()
+ *
+ * Find the number of unique colors in an image
+ * l_int32 pixNumColors()
+ *
+ * Lossless conversion of RGB image to colormapped
+ * PIX *pixConvertRGBToCmapLossless()
+ *
+ * Find the most "populated" colors in the image (and quantize)
+ * l_int32 pixGetMostPopulatedColors()
+ * PIX *pixSimpleColorQuantize()
+ *
+ * Construct 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/ <255>
+ * (b) r = 0, g = 127, b = 255 {191} /128/ <255>
+ * 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 consider three of the methods in (c):
+ * L_INTERMED_DIFF
+ * Define the color magnitude as the intermediate value of the
+ * three differences between the three components.
+ * For (a) and (b) above, this value is in /../.
+ * L_AVE_MAX_DIFF_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_DIFF
+ * Define the color magnitude as the maximum value of the
+ * three differences between the three components.
+ * For (a) and (b) above, this value is in <..>.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* ----------------------------------------------------------------------- *
+ * Build 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. *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixColorContent()
+ *
+ * \param[in] pixs 32 bpp rgb or 8 bpp colormapped
+ * \param[in] rref reference value for red component
+ * \param[in] gref reference value for green component
+ * \param[in] bref reference value for blue component
+ * \param[in] mingray min gray value for which color is measured
+ * \param[out] ppixr [optional] 8 bpp red 'content'
+ * \param[out] ppixg [optional] 8 bpp green 'content'
+ * \param[out] ppixb [optional] 8 bpp blue 'content'
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 (rref, gref and bref) can be thought
+ * of in two ways:
+ * (a) as the values in the image corresponding to white,
+ * to compensate for an unbalanced color white point.
+ * (b) the median or mean values of the background color of
+ * a scan.
+ * The gamma TRC transformation is used to modify all colors so that
+ * these reference values become white.
+ * These three numbers must either be all 0 or all non-zero.
+ * To skip the TRC transform, 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.
+ * </pre>
+ */
+l_ok
+pixColorContent(PIX *pixs,
+ l_int32 rref,
+ l_int32 gref,
+ l_int32 bref,
+ l_int32 mingray,
+ PIX **ppixr,
+ PIX **ppixg,
+ PIX **ppixb)
+{
+l_int32 w, h, i, j, wpl1, wplr, wplg, wplb;
+l_int32 rval, gval, bval, rgdiff, rbdiff, gbdiff, maxval, colorval;
+l_uint32 pixel;
+l_uint32 *data1, *datar, *datag, *datab, *line1, *liner, *lineg, *lineb;
+PIX *pix1, *pixr, *pixg, *pixb;
+
+ 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;
+ if (mingray > 255)
+ return ERROR_INT("mingray > 255", procName, 1);
+
+ /* Do the optional linear color map; this checks the ref vals */
+ if ((pix1 = pixColorShiftWhitePoint(pixs, rref, gref, bref)) == NULL)
+ return ERROR_INT("pix1 not returned", procName, 1);
+
+ pixr = pixg = pixb = NULL;
+ pixGetDimensions(pix1, &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;
+ }
+
+ data1 = pixGetData(pix1);
+ wpl1 = pixGetWpl(pix1);
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl1;
+ 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 = line1[j];
+ extractRGBValues(pixel, &rval, &gval, &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);
+ }
+ }
+ }
+
+ pixDestroy(&pix1);
+ return 0;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Find the 'amount' of color in an image, on a per-pixel basis, *
+ * as a measure of the difference of the pixel color from gray. *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixColorMagnitude()
+ *
+ * \param[in] pixs 32 bpp rgb or 8 bpp colormapped
+ * \param[in] rref reference value for red component
+ * \param[in] gref reference value for green component
+ * \param[in] bref reference value for blue component
+ * \param[in] type chooses the method for calculating the color magnitude:
+ * L_INTERMED_DIFF, L_AVE_MAX_DIFF_2, L_MAX_DIFF
+ * \return pixd 8 bpp, amount of color in each source pixel,
+ * or NULL on error
+ *
+ * <pre>
+ * 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 minimum value of these two differences. This is
+ * the intermediate value of the three distances between
+ * component values.
+ * (b) 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.
+ * (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 minimum distance of B from these two:
+ * min(|B - R|, |B - G|).
+ * (b) The average distance of B from these two:
+ * (|B - R| + |B - G|) / 2
+ * (c) The maximum distance of B from these two:
+ * max(|B - R|, |B - G|)
+ * (3) This example can be visualized graphically. Put the R,G and B
+ * component values on a line; e.g.,
+ * G...R...........B
+ * (a) B - R
+ * (b) B - (R + G) / 2
+ * (c) B - G
+ * (4) The three methods for choosing the color magnitude from
+ * the components are selected with these flags:
+ * (a) L_INTERMED_DIFF
+ * (b) L_AVE_MAX_DIFF_2
+ * (c) L_MAX_DIFF
+ * (5) The three numbers (rref, gref and bref) can be thought
+ * of in two ways:
+ * (a) as the values in the image corresponding to white,
+ * to compensate for an unbalanced color white point.
+ * (b) the median or mean values of the background color of
+ * a scan.
+ * The gamma TRC transformation is used to modify all colors so that
+ * these reference values become white.
+ * These three numbers must either be all 0 or all non-zero.
+ * To skip the TRC transform, set them all to 0.
+ * </pre>
+ */
+PIX *
+pixColorMagnitude(PIX *pixs,
+ l_int32 rref,
+ l_int32 gref,
+ l_int32 bref,
+ l_int32 type)
+{
+l_int32 w, h, i, j, wpl1, wpld;
+l_int32 rval, gval, bval, rdist, gdist, bdist, colorval;
+l_int32 rgdist, rbdist, gbdist, mindist, maxdist, minval, maxval;
+l_uint32 pixel;
+l_uint32 *data1, *datad, *line1, *lined;
+PIX *pix1, *pixd;
+
+ PROCNAME("pixColorMagnitude");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+ if (type != L_INTERMED_DIFF && type != L_AVE_MAX_DIFF_2 &&
+ type != L_MAX_DIFF)
+ return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+ /* Do the optional linear color map; this checks the ref vals */
+ if ((pix1 = pixColorShiftWhitePoint(pixs, rref, gref, bref)) == NULL)
+ return (PIX *)ERROR_PTR("pix1 not returned", procName, NULL);
+
+ pixGetDimensions(pix1, &w, &h, NULL);
+ pixd = pixCreate(w, h, 8);
+ datad = pixGetData(pixd);
+ wpld = pixGetWpl(pixd);
+ data1 = pixGetData(pix1);
+ wpl1 = pixGetWpl(pix1);
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl1;
+ lined = datad + i * wpld;
+ for (j = 0; j < w; j++) {
+ pixel = line1[j];
+ extractRGBValues(pixel, &rval, &gval, &bval);
+ if (type == L_INTERMED_DIFF) {
+ 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 if (type == L_AVE_MAX_DIFF_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 { /* 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);
+ }
+ }
+
+ pixDestroy(&pix1);
+ return pixd;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Find the fraction of pixels with "color" that are not close to black *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixColorFraction()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] darkthresh threshold near black; if the largest (lightest)
+ * component is below this, the pixel is not
+ * considered in the statistics; typ. 20
+ * \param[in] lightthresh threshold near white; if the smallest (darkest)
+ * component is above this, the pixel is not
+ * considered in the statistics; typ. 244
+ * \param[in] diffthresh thresh for the maximum difference between
+ * component values; below this the pixel is not
+ * considered to have sufficient color
+ * \param[in] factor subsampling factor
+ * \param[out] ppixfract fraction of pixels in intermediate
+ * brightness range that were considered
+ * for color content
+ * \param[out] pcolorfract fraction of pixels that meet the
+ * criterion for sufficient color; 0.0 on error
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Do a linear TRC to map colors so that the three input reference *
+ * values go to white. These three numbers are typically the median *
+ * or average background values. *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixColorShiftWhitePoint()
+ *
+ * \param[in] pixs 32 bpp rgb or 8 bpp colormapped
+ * \param[in] rref reference value for red component
+ * \param[in] gref reference value for green component
+ * \param[in] bref reference value for blue component
+ * \return pix2 32 bpp if OK, NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This returns a pix where the colors are linearly mapped to
+ * so that the components go to 255 at the input reference values.
+ * (2) These three numbers (rref, gref and bref) can be thought
+ * of in two ways:
+ * (a) as the values in the image corresponding to white,
+ * to compensate for an unbalanced color white point.
+ * (b) the median or mean values of the background color of
+ * an image.
+ * A linear (gamma = 1) TRC transformation is used.
+ * (3) Any existing colormap is removed and a 32 bpp rgb pix is returned.
+ * (4) No transformation is applied if any of the three numbers are <= 0.
+ * If any are < 0, or if some but not all are 0, a warning is given.
+ * </pre>
+ */
+PIX *
+pixColorShiftWhitePoint(PIX *pixs,
+ l_int32 rref,
+ l_int32 gref,
+ l_int32 bref)
+{
+l_int32 w, h, i, j, wpl1, wpl2, rval, gval, bval;
+l_int32 *rtab, *gtab, *btab;
+l_uint32 pixel;
+l_uint32 *data1, *data2, *line1, *line2;
+NUMA *nar, *nag, *nab;
+PIX *pix1, *pix2;
+PIXCMAP *cmap;
+
+ PROCNAME("pixColorShiftWhitePoint");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+ cmap = pixGetColormap(pixs);
+ if (!cmap && pixGetDepth(pixs) != 32)
+ return (PIX *)ERROR_PTR("pixs neither cmapped nor 32 bpp",
+ procName, NULL);
+ if (cmap)
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+ else
+ pix1 = pixClone(pixs);
+
+ if (!rref && !gref && !bref) /* all 0; no transform requested */
+ return pix1;
+
+ /* Some ref values are < 0, or some (but not all) are 0 */
+ if ((rref < 0 || gref < 0 || bref < 0) || (rref * gref * bref == 0)) {
+ L_WARNING("invalid set of ref values\n", procName);
+ return pix1;
+ }
+
+ /* All white point ref values > 0; do transformation */
+ pixGetDimensions(pix1, &w, &h, NULL);
+ pix2 = pixCreate(w, h, 32);
+ data1 = pixGetData(pix1);
+ wpl1 = pixGetWpl(pix1);
+ data2 = pixGetData(pix2);
+ wpl2 = pixGetWpl(pix2);
+ nar = numaGammaTRC(1.0, 0, rref);
+ rtab = numaGetIArray(nar);
+ nag = numaGammaTRC(1.0, 0, gref);
+ gtab = numaGetIArray(nag);
+ nab = numaGammaTRC(1.0, 0, bref);
+ btab = numaGetIArray(nab);
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl1;
+ line2 = data2 + i * wpl2;
+ for (j = 0; j < w; j++) {
+ pixel = line1[j];
+ extractRGBValues(pixel, &rval, &gval, &bval);
+ rval = rtab[rval];
+ gval = gtab[gval];
+ bval = btab[bval];
+ composeRGBPixel(rval, gval, bval, line2 + j);
+ }
+ }
+ numaDestroy(&nar);
+ numaDestroy(&nag);
+ numaDestroy(&nab);
+ LEPT_FREE(rtab);
+ LEPT_FREE(gtab);
+ LEPT_FREE(btab);
+ pixDestroy(&pix1);
+ return pix2;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Generate a mask over pixels that have sufficient color and *
+ * are not too close to gray pixels. *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixMaskOverColorPixels()
+ *
+ * \param[in] pixs 32 bpp rgb or 8 bpp colormapped
+ * \param[in] threshdiff threshold for minimum of the max difference
+ * between components
+ * \param[in] mindist min allowed distance from nearest non-color pixel
+ * \return pixd 1 bpp, mask over color pixels, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!pixc || pixGetDepth(pixc) != 32) {
+ pixDestroy(&pixc);
+ return (PIX *)ERROR_PTR("rgb pix not made", procName, NULL);
+ }
+
+ 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;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Generate a mask over dark pixels with little color *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixMaskOverGrayPixels()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] maxlimit only consider pixels with max component <= %maxlimit
+ * \param[in] satlimit only consider pixels with saturation <= %satlimit
+ * \return pixd (1 bpp), or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates a mask over rgb pixels that are gray (i.e.,
+ * have low saturation) and are not too bright. For example, if
+ * we know that the gray pixels in %pixs have saturation
+ * (max - min) less than 10, and brightness (max) less than 200,
+ * pixMaskOverGrayPixels(pixs, 220, 10)
+ * will generate a mask over the gray pixels. Other pixels that
+ * are not too dark and have a relatively large saturation will
+ * be little affected.
+ * (2) The algorithm is related to pixDarkenGray().
+ * </pre>
+ */
+PIX *
+pixMaskOverGrayPixels(PIX *pixs,
+ l_int32 maxlimit,
+ l_int32 satlimit)
+{
+l_int32 w, h, i, j, wpls, wpld;
+l_int32 rval, gval, bval, minrg, min, maxrg, max, sat;
+l_uint32 *datas, *datad, *lines, *lined;
+PIX *pixd;
+
+ PROCNAME("pixMaskOverGrayPixels");
+
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+ if (maxlimit < 0 || maxlimit > 255)
+ return (PIX *)ERROR_PTR("invalid maxlimit", procName, NULL);
+ if (satlimit < 1)
+ return (PIX *)ERROR_PTR("invalid satlimit", procName, NULL);
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+ if ((pixd = pixCreate(w, h, 1)) == 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++) {
+ extractRGBValues(lines[j], &rval, &gval, &bval);
+ minrg = L_MIN(rval, gval);
+ min = L_MIN(minrg, bval);
+ maxrg = L_MAX(rval, gval);
+ max = L_MAX(maxrg, bval);
+ sat = max - min;
+ if (max <= maxlimit && sat <= satlimit)
+ SET_DATA_BIT(lined, j);
+ }
+ }
+ return pixd;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Generate a mask over pixels that have RGB color components *
+ * within the prescribed range (a cube in RGB color space) *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixMaskOverColorRange()
+ *
+ * \param[in] pixs 32 bpp rgb or 8 bpp colormapped
+ * \param[in] rmin, rmax min and max allowed values for red component
+ * \param[in] gmin, gmax ditto for green
+ * \param[in] bmin, bmax ditto for blue
+ * \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;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Determine if there are significant color regions in a page image *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixFindColorRegions()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] pixm [optional] 1 bpp mask image
+ * \param[in] factor subsample factor; integer >= 1
+ * \param[in] lightthresh threshold for component average in lightest
+ * of 10 buckets; typ. 210; -1 for default
+ * \param[in] darkthresh threshold to eliminate dark pixels (e.g., text)
+ * from consideration; typ. 70; -1 for default.
+ * \param[in] mindiff minimum difference (b - r) and (g - r), used to
+ * find blue or green pixels; typ. 10; -1 for default
+ * \param[in] colordiff minimum difference in (max - min) component to
+ * qualify as a color pixel; typ. 90; -1 for default
+ * \param[in] edgefract fraction of image half-width and half-height
+ * for which color pixels are ignored; typ. 0.05.
+ * \param[out] pcolorfract fraction of 'color' pixels found
+ * \param[out] pcolormask1 [optional] mask over background color, if any
+ * \param[out] pcolormask2 [optional] filtered mask over background color
+ * \param[out] pixadb [optional] debug intermediate results
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function tries to determine if there is a significant
+ * color or darker region on a scanned page image, where part
+ * of the image is background that is either white or reddish.
+ * This also allows extraction of regions of colored pixels that
+ * have a smaller red component than blue or green components.
+ * (2) If %pixm exists, pixels under its fg are combined with
+ * dark pixels to make a mask of pixels not to be considered
+ * as color candidates.
+ * (3) There are four thresholds.
+ * * %lightthresh: compute the average value of each rgb pixel,
+ * and make 10 buckets by value. If the lightest bucket gray
+ * value is below %lightthresh, the image is not considered
+ * to have a light bg, and this returns 0.0 for %colorfract.
+ * * %darkthresh: ignore pixels darker than this (typ. fg text).
+ * We make a 1 bpp mask of these pixels, and then dilate it to
+ * remove all vestiges of fg from their vicinity.
+ * * %mindiff: consider pixels with either (b - r) or (g - r)
+ * being at least this value, as having color.
+ * * %colordiff: consider pixels where the (max - min) difference
+ * of the pixel components exceeds this value, as having color.
+ * (4) All components of color pixels that are touching the image
+ * border are removed. Additionally, all pixels within some
+ * normalized distance %edgefract from the image border can
+ * be removed. This insures that dark pixels near the edge
+ * of the image are not included.
+ * (5) This returns in %pcolorfract the fraction of pixels that have
+ * color and are not in the set consisting of an OR between
+ * %pixm and the dilated dark pixel mask.
+ * (6) No masks are returned unless light color pixels are found.
+ * If colorfract > 0.0 and %pcolormask1 is defined, this returns
+ * a 1 bpp mask with fg pixels over the color background.
+ * This mask may have some holes in it.
+ * (7) If colorfract > 0.0 and %pcolormask2 is defined, this returns
+ * a version of colormask1 where small holes have been filled.
+ * (8) To generate a boxa of rectangular regions from the overlap
+ * of components in the filtered mask:
+ * boxa1 = pixConnCompBB(colormask2, 8);
+ * boxa2 = boxaCombineOverlaps(boxa1, NULL);
+ * This is done here in debug mode.
+ * </pre>
+ */
+l_ok
+pixFindColorRegions(PIX *pixs,
+ PIX *pixm,
+ l_int32 factor,
+ l_int32 lightthresh,
+ l_int32 darkthresh,
+ l_int32 mindiff,
+ l_int32 colordiff,
+ l_float32 edgefract,
+ l_float32 *pcolorfract,
+ PIX **pcolormask1,
+ PIX **pcolormask2,
+ PIXA *pixadb)
+{
+l_int32 w, h, count, rval, gval, bval, aveval, proceed;
+l_float32 ratio;
+l_uint32 *carray;
+BOXA *boxa1, *boxa2;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pixm1, *pixm2, *pixm3;
+
+ PROCNAME("pixFindColorRegions");
+
+ if (pcolormask1) *pcolormask1 = NULL;
+ if (pcolormask2) *pcolormask2 = NULL;
+ if (!pcolorfract)
+ return ERROR_INT("&colorfract not defined", procName, 1);
+ *pcolorfract = 0.0;
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return ERROR_INT("pixs not defined or not 32 bpp", procName, 1);
+ if (factor < 1) factor = 1;
+ if (lightthresh < 0) lightthresh = 210; /* defaults */
+ if (darkthresh < 0) darkthresh = 70;
+ if (mindiff < 0) mindiff = 10;
+ if (colordiff < 0) colordiff = 90;
+ if (edgefract < 0.0 || edgefract > 1.0) edgefract = 0.05f;
+
+ /* Check if pixm covers most of the image. If so, just return. */
+ if (pixm) {
+ pixForegroundFraction(pixm, &ratio);
+ if (ratio > 0.7) {
+ if (pixadb) L_INFO("pixm has big fg: %f5.2\n", procName, ratio);
+ return 0;
+ }
+ }
+
+ /* Get the light background color. Use the average component value
+ * and select the lightest of 10 buckets. Require that it is
+ * reddish and, using lightthresh, not too dark. */
+ pixGetRankColorArray(pixs, 10, L_SELECT_AVERAGE, factor, &carray, NULL, 0);
+ if (!carray)
+ return ERROR_INT("rank color array not made", procName, 1);
+ extractRGBValues(carray[9], &rval, &gval, &bval);
+ if (pixadb) L_INFO("lightest background color: (r,g,b) = (%d,%d,%d)\n",
+ procName, rval, gval, bval);
+ proceed = TRUE;
+ if ((rval < bval - 2) || (rval < gval - 2)) {
+ if (pixadb) L_INFO("background not reddish\n", procName);
+ proceed = FALSE;
+ }
+ aveval = (rval + gval + bval) / 3;
+ if (aveval < lightthresh) {
+ if (pixadb) L_INFO("background too dark\n", procName);
+ proceed = FALSE;
+ }
+ if (pixadb) {
+ pix1 = pixDisplayColorArray(carray, 10, 120, 3, 6);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ }
+ LEPT_FREE(carray);
+ if (proceed == FALSE) return 0;
+
+ /* Make a mask pixm1 over the dark pixels in the image:
+ * convert to gray using the average of the components;
+ * threshold using darkthresh; do a small dilation;
+ * combine with pixm. */
+ pix1 = pixConvertRGBToGray(pixs, 0.33f, 0.34f, 0.33f);
+ if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
+ pixm1 = pixThresholdToBinary(pix1, darkthresh);
+ pixDilateBrick(pixm1, pixm1, 7, 7);
+ if (pixadb) pixaAddPix(pixadb, pixm1, L_COPY);
+ if (pixm) {
+ pixOr(pixm1, pixm1, pixm);
+ if (pixadb) pixaAddPix(pixadb, pixm1, L_COPY);
+ }
+ pixDestroy(&pix1);
+
+ /* Make masks over pixels that are bluish, or greenish, or
+ have a very large color saturation (max - min) value. */
+ pixm2 = pixConvertRGBToBinaryArb(pixs, -1.0, 0.0, 1.0, mindiff,
+ L_SELECT_IF_GTE); /* b - r */
+ if (pixadb) pixaAddPix(pixadb, pixm2, L_COPY);
+ pix1 = pixConvertRGBToBinaryArb(pixs, -1.0, 1.0, 0.0, mindiff,
+ L_SELECT_IF_GTE); /* g - r */
+ if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
+ pixOr(pixm2, pixm2, pix1);
+ pixDestroy(&pix1);
+ pix1 = pixConvertRGBToGrayMinMax(pixs, L_CHOOSE_MAXDIFF);
+ pix2 = pixThresholdToBinary(pix1, colordiff);
+ pixInvert(pix2, pix2);
+ if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
+ pixOr(pixm2, pixm2, pix2);
+ if (pixadb) pixaAddPix(pixadb, pixm2, L_COPY);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Subtract the dark pixels represented by pixm1.
+ * pixm2 now holds all the color pixels of interest */
+ pixSubtract(pixm2, pixm2, pixm1);
+ pixDestroy(&pixm1);
+ if (pixadb) pixaAddPix(pixadb, pixm2, L_COPY);
+
+ /* But we're not quite finished. Remove pixels from any component
+ * that is touching the image border. False color pixels can
+ * sometimes be found there if the image is much darker near
+ * the border, due to oxidation or reduced illumination. Also
+ * remove any pixels within the normalized fraction %distfract
+ * of the image border. */
+ pixm3 = pixRemoveBorderConnComps(pixm2, 8);
+ pixGetDimensions(pixm3, &w, &h, NULL);
+ pixDestroy(&pixm2);
+ if (edgefract > 0.0) {
+ pix2 = pixMakeSymmetricMask(w, h, edgefract, edgefract, L_USE_INNER);
+ pixAnd(pixm3, pixm3, pix2);
+ pixDestroy(&pix2);
+ }
+ if (pixadb) pixaAddPix(pixadb, pixm3, L_COPY);
+
+ /* Get the fraction of light color pixels */
+ pixCountPixels(pixm3, &count, NULL);
+ *pcolorfract = (l_float32)count / ((l_float32)(w) * h);
+ if (pixadb) {
+ if (count == 0)
+ L_INFO("no light color pixels found\n", procName);
+ else
+ L_INFO("fraction of light color pixels = %5.3f\n", procName,
+ *pcolorfract);
+ }
+
+ /* Debug: extract the color pixels from pixs */
+ if (pixadb && count > 0) {
+ /* Use pixm3 to extract the color pixels */
+ pix3 = pixCreateTemplate(pixs);
+ pixSetAll(pix3);
+ pixCombineMasked(pix3, pixs, pixm3);
+ pixaAddPix(pixadb, pix3, L_INSERT);
+
+ /* Use additional filtering to extract the color pixels */
+ pix3 = pixCloseSafeBrick(NULL, pixm3, 15, 15);
+ pixaAddPix(pixadb, pix3, L_INSERT);
+ pix5 = pixCreateTemplate(pixs);
+ pixSetAll(pix5);
+ pixCombineMasked(pix5, pixs, pix3);
+ pixaAddPix(pixadb, pix5, L_INSERT);
+
+ /* Get the combined bounding boxes of the mask components
+ * in pix3, and extract those pixels from pixs. */
+ boxa1 = pixConnCompBB(pix3, 8);
+ boxa2 = boxaCombineOverlaps(boxa1, NULL);
+ pix4 = pixCreateTemplate(pix3);
+ pixMaskBoxa(pix4, pix4, boxa2, L_SET_PIXELS);
+ pixaAddPix(pixadb, pix4, L_INSERT);
+ pix5 = pixCreateTemplate(pixs);
+ pixSetAll(pix5);
+ pixCombineMasked(pix5, pixs, pix4);
+ pixaAddPix(pixadb, pix5, L_INSERT);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ }
+ pixaAddPix(pixadb, pixs, L_COPY);
+
+ /* Optional colormask returns */
+ if (pcolormask2 && count > 0)
+ *pcolormask2 = pixCloseSafeBrick(NULL, pixm3, 15, 15);
+ if (pcolormask1 && count > 0)
+ *pcolormask1 = pixm3;
+ else
+ pixDestroy(&pixm3);
+ return 0;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Find the number of perceptually significant gray intensities *
+ * in a grayscale image. *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixNumSignificantGrayColors()
+ *
+ * \param[in] pixs 8 bpp gray
+ * \param[in] darkthresh dark threshold for minimum intensity to be
+ * considered; typ. 20
+ * \param[in] lightthresh threshold near white, for maximum intensity
+ * to be considered; typ. 236
+ * \param[in] minfract minimum fraction of all pixels to include a level
+ * as significant; typ. 0.0001; should be < 0.001
+ * \param[in] factor subsample factor; integer >= 1
+ * \param[out] pncolors number of significant colors; 0 on error
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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.0001f;
+ 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. *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixColorsForQuantization()
+ * \param[in] pixs 8 bpp gray or 32 bpp rgb; with or without colormap
+ * \param[in] thresh binary threshold on edge gradient; 0 for default
+ * \param[out] pncolors the number of colors found
+ * \param[out] piscolor [optional] 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
+ * \param[in] debug 1 to output masked image that is tested for colors;
+ * 0 otherwise
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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.0001f, 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;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Find the number of unique colors in an image *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixNumColors()
+ * \param[in] pixs 2, 4, 8, 32 bpp
+ * \param[in] factor subsampling factor; integer
+ * \param[out] pncolors the number of colors found in the pix
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This returns the 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 more efficiently find an 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 uses a hash set with %factor == 1.
+ * </pre>
+ */
+l_ok
+pixNumColors(PIX *pixs,
+ l_int32 factor,
+ l_int32 *pncolors)
+{
+l_int32 w, h, d, i, j, wpl, hashsize, sum, count, manycolors;
+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));
+ manycolors = 0;
+ for (i = 0; i < h && manycolors == 0; 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) {
+ manycolors = 1;
+ break;
+ }
+ }
+ }
+ }
+ LEPT_FREE(inta);
+
+ if (manycolors == 0) {
+ *pncolors = sum;
+ return 0;
+ }
+
+ /* More than 256 colors in RGB image; count all the pixels */
+ return pixCountRGBColorsByHash(pixs, pncolors);
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Lossless conversion of RGB image to colormapped *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixConvertRGBToCmapLossless()
+ * \param[in] pixs 32 bpp RGB
+ * \return pixd if num colors <= 256; null otherwise or on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If there are not more than 256 colors, this losslessly
+ * converts and RGB image to a colormapped one, with the
+ * smallest pixel depth required to hold all the colors.
+ * </pre>
+ */
+PIX *
+pixConvertRGBToCmapLossless(PIX *pixs)
+{
+l_int32 w, h, d, i, j, wpls, wpld, hashsize, hashval, ncolors, index;
+l_int32 rval, gval, bval, val;
+l_int32 *hasha1, *hasha2;
+l_uint32 pixel;
+l_uint32 *datas, *lines, *datad, *lined;
+PIX *pixd;
+PIXCMAP *cmap;
+
+ PROCNAME("pixConvertRGBToCmapLossless");
+
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+
+ pixNumColors(pixs, 1, &ncolors);
+ if (ncolors > 256) {
+ L_ERROR("too many colors found: %d\n", procName, ncolors);
+ return NULL;
+ }
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ if (ncolors <= 2)
+ d = 1;
+ else if (ncolors <= 4)
+ d = 2;
+ else if (ncolors <= 16)
+ d = 4;
+ else /* ncolors <= 256 */
+ d = 8;
+
+ if ((pixd = pixCreate(w, h, d)) == NULL)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ cmap = pixcmapCreate(d);
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+ datad = pixGetData(pixd);
+ wpld = pixGetWpl(pixd);
+
+ /* hasha1 is a 1/0 indicator array for colors seen.
+ hasha2 holds the index into the colormap that will be
+ generated from the colors in the order seen. This is
+ the value inserted into pixd. */
+ hashsize = 5507; /* big and prime; collisions are not likely */
+ hasha1 = (l_int32 *)LEPT_CALLOC(hashsize, sizeof(l_int32));
+ hasha2 = (l_int32 *)LEPT_CALLOC(hashsize, sizeof(l_int32));
+ index = -1;
+ 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);
+ hashval = (137 * rval + 269 * gval + 353 * bval) % hashsize;
+ if (hasha1[hashval] == 0) { /* new color */
+ hasha1[hashval] = 1;
+ index++;
+ hasha2[hashval] = index;
+ pixcmapAddColor(cmap, rval, gval, bval);
+ }
+ val = hasha2[hashval];
+ setLineDataVal(lined, j, d, val);
+ }
+ }
+ pixSetColormap(pixd, cmap);
+
+ LEPT_FREE(hasha1);
+ LEPT_FREE(hasha2);
+ return pixd;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Find the most "populated" colors in the image (and quantize) *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixGetMostPopulatedColors()
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] sigbits 2-6, significant bits retained in the quantizer
+ * for each component of the input image
+ * \param[in] factor subsampling factor; use 1 for no subsampling
+ * \param[in] ncolors the number of most populated colors to select
+ * \param[out] parray [optional] array of colors, each as 0xrrggbb00
+ * \param[out] pcmap [optional] colormap of the colors
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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, ...);
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSimpleColorQuantize()
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] sigbits 2-4, significant bits retained in the quantizer
+ * for each component of the input image
+ * \param[in] factor subsampling factor; use 1 for no subsampling
+ * \param[in] ncolors the number of most populated colors to select
+ * \return pixd 8 bpp cmapped or NULL on error
+ *
+ * <pre>
+ * 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.
+ * (5) See also pixConvertRGBToCmapLossless() to losslessly convert
+ * an RGB image with not more than 256 colors.
+ * </pre>
+ */
+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 *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixGetRGBHistogram()
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] sigbits 2-6, significant bits retained in the quantizer
+ * for each component of the input image
+ * \param[in] factor subsampling factor; use 1 for no subsampling
+ * \return numa histogram of colors, indexed by RGB
+ * components, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief makeRGBIndexTables()
+ *
+ * \param[out] prtab, pgtab, pbtab 256-entry rgb index tables
+ * \param[in] sigbits 2-6, significant bits retained in the quantizer
+ * for each component of the input image
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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));
+ if (!rtab || !gtab || !btab)
+ return ERROR_INT("calloc fail for tab", procName, 1);
+ *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;
+}
+
+
+/*!
+ * \brief getRGBFromIndex()
+ *
+ * \param[in] index rgbindex
+ * \param[in] sigbits 2-6, significant bits retained in the quantizer
+ * for each component of the input image
+ * \param[out] prval, pgval, pbval rgb values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixHasHighlightRed()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] factor subsampling; an integer >= 1; use 1 for all pixels
+ * \param[in] minfract threshold fraction of all image pixels; must be > 0.0
+ * \param[in] fthresh threshold on a function of the components; typ. ~2.5
+ * \param[out] phasred 1 if red pixels are above threshold
+ * \param[out] pratio [optional] normalized fraction of threshold
+ * red pixels that is actually observed
+ * \param[out] ppixdb [optional] seed pixel mask
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 minfract = 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.
+ * </pre>
+ */
+l_ok
+pixHasHighlightRed(PIX *pixs,
+ l_int32 factor,
+ l_float32 minfract,
+ l_float32 fthresh,
+ l_int32 *phasred,
+ l_float32 *pratio,
+ PIX **ppixdb)
+{
+l_float32 fract, 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 (minfract <= 0.0)
+ return ERROR_INT("minfract must be > 0.0", 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);
+ pixForegroundFraction(pix4, &fract);
+ ratio = fract / minfract;
+ L_INFO("fract = %7.5f, ratio = %7.3f\n", procName, fract, ratio);
+ 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/leptonica/src/colorfill.c b/leptonica/src/colorfill.c
new file mode 100644
index 00000000..970c00bd
--- /dev/null
+++ b/leptonica/src/colorfill.c
@@ -0,0 +1,913 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file colorfill.c
+ * <pre>
+ *
+ * Determine color content using proximity. What can we say about the
+ * color in an image from growing regions with nearly the same color?
+ *
+ * L_COLORFILL *l_colorfillCreate()
+ * void l_colorfillDestroy()
+ *
+ * L_COLORFILL *pixColorContentByLocation()
+ * PIX *pixColorFill()
+ *
+ * Generate data for testing
+ * PIXA *makeColorfillTestData()
+ *
+ * Static helpers
+ * static COLOREL *colorelCreate()
+ * static void pixColorFillFromSeed()
+ * static void pixGetVisitedNeighbors()
+ * static l_int32 findNextUnvisited()
+ * static l_int32 colorsAreSimilarForFill()
+ * static void pixelColorIsValid()
+ * static l_int32 pixelIsOnColorBoundary()
+ * static l_int32 evalColorfillData()
+ *
+ * See colorcontent.c for location-independent measures of the amount
+ * of color in an image.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+struct ColorEl {
+ l_int32 x;
+ l_int32 y;
+ l_uint32 color;
+};
+typedef struct ColorEl COLOREL;
+
+ /* Ignore pixels with smaller max component */
+static l_int32 DefaultMinMax = 70;
+
+ /* Static helpers */
+static COLOREL *colorelCreate(l_int32 x, l_int32 y, l_uint32 color);
+static void pixColorFillFromSeed(PIX *pixs, PIX *pixv, PTA **ppta,
+ l_int32 x, l_int32 y, L_QUEUE *lq,
+ l_int32 maxdiff, l_int32 minarea,
+ l_int32 debug);
+static void pixGetVisitedNeighbors(PIX *pixs, l_int32 x, l_int32 y,
+ l_uint32 *visited);
+static l_int32 findNextUnvisited(PIX *pixv, l_int32 *px, l_int32 *py);
+static l_int32 colorsAreSimilarForFill(l_uint32 val1, l_uint32 val2,
+ l_int32 maxdiff);
+static l_int32 pixelColorIsValid(l_uint32 val, l_int32 minmax);
+static l_int32 pixelIsOnColorBoundary(PIX *pixs, l_int32 x, l_int32 y);
+static l_int32 evalColorfillData(L_COLORFILL *cf, l_int32 debug);
+
+
+/*---------------------------------------------------------------------*
+ * Colorfill creation and destruction *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_colorfillCreate()
+ *
+ * \param[in] pixs input RGB image
+ * \param[in] nx requested number of tiles in each row
+ * \param[in] ny requested number of tiles in each column
+ * \return boxa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Tiles must at least 10 pixels in each dimension.
+ * </pre>
+ */
+L_COLORFILL *
+l_colorfillCreate(PIX *pixs,
+ l_int32 nx,
+ l_int32 ny)
+{
+l_int32 i, j, w, h, tw, th, ntiles;
+BOX *box;
+BOXA *boxas;
+L_COLORFILL *cf;
+
+ PROCNAME("l_colorfillCreate");
+
+ if (!pixs)
+ return (L_COLORFILL *)ERROR_PTR("pixs not defined", procName, NULL);
+ if (pixGetDepth(pixs) != 32)
+ return (L_COLORFILL *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ tw = w / nx;
+ th = h / ny;
+ if (tw < 10 || th < 10)
+ return (L_COLORFILL *)ERROR_PTR("tile size too small", procName, NULL);
+ boxas = boxaCreate(nx * ny);
+ for (i = 0; i < ny; i++) {
+ for (j = 0; j < nx; j++) {
+ box = boxCreate(j * tw, i * th, tw, th);
+ boxaAddBox(boxas, box, L_INSERT);
+ }
+ }
+ ntiles = nx * ny;
+
+ cf = (L_COLORFILL *)LEPT_CALLOC(1, sizeof(L_COLORFILL));
+ cf->pixs = pixClone(pixs);
+ cf->nx = nx;
+ cf->ny = ny;
+ cf->tw = tw;
+ cf->th = th;
+ cf->boxas = boxas;
+ cf->naa = numaaCreate(ntiles);
+ cf->dnaa = l_dnaaCreate(ntiles);
+ cf->pixadb = pixaCreate(0);
+ return cf;
+}
+
+
+/*!
+ * \brief l_colorfillDestroy()
+ *
+ * \param[in,out] pcf will be set to null before returning
+ * \return void
+ */
+void
+l_colorfillDestroy(L_COLORFILL **pcf)
+{
+L_COLORFILL *cf;
+
+ PROCNAME("l_colorfillDestroy");
+
+ if (pcf == NULL) {
+ L_WARNING("ptr address is null!\n", procName);
+ return;
+ }
+
+ if ((cf = *pcf) == NULL)
+ return;
+
+ pixDestroy(&cf->pixs);
+ pixDestroy(&cf->pixst);
+ boxaDestroy(&cf->boxas);
+ pixaDestroy(&cf->pixas);
+ pixaDestroy(&cf->pixam);
+ numaaDestroy(&cf->naa);
+ l_dnaaDestroy(&cf->dnaa);
+ pixaDestroy(&cf->pixadb);
+ LEPT_FREE(cf);
+ *pcf = NULL;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Determine color content using proximity. What do we get when *
+ * growing regions with nearly the same color? *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief pixColorContentByLocation()
+ *
+ * \param[in] cf colorfill
+ * \param[in] rref reference value for red component
+ * \param[in] gref reference value for green component
+ * \param[in] bref reference value for blue component
+ * \param[in] minmax min of max component for possible color region
+ * \param[in] maxdiff max component diff to be in same color region
+ * \param[in] minarea min number of pixels for a color region
+ * \param[in] smooth low-pass kernel size (1,3,5); use 1 to skip
+ * \param[in] debug generates debug images and fill info
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This computes color fill information in each tile, identifying
+ * regions of approximately constant color. It does this
+ * independently for each tile, using flood fills. Regions
+ * of low intensity are considered 'not colorful'.
+ * (2) The three numbers (rref, gref and bref) can be thought
+ * of in two ways:
+ * (a) as rgb values in the image corresponding to white,
+ * to compensate for an unbalanced color white point.
+ * (b) as the median or mean values of the background color
+ * of a scan.
+ * The gamma TRC transformation, which does not change hue, is used
+ * to modify all colors so that these reference values become white.
+ * These three numbers must either be all 0 or all non-zero.
+ * To skip the TRC transform, set them all to 0.
+ * (3) If the maximum component after white point correction,
+ * max(r,g,b), is less than minmax, the pixel color is invalid, and it
+ * is assigned its neighbor's value in the filling operation.
+ * Use %minmax = 0 for a default value.
+ * </pre>
+ */
+l_ok
+pixColorContentByLocation(L_COLORFILL *cf,
+ l_int32 rref,
+ l_int32 gref,
+ l_int32 bref,
+ l_int32 minmax,
+ l_int32 maxdiff,
+ l_int32 minarea,
+ l_int32 smooth,
+ l_int32 debug)
+{
+l_int32 i, n;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixas, *pixam;
+
+ PROCNAME("pixColorContentByLocation");
+
+ if (!cf)
+ return ERROR_INT("cf not defined", procName, 1);
+ if (minmax <= 0) minmax = DefaultMinMax;
+ if (minmax > 200)
+ return ERROR_INT("minmax > 200; unreasonably large", procName, 1);
+
+ /* Do the optional linear color map; this checks the ref vals
+ * and uses them if valid. Use {0,0,0} to skip this operation. */
+ if ((pix1 = pixColorShiftWhitePoint(cf->pixs, rref, gref, bref)) == NULL)
+ return ERROR_INT("pix1 not returned", procName, 1);
+ cf->pixst = pix1;
+
+ /* Break the image up into small tiles */
+ pixas = pixaCreateFromBoxa(pix1, cf->boxas, 0, 0, NULL);
+ cf->pixas = pixas;
+
+ /* Find regions of similar color in each tile */
+ n = pixaGetCount(pixas);
+ pixam = pixaCreate(n);
+ cf->pixam = pixam;
+ for (i = 0; i < n; i++) {
+ pix2 = pixaGetPix(pixas, i, L_COPY);
+ pix3 = pixColorFill(pix2, minmax, maxdiff, smooth, minarea, 0);
+ pixDestroy(&pix2);
+ pixaAddPix(pixam, pix3, L_INSERT);
+ }
+
+ /* Evaluate color components. Find the average color in each
+ * component and determine if there is more than one color in
+ * each of the tiles. */
+ evalColorfillData(cf, debug);
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixColorFill()
+ *
+ * \param[in] pixs 32 bpp RGB
+ * \param[in] minmax min of max component for possible color region
+ * \param[in] maxdiff max component diff to be in same color region
+ * \param[in] smooth low-pass kernel size (1,3,5); use 1 to skip
+ * \param[in] minarea min number of pixels for a color region
+ * \param[in] debug generates debug images and fill info
+ * \return pixm mask showing connected regions of similar color,
+ * or null on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is the basic color filling operation. It sets the
+ * non-color pixel to black, optionally does a low-pass filter,
+ * and grows the 8-connected color components. Finally, it
+ * removes components that have a small area.
+ * </pre>
+ */
+PIX *
+pixColorFill(PIX *pixs,
+ l_int32 minmax,
+ l_int32 maxdiff,
+ l_int32 smooth,
+ l_int32 minarea,
+ l_int32 debug)
+{
+l_int32 x, y, w, h, empty;
+l_uint32 val;
+L_KERNEL *kel;
+PIX *pixm, *pixm1, *pixv, *pixnc, *pixncd, *pixss, *pixf;
+PTA *pta1;
+L_QUEUE *lq;
+
+ PROCNAME("pixColorFill");
+
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+
+ /* Set the non-color pixels to 0; generate a mask representing them */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixnc = pixCreate(w, h, 1); /* mask for no color */
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++) {
+ pixGetPixel(pixs, x, y, &val);
+ if (!pixelColorIsValid(val, minmax)) {
+ pixSetPixel(pixnc, x, y, 1);
+ pixSetPixel(pixs, x, y, 0x0);
+ }
+ }
+ }
+
+ /* Optionally, dilate the no-color mask */
+ pixncd = pixDilateBrick(NULL, pixnc, smooth, smooth);
+ pixDestroy(&pixnc);
+
+ /* Do a low-pass filter on pixs. This will make bad pixels
+ * near the zeroed non-color pixels, but any components made
+ * from these pixels will be removed at the end by the
+ * (optionally dilated) no-color mask. */
+ if (smooth > 1) {
+ kel = makeFlatKernel(smooth, smooth, smooth / 2, smooth / 2);
+ pixss = pixConvolveRGBSep(pixs, kel, kel);
+ kernelDestroy(&kel);
+ } else {
+ pixss = pixCopy(NULL, pixs);
+ }
+
+ /* Paint through everything under pixncd */
+ pixPaintThroughMask(pixss, pixncd, 0, 0, 0);
+
+ /* Find the color components */
+ pixv = pixCreate(w, h, 1); /* visited pixels */
+ pixOr(pixv, pixv, pixncd); /* consider non-color as visited */
+ pixSetBorderRingVal(pixv, 1, 1);
+ pixm = pixCreate(w, h, 1); /* color components */
+ lq = lqueueCreate(0);
+ x = y = 1; /* first row and column have been marked as visited */
+ while (findNextUnvisited(pixv, &x, &y) == 1) {
+ /* Flood fill this component, starting from (x,y) */
+ if (debug) lept_stderr("Start: x = %d, y = %d\n", x, y);
+ pixColorFillFromSeed(pixss, pixv, &pta1, x, y, lq, maxdiff,
+ minarea, debug);
+ if (pta1) { /* erode and add the pixels to pixm */
+ pixm1 = pixGenerateFromPta(pta1, w, h);
+ pixErodeBrick(pixm1, pixm1, 3, 3);
+ pixOr(pixm, pixm, pixm1);
+ pixDestroy(&pixm1);
+ ptaDestroy(&pta1);
+ }
+ }
+ pixDestroy(&pixv);
+
+ /* Remove everything under pixncd */
+ pixSubtract(pixm, pixm, pixncd);
+
+ /* Remove remaining small stuff */
+ pixf = pixSelectByArea(pixm, minarea, 4, L_SELECT_IF_GTE, NULL);
+
+ lqueueDestroy(&lq, 1);
+ pixDestroy(&pixncd);
+ pixDestroy(&pixss);
+ pixDestroy(&pixm);
+ return pixf;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Generate data for testing *
+ * ----------------------------------------------------------------------- */
+/*!
+ * \brief makeColorfillTestData()
+ *
+ * \param[in] w width of generated pix
+ * \param[in] h height of generated pix
+ * \param[in] nseeds number of regions
+ * \param[in] range of color component values
+ * \return pixa various pix with filled regions of random color,
+ * or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The seeds are random points. The colors are assigned
+ * randomly from a restricted range of component values,
+ * in [128 - range/2 ... 128 + range/2]
+ * (2) Output is pixa:
+ * * pixa[0] cmapped, with color regions shown
+ * - pixa[1] cmapped, additionally with boundary pixels set to black
+ * - pixa[2] cmapped, as in pixa[1] with all non-black pixels
+ * in the same color
+ * </pre>
+ */
+PIXA *
+makeColorfillTestData(l_int32 w,
+ l_int32 h,
+ l_int32 nseeds,
+ l_int32 range)
+{
+l_int32 i, j, x, y, rval, gval, bval, start, end;
+l_uint32 color;
+l_float64 dval;
+L_DNA *da;
+PIX *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa;
+PTA *pta;
+PIXCMAP *cmap;
+
+ /* Generate data for seeds */
+ pta = ptaCreate(nseeds); /* for seed locations */
+ da = l_dnaCreate(nseeds); /* for colors */
+ srand(4);
+ start = 128 - range / 2;
+ end = 128 + (range - 1) / 2;
+ for (i = 0; i < nseeds; i++) {
+ genRandomIntOnInterval(0, w - 1, 0, &x);
+ genRandomIntOnInterval(0, h - 1, 0, &y);
+ ptaAddPt(pta, x, y);
+ genRandomIntOnInterval(start, end, 0, &rval);
+ genRandomIntOnInterval(start, end, 0, &gval);
+ genRandomIntOnInterval(start, end, 0, &bval);
+ composeRGBPixel(rval, gval, bval, &color);
+ l_dnaAddNumber(da, color);
+ }
+
+ /* Generate the 8 bpp seed image */
+ pix1 = pixCreate(w, h, 8);
+ for (i = 0; i < nseeds; i++) {
+ ptaGetIPt(pta, i, &x, &y);
+ pixSetPixel(pix1, x, y, i + 1); /* all seeds have non-zero values */
+ }
+
+ /* Spread seed values to all pixels that are nearest to
+ * the seed pixel from which they take their value. */
+ pix2 = pixSeedspread(pix1, 4);
+
+ /* Add a colormap for the random colors, using 0 for black */
+ cmap = pixcmapCreate(8);
+ pixcmapAddColor(cmap, 0, 0, 0);
+ for (i = 0; i < nseeds; i++) {
+ l_dnaGetDValue(da, i, &dval);
+ extractRGBValues(dval, &rval, &gval, &bval);
+ pixcmapAddColor(cmap, rval, gval, bval);
+ }
+ pixSetColormap(pix2, cmap);
+ pixDestroy(&pix1);
+ ptaDestroy(&pta);
+ l_dnaDestroy(&da);
+
+ /* Add to output; no black boundaries */
+ pixa = pixaCreate(0);
+ pixaAddPix(pixa, pix2, L_COPY);
+
+ /* Make pixels on the color boundaries black */
+ pix3 = pixCopy(NULL, pix2);
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ if (pixelIsOnColorBoundary(pix2, j, i))
+ pixSetPixel(pix3, j, i, 0); /* black */
+ }
+ }
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+
+ /* Have all the non-black regions be the same color */
+ cmap = pixcmapCreate(8);
+ pixcmapAddColor(cmap, 0, 0, 0);
+ for (i = 0; i < nseeds; i++)
+ pixcmapAddColor(cmap, rval, gval, bval);
+ pix4 = pixCopy(NULL, pix3);
+ pixSetColormap(pix4, cmap);
+ pixaAddPix(pixa, pix4, L_INSERT);
+
+ return pixa;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ * Static helpers *
+ * ----------------------------------------------------------------------- */
+static COLOREL *
+colorelCreate(l_int32 x,
+ l_int32 y,
+ l_uint32 color)
+{
+COLOREL *el;
+
+ el = (COLOREL *)LEPT_CALLOC(1, sizeof(COLOREL));
+ el->x = x;
+ el->y = y;
+ el->color = color;
+ return el;
+}
+
+/*!
+ * \brief pixColorFillFromSeed()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] pixv 1 bpp labeling visited pixels
+ * \param[out] ppta points visited with similar colors during fill
+ * \param[in] x starting x coord for fill (seed)
+ * \param[in] y starting y coord for fill (seed)
+ * \param[in] lq head of queue holding pixels
+ * \param[in] maxdiff max component diff allowed for similar pixels
+ * \param[in] minarea min size of component to keep
+ * \param[in] debug output some text data
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Use 8-connected filling. It is faster because it reduces the
+ * number of single-pixel noise components near color boundaries.
+ * (2) The seed pixel at (x,y) is unvisited, and can never be on the
+ * exterior boundary of the tile %pixs.
+ * (3) If the size of the connected component >= %minarea, we return
+ * the array of pixel locations; otherwise, return NULL for the pta.
+ * </pre>
+ */
+static void
+pixColorFillFromSeed(PIX *pixs,
+ PIX *pixv,
+ PTA **ppta,
+ l_int32 x,
+ l_int32 y,
+ L_QUEUE *lq,
+ l_int32 maxdiff,
+ l_int32 minarea,
+ l_int32 debug)
+{
+l_int32 w, h, np;
+l_uint32 visited[8]; /* W, N, E, S, NW, NE, SW, SE */
+l_uint32 color, val;
+COLOREL *el;
+PTA *pta;
+
+ /* Prime the queue with this pixel */
+ pixGetPixel(pixs, x, y, &val);
+ el = colorelCreate(x, y, val);
+ lqueueAdd(lq, el);
+ pixSetPixel(pixv, x, y, 1); /* visited */
+ pta = ptaCreate(0);
+ *ppta = pta;
+ ptaAddPt(pta, x, y);
+
+ /* Trace out the color component. Each pixel on the queue has
+ * a color. Pop from the queue and for each of its 8 neighbors,
+ * for those that have color:
+ * - If the pixel has a similar color, add to the pta array for
+ * the component, using the color of its parent.
+ * - Mark visited so that it will not be included in another
+ * component -- this effectively separates the growing component
+ * from all others. */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ while (lqueueGetCount(lq) > 0) {
+ el = (COLOREL *)lqueueRemove(lq);
+ x = el->x;
+ y = el->y;
+ color = el->color;
+ LEPT_FREE(el);
+ pixGetVisitedNeighbors(pixv, x, y, visited);
+ if (!visited[0]) { /* check W */
+ pixGetPixel(pixs, x - 1, y, &val);
+ if (colorsAreSimilarForFill(color, val, maxdiff)) {
+ el = colorelCreate(x - 1, y, color);
+ lqueueAdd(lq, el);
+ ptaAddPt(pta, x - 1, y); /* added to component */
+ pixSetPixel(pixv, x - 1, y, 1); /* visited */
+ }
+ }
+ if (!visited[1]) { /* check N */
+ pixGetPixel(pixs, x, y - 1, &val);
+ if (colorsAreSimilarForFill(color, val, maxdiff)) {
+ el = colorelCreate(x, y - 1, color);
+ lqueueAdd(lq, el);
+ ptaAddPt(pta, x, y - 1);
+ pixSetPixel(pixv, x, y - 1, 1);
+ }
+ }
+ if (!visited[2]) { /* check E */
+ pixGetPixel(pixs, x + 1, y, &val);
+ if (colorsAreSimilarForFill(color, val, maxdiff)) {
+ el = colorelCreate(x + 1, y, color);
+ lqueueAdd(lq, el);
+ ptaAddPt(pta, x + 1, y);
+ pixSetPixel(pixv, x + 1, y, 1);
+ }
+ }
+ if (!visited[3]) { /* check S */
+ pixGetPixel(pixs, x, y + 1, &val);
+ if (colorsAreSimilarForFill(color, val, maxdiff)) {
+ el = colorelCreate(x, y + 1, color);
+ lqueueAdd(lq, el);
+ ptaAddPt(pta, x, y + 1);
+ pixSetPixel(pixv, x, y + 1, 1);
+ }
+ }
+ if (!visited[4]) { /* check NW */
+ pixGetPixel(pixs, x - 1, y - 1, &val);
+ if (colorsAreSimilarForFill(color, val, maxdiff)) {
+ el = colorelCreate(x - 1, y - 1, color);
+ lqueueAdd(lq, el);
+ ptaAddPt(pta, x - 1, y - 1);
+ pixSetPixel(pixv, x - 1, y - 1, 1);
+ }
+ }
+ if (!visited[5]) { /* check NE */
+ pixGetPixel(pixs, x + 1, y - 1, &val);
+ if (colorsAreSimilarForFill(color, val, maxdiff)) {
+ el = colorelCreate(x + 1, y - 1, color);
+ lqueueAdd(lq, el);
+ ptaAddPt(pta, x + 1, y - 1);
+ pixSetPixel(pixv, x + 1, y - 1, 1);
+ }
+ }
+ if (!visited[6]) { /* check SW */
+ pixGetPixel(pixs, x - 1, y + 1, &val);
+ if (colorsAreSimilarForFill(color, val, maxdiff)) {
+ el = colorelCreate(x - 1, y + 1, color);
+ lqueueAdd(lq, el);
+ ptaAddPt(pta, x - 1, y + 1);
+ pixSetPixel(pixv, x - 1, y + 1, 1);
+ }
+ }
+ if (!visited[7]) { /* check SE */
+ pixGetPixel(pixs, x + 1, y + 1, &val);
+ if (colorsAreSimilarForFill(color, val, maxdiff)) {
+ el = colorelCreate(x + 1, y + 1, color);
+ lqueueAdd(lq, el);
+ ptaAddPt(pta, x + 1, y + 1);
+ pixSetPixel(pixv, x + 1, y + 1, 1);
+ }
+ }
+ }
+
+ /* If there are not enough pixels, do not return the pta.
+ * Otherwise, if a pta is returned, the caller will generate
+ * a component and put it in the mask. */
+ np = ptaGetCount(pta);
+ if (np < minarea) {
+ if (debug) lept_stderr(" Too small. End: x = %d, y = %d, np = %d\n",
+ x, y, np);
+ ptaDestroy(ppta);
+ } else {
+ if (debug) lept_stderr(" Keep. End: x = %d, y = %d, np = %d\n",
+ x, y, np);
+ }
+}
+
+
+/*!
+ * \brief pixGetVisitedNeighbors()
+ *
+ * \param[in] pixs 1 bpp, representing visited locations
+ * \param[in] x x coord of pixel
+ * \param[in] y y coord of pixel
+ * \param[in,out] visited array of 8 int
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) The values of the neighbors of (x,y) in pixs, given in
+ * order {W,N,E,S,NW,NE,SW,SE}, are returned in %visited.
+ * A "1" value means that pixel has been visited. Initialize
+ * each neighbor to 1 (visited).
+ * (2) The input point (%x,%y) is never on the outer boundary of %pixs,
+ * (e.g., x >= 1, y >= 1), so no checking is required.
+ * </pre>
+ */
+static void
+pixGetVisitedNeighbors(PIX *pixs,
+ l_int32 x,
+ l_int32 y,
+ l_uint32 *visited)
+{
+ pixGetPixel(pixs, x - 1, y, visited); /* W */
+ pixGetPixel(pixs, x, y - 1, visited + 1); /* N */
+ pixGetPixel(pixs, x + 1, y, visited + 2); /* E */
+ pixGetPixel(pixs, x, y + 1, visited + 3); /* S */
+ pixGetPixel(pixs, x - 1, y - 1, visited + 4); /* NW */
+ pixGetPixel(pixs, x + 1, y - 1, visited + 5); /* NE */
+ pixGetPixel(pixs, x - 1, y + 1, visited + 6); /* SW */
+ pixGetPixel(pixs, x + 1, y + 1, visited + 7); /* SE */
+}
+
+
+/*!
+ * \brief findNextUnvisited()
+ *
+ * \param[in] pixv visited pixels
+ * \param[in,out] py input start scanline; output y-coord of next seed
+ * \param[out] px x-coord of next seed
+ * \return 1 if new seed point is found; 0 if there are none
+ *
+ * <pre>
+ * Notes:
+ * (1) Start the search at the beginning of the raster line
+ * for the pixel that ended the previous search.
+ * </pre>
+ */
+static l_int32
+findNextUnvisited(PIX *pixv,
+ l_int32 *px,
+ l_int32 *py)
+{
+l_int32 ret;
+PIX *pix1;
+
+ pix1 = pixCopy(NULL, pixv);
+ pixInvert(pix1, pix1); /* After inversion, ON pixels are unvisited */
+ ret = nextOnPixelInRaster(pix1, 1, *py, px, py);
+ pixDestroy(&pix1);
+ return ret;
+}
+
+
+/*!
+ * \brief colorsAreSimilarForFill()
+ *
+ * \param[in] val1 color of pixel 1, as 0xrrggbb00
+ * \param[in] val2 color of pixel 2, as 0xrrggbb00
+ * \param[in] maxdiff max of difference function to be similar
+ * \return 1 if val1 and val2 are similar; 0 otherwise
+ *
+ * <pre>
+ * Notes:
+ * (1) An example will explain the approach. Suppose we have:
+ * val1 = {100, 130, 70}
+ * val2 = {90, 135, 62}
+ * First find that red is the color with largest abs(difference):
+ * rdiff = val1 - val2 = 10
+ * Find the green and blue differences
+ * gdiff = 130 - 135 = -5
+ * bdiff = 70 - 62 = 8
+ * and subtract each from rdiff:
+ * rdiff - gdiff = 15
+ * rdiff - bdiff = 7
+ * The max of these is 15, which is then compared with %maxdiff
+ * </pre>
+ */
+static l_int32
+colorsAreSimilarForFill(l_uint32 val1,
+ l_uint32 val2,
+ l_int32 maxdiff)
+{
+l_int32 rdiff, gdiff, bdiff, maxindex, del1, del2, del3, maxdel;
+l_int32 v1[3], v2[3];
+
+ extractRGBValues(val1, v1, v1 + 1, v1 + 2);
+ extractRGBValues(val2, v2, v2 + 1, v2 + 2);
+ rdiff = v1[0] - v2[0];
+ gdiff = v1[1] - v2[1];
+ bdiff = v1[2] - v2[2];
+ maxindex = 0;
+ if (L_ABS(gdiff) > L_ABS(rdiff))
+ maxindex = 1;
+ if (L_ABS(bdiff) > L_ABS(rdiff) && L_ABS(bdiff) > L_ABS(gdiff))
+ maxindex = 2;
+ del1 = v1[maxindex] - v2[maxindex];
+ del2 = v1[(maxindex + 1) % 3] - v2[(maxindex + 1) % 3];
+ del3 = v1[(maxindex + 2) % 3] - v2[(maxindex + 2) % 3];
+ maxdel = L_MAX(L_ABS(del1 - del2), L_ABS(del1 - del3));
+ return (maxdel <= maxdiff) ? 1 : 0;
+}
+
+
+/*!
+ * \brief pixelColorIsValid()
+ *
+ * \param[in] val color, as 0xrrggbb00
+ * \param[in] minmax max component must be < %minmax to be valid
+ * \return 0 if max component < %minmax; 1 otherwise
+ */
+static l_int32
+pixelColorIsValid(l_uint32 val,
+ l_int32 minmax)
+{
+l_int32 rval, gval, bval;
+
+ extractRGBValues(val, &rval, &gval, &bval);
+ if (rval < minmax && gval < minmax && bval < minmax)
+ return 0; /* maximum component is less than threshold */
+ else
+ return 1;
+}
+
+
+/*!
+ * \brief pixelIsOnColorBoundary()
+ *
+ * \param[in] pixs 32 bpp rgb or 8 bpp with colormap
+ * \param[in] x, y location of pixel of interest
+ * \return 1 if at least one neighboring pixel had a different color;
+ * 0 otherwise.
+ */
+static l_int32
+pixelIsOnColorBoundary(PIX *pixs,
+ l_int32 x,
+ l_int32 y)
+{
+l_int32 w, h;
+l_uint32 val, neigh;
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ pixGetPixel(pixs, x, y, &val);
+ if (x > 0) {
+ pixGetPixel(pixs, x - 1, y, &neigh); /* W */
+ if (neigh != val) return TRUE;
+ }
+ if (x < w - 1) {
+ pixGetPixel(pixs, x + 1, y, &neigh); /* E */
+ if (neigh != val) return TRUE;
+ }
+ if (y > 0) {
+ pixGetPixel(pixs, x, y - 1, &neigh); /* N */
+ if (neigh != val) return TRUE;
+ }
+ if (y < h - 1) {
+ pixGetPixel(pixs, x, y + 1, &neigh); /* S */
+ if (neigh != val) return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*!
+ * \brief evalColorfillData()
+ *
+ * \param[in] cf colorfill with masks generated for all tiles
+ * \param[in] debug show segmented regions with their median color
+ * \return 0 if OK, 1 on error
+ */
+static l_int32
+evalColorfillData(L_COLORFILL *cf,
+ l_int32 debug)
+{
+l_int32 i, j, n, nc, w, h, x, y, count;
+l_float32 rval, gval, bval;
+l_uint32 pixel;
+l_int32 *tab;
+BOX *box1;
+BOXA *boxa1;
+L_DNA *da;
+NUMA *na;
+PIX *pixm, *pix1, *pix2, *pixdb;
+PIXA *pixa1;
+
+ PROCNAME("evalColorfillData");
+
+ if (!cf)
+ return ERROR_INT("cf not defind", procName, 1);
+
+ tab = makePixelSumTab8();
+ n = cf->nx * cf->ny;
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(cf->pixas, i, L_CLONE);
+ pixm = pixaGetPix(cf->pixam, i, L_CLONE);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ boxa1 = pixConnComp(pixm, &pixa1, 4);
+ boxaDestroy(&boxa1);
+ nc = pixaGetCount(pixa1);
+ na = numaCreate(0);
+ da = l_dnaCreate(0);
+ pixdb = (debug) ? pixCreate(w, h, 32) : NULL;
+ for (j = 0; j < nc; j++) {
+ pix2 = pixaGetPix(pixa1, j, L_COPY);
+ box1 = pixaGetBox(pixa1, j, L_COPY);
+ boxGetGeometry(box1, &x, &y, NULL, NULL);
+ pixGetRankValueMaskedRGB(pix1, pix2, x, y, 1, 0.5,
+ &rval, &gval, &bval);
+ composeRGBPixel(rval, gval, bval, &pixel);
+
+ l_dnaAddNumber(da, pixel);
+ pixCountPixels(pix2, &count, tab);
+ numaAddNumber(na, count);
+ if (debug)
+ pixPaintThroughMask(pixdb, pix2, x, y, pixel);
+ boxDestroy(&box1);
+ pixDestroy(&pix2);
+ }
+ pixaAddPix(cf->pixadb, pixdb, L_INSERT);
+ numaaAddNuma(cf->naa, na, L_INSERT);
+ l_dnaaAddDna(cf->dnaa, da, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pixm);
+ pixaDestroy(&pixa1);
+ }
+
+ if (debug) { /* first tile */
+ na = numaaGetNuma(cf->naa, 0, L_CLONE);
+ lept_stderr("Size of components in tile 0:");
+ numaWriteStderr(na);
+ numaDestroy(&na);
+ }
+ LEPT_FREE(tab);
+ return 0;
+}
diff --git a/leptonica/src/colorfill.h b/leptonica/src/colorfill.h
new file mode 100644
index 00000000..2d3a8679
--- /dev/null
+++ b/leptonica/src/colorfill.h
@@ -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.
+ *====================================================================*/
+
+#ifndef LEPTONICA_COLORFILL_H
+#define LEPTONICA_COLORFILL_H
+
+/*!
+ * \file colorfill.h
+ *
+ * <pre>
+ * Contains the following struct
+ * struct L_Colorfill
+ *
+ * This accumulates color information, linked to location, within a
+ * set of tiles that (mostly) covers an input RGB image.
+ * </pre>
+ */
+
+
+/*------------------------------------------------------------------------*
+ * Colorfill data *
+ *------------------------------------------------------------------------*/
+/*! Colorfill data */
+struct L_Colorfill
+{
+ struct Pix *pixs; /*!< clone of source pix */
+ struct Pix *pixst; /*!< source pix, after optional transform */
+ l_int32 nx; /*!< number of tiles in each tile row */
+ l_int32 ny; /*!< number of tiles in each tile column */
+ l_int32 tw; /*!< width of each tile */
+ l_int32 th; /*!< height of each tile */
+ l_int32 minarea; /*!< min number of pixels in a color region */
+ struct Boxa *boxas; /*!< tile locations */
+ struct Pixa *pixas; /*!< tiles from source pix */
+ struct Pixa *pixam; /*!< mask tiles with components covering */
+ /*!< regions with similar color */
+ struct Numaa *naa; /*!< sizes of color regions (in pixels) */
+ struct L_Dnaa *dnaa; /*!< average color in each region */
+ struct Pixa *pixadb; /*!< debug reconstruction from segmentation */
+};
+typedef struct L_Colorfill L_COLORFILL;
+
+
+#endif /* LEPTONICA_COLORFILL_H */
diff --git a/leptonica/src/coloring.c b/leptonica/src/coloring.c
new file mode 100644
index 00000000..77b5cb59
--- /dev/null
+++ b/leptonica/src/coloring.c
@@ -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.
+ *====================================================================*/
+
+/*!
+ * \file coloring.c
+ * <pre>
+ *
+ * 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()
+ * PIX *pixShiftWithInvariantHue()
+ *
+ * There are quite a few "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, pixMapWithInvariantHue).
+ * (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 or printing.
+ * (enhance.c: pixColorShiftRGB, pixMosaicColorShiftRGB).
+ * (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 one of
+ * these parameters in HSV color space by a fraction of the distance
+ * toward its end-point, but leaving the other two parameters
+ * invariant. 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), without altering
+ * the hue or saturation. (enhance.c: pixModifySaturation,
+ * pixModifyHue, pixModifyBrightness)
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*
+ * Coloring "gray" pixels *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixColorGrayRegions()
+ *
+ * \param[in] pixs 2, 4 or 8 bpp gray, rgb, or colormapped
+ * \param[in] boxa of regions in which to apply color
+ * \param[in] type L_PAINT_LIGHT, L_PAINT_DARK
+ * \param[in] thresh average value below/above which pixel is unchanged
+ * \param[in] rval, gval, bval new color to paint
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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 pixHasHighlightRed() to
+ * add highlight color to a grayscale image.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixColorGray()
+ *
+ * \param[in] pixs 8 bpp gray, rgb or colormapped image
+ * \param[in] box [optional] region in which to apply color; can be NULL
+ * \param[in] type L_PAINT_LIGHT, L_PAINT_DARK
+ * \param[in] thresh average value below/above which pixel is unchanged
+ * \param[in] rval, gval, bval new color to paint
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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.f / 255.f;
+ 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;
+}
+
+
+/*!
+ * \brief pixColorGrayMasked()
+ *
+ * \param[in] pixs 8 bpp gray, rgb or colormapped image
+ * \param[in] pixm 1 bpp mask, through which to apply color
+ * \param[in] type L_PAINT_LIGHT, L_PAINT_DARK
+ * \param[in] thresh average value below/above which pixel is unchanged
+ * \param[in] rval, gval, bval new color to paint
+ * \return pixd colorized, or NULL on error
+ *
+ * <pre>
+ * 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 pixHasHighlightRed() to
+ * add highlight color to a grayscale image.
+ * </pre>
+ */
+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.f / 255.f;
+ 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixSnapColor()
+ *
+ * \param[in] pixd [optional]; either NULL or equal to pixs for in-place
+ * \param[in] pixs colormapped or 8 bpp gray or 32 bpp rgb
+ * \param[in] srcval color center to be selected for change: 0xrrggbb00
+ * \param[in] dstval target color for pixels: 0xrrggbb00
+ * \param[in] diff max absolute difference, applied to all components
+ * \return pixd with all pixels within diff of pixval set to pixval,
+ * or pixd on error
+ *
+ * <pre>
+ * 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'.
+ * </pre>
+ */
+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 exists, but != 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;
+}
+
+
+/*!
+ * \brief pixSnapColorCmap()
+ *
+ * \param[in] pixd [optional]; either NULL or equal to pixs for in-place
+ * \param[in] pixs colormapped
+ * \param[in] srcval color center to be selected for change: 0xrrggbb00
+ * \param[in] dstval target color for pixels: 0xrrggbb00
+ * \param[in] diff max absolute difference, applied to all components
+ * \return pixd with all pixels within diff of srcval set to dstval,
+ * or pixd on error
+ *
+ * <pre>
+ * 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'.
+ * </pre>
+ */
+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 exists, but != 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixLinearMapToTargetColor()
+ *
+ * \param[in] pixd [optional]; either NULL or equal to pixs for in-place
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] srcval source color: 0xrrggbb00
+ * \param[in] dstval target color: 0xrrggbb00
+ * \return pixd with all pixels mapped based on the srcval/destval mapping,
+ * or pixd on error
+ *
+ * <pre>
+ * 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. A special case is where the dest in the
+ * map is white (255, 255, 255) for an arbitrary srcval.
+ * (3) For inplace operation, call it this way:
+ * pixLinearMapToTargetColor(pixs, pixs, ... );
+ * For generating a new pixd:
+ * pixd = pixLinearMapToTargetColor(NULL, pixs, ...);
+ * (4) See pixShiftWithInvariantHue() for a special case of this function.
+ * </pre>
+ */
+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 || pixGetDepth(pixs) != 32)
+ return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, pixd);
+ if (pixd && (pixd != pixs))
+ return (PIX *)ERROR_PTR("pixd exists, but != pixs", 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));
+ if (!rtab || !gtab || !btab)
+ return (PIX *)ERROR_PTR("calloc fail for tab", procName, pixd);
+ 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;
+}
+
+
+/*!
+ * \brief pixelLinearMapToTargetColor()
+ *
+ * \param[in] scolor rgb source color: 0xrrggbb00
+ * \param[in] srcmap source mapping color: 0xrrggbb00
+ * \param[in] dstmap target mapping color: 0xrrggbb00
+ * \param[out] pdcolor rgb dest color: 0xrrggbb00
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixShiftByComponent()
+ *
+ * \param[in] pixd [optional]; either NULL or equal to pixs for in-place
+ * \param[in] pixs 32 bpp rgb, cmap OK
+ * \param[in] srcval source color: 0xrrggbb00
+ * \param[in] dstval target color: 0xrrggbb00
+ * \return pixd with all pixels mapped based on the srcval/destval mapping,
+ * or pixd on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 exists, but != 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));
+ if (!rtab || !gtab || !btab) {
+ L_ERROR("calloc fail for tab\n", procName);
+ goto cleanup;
+ }
+ 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;
+ }
+ }
+
+cleanup:
+ LEPT_FREE(rtab);
+ LEPT_FREE(gtab);
+ LEPT_FREE(btab);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixelShiftByComponent()
+ *
+ * \param[in] rval, gval, bval
+ * \param[in] srcval source color: 0xrrggbb00
+ * \param[in] dstval target color: 0xrrggbb00
+ * \param[out] ppixel rgb value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixelFractionalShift()
+ *
+ * \param[in] rval red source component
+ * \param[in] gval green source component
+ * \param[in] bval blue source component
+ * \param[in] fract negative toward black; positive toward white
+ * \param[out] ppixel resulting rgb value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This linear transformation shifts each component a fraction
+ * toward either black (%fract < 0) or white (%fract > 0).
+ * (2) It changes the saturation and intensity, but leaves the hue
+ * invariant. See usage in pixLinearMapToTargetColor() and
+ * pixMapWithInvariantHue().
+ * (3) %fract is in the range [-1 .... +1]. If %fract < 0,
+ * saturation is increased and brightness is reduced. The
+ * opposite results if %fract > 0. If %fract == -1,
+ * the resulting pixel is black; %fract == 1 results in white.
+ * </pre>
+ */
+l_ok
+pixelFractionalShift(l_int32 rval,
+ l_int32 gval,
+ l_int32 bval,
+ l_float32 fract,
+ l_uint32 *ppixel)
+{
+l_int32 nrval, ngval, nbval;
+
+ PROCNAME("pixelFractionalShift");
+
+ if (!ppixel)
+ return ERROR_INT("&pixel defined", procName, 1);
+ if (fract < -1.0 || fract > 1.0)
+ return ERROR_INT("fraction not in [-1 ... +1]", procName, 1);
+
+ nrval = (fract < 0) ? (l_int32)((1.0 + fract) * rval + 0.5) :
+ rval + (l_int32)(fract * (255 - rval) + 0.5);
+ ngval = (fract < 0) ? (l_int32)((1.0 + fract) * gval + 0.5) :
+ gval + (l_int32)(fract * (255 - gval) + 0.5);
+ nbval = (fract < 0) ? (l_int32)((1.0 + fract) * bval + 0.5) :
+ bval + (l_int32)(fract * (255 - bval) + 0.5);
+ composeRGBPixel(nrval, ngval, nbval, ppixel);
+ return 0;
+}
+
+
+/*!
+ * \brief pixMapWithInvariantHue()
+ *
+ * \param[in] pixd [optional]; either NULL or equal to pixs for in-place
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] srcval reference source color: 0xrrggbb00
+ * \param[in] fract fraction toward white of dest color
+ * \return pixd with all pixels mapped based on the srcval/destval mapping,
+ * or pixd on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The combination of %srcval and %fract define the linear
+ * hue-preserving transformation, that is applied to all pixels.
+ * (2) %fract is in the range [-1 .... +1]. If %fract < 0,
+ * saturation is increased and brightness is reduced. The
+ * opposite results if %fract > 0. If %fract == -1,
+ * %srcval is mapped to black; if %fract == 1, it is mapped to white.
+ * (3) For inplace operation, call it this way:
+ * pixMapWithInvariatHue(pixs, pixs, ... );
+ * For generating a new pixd:
+ * pixd = pixMapWithInvariantHue(NULL, pixs, ...);
+ * </pre>
+ */
+PIX *
+pixMapWithInvariantHue(PIX *pixd,
+ PIX *pixs,
+ l_uint32 srcval,
+ l_float32 fract)
+{
+l_int32 rval, gval, bval;
+l_uint32 dstval;
+
+ PROCNAME("pixMapWithInvariantHue");
+
+ 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 exists, but != pixs", procName, pixd);
+ if (fract < -1.0 || fract > 1.0)
+ return (PIX *)ERROR_PTR("fraction not in [-1 ... +1]", procName, NULL);
+
+ /* Generate the dstval that is %fract toward white from %srcval */
+ extractRGBValues(srcval, &rval, &gval, &bval);
+ pixelFractionalShift(rval, gval, bval, fract, &dstval);
+
+ /* Use the (%srcval, dstval) pair to define the linear transform */
+ return pixLinearMapToTargetColor(pixd, pixs, srcval, dstval);
+}
diff --git a/leptonica/src/colormap.c b/leptonica/src/colormap.c
new file mode 100644
index 00000000..c56c67cd
--- /dev/null
+++ b/leptonica/src/colormap.c
@@ -0,0 +1,2433 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file colormap.c
+ * <pre>
+ *
+ * Colormap creation, copy, destruction, addition
+ * PIXCMAP *pixcmapCreate()
+ * PIXCMAP *pixcmapCreateRandom()
+ * PIXCMAP *pixcmapCreateLinear()
+ * PIXCMAP *pixcmapCopy()
+ * void pixcmapDestroy()
+ * l_int32 pixcmapIsValid()
+ * 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 pixcmapGetDistanceToColor()
+ * l_int32 pixcmapGetRangeValues()
+ *
+ * Colormap conversion
+ * PIXCMAP *pixcmapGrayToFalseColor()
+ * PIXCMAP *pixcmapGrayToColor()
+ * PIXCMAP *pixcmapColorToGray()
+ * PIXCMAP *pixcmapConvertTo4()
+ * PIXCMAP *pixcmapConvertTo8()
+ *
+ * Colormap I/O
+ * l_int32 pixcmapRead()
+ * l_int32 pixcmapReadStream()
+ * l_int32 pixcmapReadMem()
+ * l_int32 pixcmapWrite()
+ * l_int32 pixcmapWriteStream()
+ * l_int32 pixcmapWriteMem()
+ *
+ * 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()
+ *
+ * Note:
+ * (1) colormaps in leptonica have a maximum of 256 entries.
+ * (2) nalloc, the allocated size of the palette array, is related
+ * to the depth d of the pixels by:
+ * nalloc = 2^(d)
+ *
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include <string.h>
+#include "allheaders.h"
+
+/*-------------------------------------------------------------*
+ * Colormap creation and addition *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixcmapCreate()
+ *
+ * \param[in] depth of pix, in bpp
+ * \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);
+
+ cmap = (PIXCMAP *)LEPT_CALLOC(1, sizeof(PIXCMAP));
+ cmap->depth = depth;
+ cmap->nalloc = 1 << depth;
+ cta = (RGBA_QUAD *)LEPT_CALLOC(cmap->nalloc, sizeof(RGBA_QUAD));
+ cmap->array = cta;
+ cmap->n = 0;
+ return cmap;
+}
+
+
+/*!
+ * \brief pixcmapCreateRandom()
+ *
+ * \param[in] depth of pix, in bpp: 2, 4 or 8
+ * \param[in] hasblack 1 if the first color is black; 0 if no black
+ * \param[in] haswhite 1 if the last color is white; 0 if no white
+ * \return cmap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixcmapCreateLinear()
+ *
+ * \param[in] d depth of pix for this colormap; 1, 2, 4 or 8
+ * \param[in] nlevels valid in range [2, 2^d]
+ * \return cmap, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Colormap has equally spaced gray color values
+ * from black (0, 0, 0) to white (255, 255, 255).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixcmapCopy()
+ *
+ * \param[in] cmaps
+ * \return cmapd, or NULL on error
+ */
+PIXCMAP *
+pixcmapCopy(const PIXCMAP *cmaps)
+{
+l_int32 nbytes, valid;
+PIXCMAP *cmapd;
+
+ PROCNAME("pixcmapCopy");
+
+ if (!cmaps)
+ return (PIXCMAP *)ERROR_PTR("cmaps not defined", procName, NULL);
+ pixcmapIsValid(cmaps, NULL, &valid);
+ if (!valid)
+ return (PIXCMAP *)ERROR_PTR("invalid cmap", procName, NULL);
+
+ cmapd = (PIXCMAP *)LEPT_CALLOC(1, sizeof(PIXCMAP));
+ nbytes = cmaps->nalloc * sizeof(RGBA_QUAD);
+ cmapd->array = (void *)LEPT_CALLOC(1, nbytes);
+ memcpy(cmapd->array, cmaps->array, cmaps->n * sizeof(RGBA_QUAD));
+ cmapd->n = cmaps->n;
+ cmapd->nalloc = cmaps->nalloc;
+ cmapd->depth = cmaps->depth;
+ return cmapd;
+}
+
+
+/*!
+ * \brief pixcmapDestroy()
+ *
+ * \param[in,out] pcmap set to null on return
+ * \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;
+}
+
+/*!
+ * \brief pixcmapIsValid()
+ *
+ * \param[in] cmap
+ * \param[in] pix optional; can be NULL
+ * \param[out] pvalid return 1 if valid; 0 if not
+ * \return 0 if OK, 1 on error or if cmap is not valid
+ *
+ * <pre>
+ * Notes:
+ * (1) If %pix is input, this will veify that pixel values cannot
+ * overflow the colormap. This is a relatively expensive operation
+ * that may need to check all the pixel values.
+ * (2) If %pix is input, there must be at least one color in the
+ * colormap if it is to be valid with any pix, even if the
+ * pixels are all 0.
+ * </pre>
+ */
+l_ok
+pixcmapIsValid(const PIXCMAP *cmap,
+ PIX *pix,
+ l_int32 *pvalid)
+{
+l_int32 d, depth, nalloc, maxindex, maxcolors;
+
+ PROCNAME("pixcmapIsValid");
+
+ if (!pvalid)
+ return ERROR_INT("&valid not defined", procName, 1);
+ *pvalid = 0;
+ if (!cmap)
+ return ERROR_INT("cmap not defined", procName, 1);
+ if (!cmap->array)
+ return ERROR_INT("cmap array not defined", procName, 1);
+ d = cmap->depth;
+ if (d != 1 && d != 2 && d != 4 && d != 8) {
+ L_ERROR("invalid cmap depth: %d\n", procName, d);
+ return 1;
+ }
+ nalloc = cmap->nalloc;
+ if (nalloc != (1 << d)) {
+ L_ERROR("invalid cmap nalloc = %d; d = %d\n", procName, nalloc, d);
+ return 1;
+ }
+ if (cmap->n < 0 || cmap->n > nalloc) {
+ L_ERROR("invalid cmap n: %d; nalloc = %d\n", procName, cmap->n, nalloc);
+ return 1;
+ }
+
+ /* If a pix is given, it must have a depth no larger than 8 */
+ if (pix) {
+ depth = pixGetDepth(pix);
+ if (depth > 8) {
+ L_ERROR("pix depth %d > 8\n", procName, depth);
+ return 1;
+ }
+ maxcolors = 1 << depth;
+ }
+
+ /* To prevent indexing overflow into the cmap, the pix depth
+ * must not exceed the cmap depth. Do not require depth equality,
+ * because some functions such as median cut quantizers allow
+ * the cmap depth to be bigger than the pix depth. */
+ if (pix && (depth > d)) {
+ L_ERROR("(pix depth = %d) > (cmap depth = %d)\n", procName, depth, d);
+ return 1;
+ }
+ if (pix && cmap->n < 1) {
+ L_ERROR("cmap array is empty; invalid with any pix\n", procName);
+ return 1;
+ }
+
+ /* Do not let the colormap have more colors than the pixels
+ * can address. The png encoder considers this to be an
+ * "invalid palette length". For example, for 1 bpp, the
+ * colormap may have a depth > 1, but it must not have more
+ * than 2 colors. */
+ if (pix && (cmap->n > maxcolors)) {
+ L_ERROR("cmap entries = %d > max colors for pix = %d\n", procName,
+ cmap->n, maxcolors);
+ return 1;
+ }
+
+ /* Where the colormap or the pix may have been corrupted, and
+ * in particular when reading or writing image files, it should
+ * be verified that the image pixel values do not exceed the
+ * max indexing into the colormap array. */
+ if (pix) {
+ pixGetMaxColorIndex(pix, &maxindex);
+ if (maxindex >= cmap->n) {
+ L_ERROR("(max index = %d) >= (num colors = %d)\n", procName,
+ maxindex, cmap->n);
+ return 1;
+ }
+ }
+
+ *pvalid = 1;
+ return 0;
+}
+
+
+/*!
+ * \brief pixcmapAddColor()
+ *
+ * \param[in] cmap
+ * \param[in] rval, gval, bval colormap entry to be added; each number
+ * is in range [0, ... 255]
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This always adds the color if there is room.
+ * (2) The alpha component is 255 (opaque)
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapAddRGBA()
+ *
+ * \param[in] cmap
+ * \param[in] rval, gval, bval, aval colormap entry to be added;
+ * each number is in range [0, ... 255]
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This always adds the color if there is room.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapAddNewColor()
+ *
+ * \param[in] cmap
+ * \param[in] rval, gval, bval colormap entry to be added; each number
+ * is in range [0, ... 255]
+ * \param[out] pindex index of color
+ * \return 0 if OK, 1 on error; 2 if unable to add color
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapAddNearestColor()
+ *
+ * \param[in] cmap
+ * \param[in] rval, gval, bval colormap entry to be added; each number
+ * is in range [0, ... 255]
+ * \param[out] pindex index of color
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapUsableColor()
+ *
+ * \param[in] cmap
+ * \param[in] rval, gval, bval colormap entry to be added; each number
+ * is in range [0, ... 255]
+ * \param[out] pusable 1 if usable; 0 if not
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This checks if the color already exists or if there is
+ * room to add it. It makes no change in the colormap.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapAddBlackOrWhite()
+ *
+ * \param[in] cmap
+ * \param[in] color 0 for black, 1 for white
+ * \param[out] pindex [optional] index of color; can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapSetBlackAndWhite()
+ *
+ * \param[in] cmap
+ * \param[in] setblack 0 for no operation; 1 to set darkest color to black
+ * \param[in] setwhite 0 for no operation; 1 to set lightest color to white
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapGetCount()
+ *
+ * \param[in] cmap
+ * \return count, or 0 on error
+ */
+l_int32
+pixcmapGetCount(const PIXCMAP *cmap)
+{
+ PROCNAME("pixcmapGetCount");
+
+ if (!cmap)
+ return ERROR_INT("cmap not defined", procName, 0);
+ return cmap->n;
+}
+
+
+/*!
+ * \brief pixcmapGetFreeCount()
+ *
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief pixcmapGetDepth()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixcmapGetMinDepth()
+ *
+ * \param[in] cmap
+ * \param[out] pmindepth minimum depth to support the colormap
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) On error, &mindepth is returned as 0.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapClear()
+ *
+ * \param[in] cmap
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This removes the colors by setting the count to 0.
+ * </pre>
+ */
+l_ok
+pixcmapClear(PIXCMAP *cmap)
+{
+ PROCNAME("pixcmapClear");
+
+ if (!cmap)
+ return ERROR_INT("cmap not defined", procName, 1);
+ cmap->n = 0;
+ return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ * Colormap random access *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixcmapGetColor()
+ *
+ * \param[in] cmap
+ * \param[in] index
+ * \param[out] prval, pgval, pbval each color value
+ * \return 0 if OK, 1 if not accessible caller should check
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapGetColor32()
+ *
+ * \param[in] cmap
+ * \param[in] index
+ * \param[out] pval32 32-bit rgb color value
+ * \return 0 if OK, 1 if not accessible caller should check
+ *
+ * <pre>
+ * Notes:
+ * (1) The returned alpha channel value is 255.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapGetRGBA()
+ *
+ * \param[in] cmap
+ * \param[in] index
+ * \param[out] prval, pgval, pbval, paval each color value
+ * \return 0 if OK, 1 if not accessible caller should check
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapGetRGBA32()
+ *
+ * \param[in] cmap
+ * \param[in] index
+ * \param[out] pval32 32-bit rgba color value
+ * \return 0 if OK, 1 if not accessible caller should check
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapResetColor()
+ *
+ * \param[in] cmap
+ * \param[in] index
+ * \param[in] 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
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapSetAlpha()
+ *
+ * \param[in] cmap
+ * \param[in] index
+ * \param[in] aval in range [0, ... 255]
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapGetIndex()
+ *
+ * \param[in] cmap
+ * \param[in] rval, gval, bval colormap colors to search for; each number
+ * is in range [0, ... 255]
+ * \param[out] pindex value of index found
+ * \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;
+}
+
+
+/*!
+ * \brief pixcmapHasColor()
+ *
+ * \param[in] cmap
+ * \param[out] pcolor TRUE if cmap has color; FALSE otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapIsOpaque()
+ *
+ * \param[in] cmap
+ * \param[out] popaque TRUE if fully opaque: all entries are 255
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapIsBlackAndWhite()
+ *
+ * \param[in] cmap
+ * \param[out] pblackwhite 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_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapCountGrayColors()
+ *
+ * \param[in] cmap
+ * \param[out] pngray number of gray colors
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This counts the unique gray colors, including black and white.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapGetRankIntensity()
+ *
+ * \param[in] cmap
+ * \param[in] rankval 0.0 for darkest, 1.0 for lightest color
+ * \param[out] pindex the index into the colormap that corresponds
+ * to the rank intensity color
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapGetNearestIndex()
+ *
+ * \param[in] cmap
+ * \param[in] rval, gval, bval colormap colors to search for; each number
+ * is in range [0, ... 255]
+ * \param[out] pindex the index of the nearest color
+ * \return 0 if OK, 1 on error caller must check
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapGetNearestGrayIndex()
+ *
+ * \param[in] cmap
+ * \param[in] val gray value to search for; in range [0, ... 255]
+ * \param[out] pindex the index of the nearest color
+ * \return 0 if OK, 1 on error caller must check
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapGetDistanceToColor()
+ *
+ * \param[in] cmap
+ * \param[in] index
+ * \param[in] rval, gval, bval target color
+ * \param[out] pdist the distance from the cmap entry to target
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Returns the L2 distance (squared) between the color at index i
+ * and the target color.
+ * </pre>
+ */
+l_ok
+pixcmapGetDistanceToColor(PIXCMAP *cmap,
+ l_int32 index,
+ l_int32 rval,
+ l_int32 gval,
+ l_int32 bval,
+ l_int32 *pdist)
+{
+l_int32 n, delta, dist;
+RGBA_QUAD *cta;
+
+ PROCNAME("pixcmapGetDistanceToColor");
+
+ if (!pdist)
+ return ERROR_INT("&dist not defined", procName, 1);
+ *pdist = UNDEF;
+ if (!cmap)
+ return ERROR_INT("cmap not defined", procName, 1);
+ n = pixcmapGetCount(cmap);
+ if (index >= n)
+ return ERROR_INT("invalid index", procName, 1);
+
+ if ((cta = (RGBA_QUAD *)cmap->array) == NULL)
+ return ERROR_INT("cta not defined(!)", procName, 1);
+
+ delta = cta[index].red - rval;
+ dist = delta * delta;
+ delta = cta[index].green - gval;
+ dist += delta * delta;
+ delta = cta[index].blue - bval;
+ dist += delta * delta;
+ *pdist = dist;
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixcmapGetRangeValues()
+ *
+ * \param[in] cmap
+ * \param[in] select L_SELECT_RED, L_SELECT_GREEN, L_SELECT_BLUE or
+ * L_SELECT_AVERAGE
+ * \param[out] pminval [optional] minimum value of component
+ * \param[out] pmaxval [optional] maximum value of component
+ * \param[out] pminindex [optional] index of minimum value
+ * \param[out] pmaxindex [optional] index of maximum value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Returns, for selected components (or the average), the
+ * the extreme values (min and/or max) and their indices
+ * that are found in the cmap.
+ * </pre>
+ */
+l_ok
+pixcmapGetRangeValues(PIXCMAP *cmap,
+ l_int32 select,
+ l_int32 *pminval,
+ l_int32 *pmaxval,
+ l_int32 *pminindex,
+ l_int32 *pmaxindex)
+{
+l_int32 i, n, imin, imax, minval, maxval, rval, gval, bval, aveval;
+
+ PROCNAME("pixcmapGetRangeValues");
+
+ if (pminval) *pminval = UNDEF;
+ if (pmaxval) *pmaxval = UNDEF;
+ if (pminindex) *pminindex = UNDEF;
+ if (pmaxindex) *pmaxindex = UNDEF;
+ if (!pminval && !pmaxval && !pminindex && !pmaxindex)
+ return ERROR_INT("no result requested", procName, 1);
+ if (!cmap)
+ return ERROR_INT("cmap not defined", procName, 1);
+
+ imin = UNDEF;
+ imax = UNDEF;
+ minval = 100000;
+ maxval = -1;
+ n = pixcmapGetCount(cmap);
+ for (i = 0; i < n; i++) {
+ pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+ if (select == L_SELECT_RED) {
+ if (rval < minval) {
+ minval = rval;
+ imin = i;
+ }
+ if (rval > maxval) {
+ maxval = rval;
+ imax = i;
+ }
+ } else if (select == L_SELECT_GREEN) {
+ if (gval < minval) {
+ minval = gval;
+ imin = i;
+ }
+ if (gval > maxval) {
+ maxval = gval;
+ imax = i;
+ }
+ } else if (select == L_SELECT_BLUE) {
+ if (bval < minval) {
+ minval = bval;
+ imin = i;
+ }
+ if (bval > maxval) {
+ maxval = bval;
+ imax = i;
+ }
+ } else if (select == L_SELECT_AVERAGE) {
+ aveval = (rval + gval + bval) / 3;
+ if (aveval < minval) {
+ minval = aveval;
+ imin = i;
+ }
+ if (aveval > maxval) {
+ maxval = aveval;
+ imax = i;
+ }
+ } else {
+ return ERROR_INT("invalid selection", procName, 1);
+ }
+ }
+
+ if (pminval) *pminval = minval;
+ if (pmaxval) *pmaxval = maxval;
+ if (pminindex) *pminindex = imin;
+ if (pmaxindex) *pmaxindex = imax;
+ return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ * Colormap conversion *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixcmapGrayToFalseColor()
+ *
+ * \param[in] gamma (factor) 0.0 or 1.0 for default; > 1.0 for brighter;
+ * 2.0 is quite nice
+ * \return cmap, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This creates a colormap that maps from gray to false colors.
+ * The colormap is modeled after the Matlap "jet" configuration.
+ * </pre>
+ */
+PIXCMAP *
+pixcmapGrayToFalseColor(l_float32 gamma)
+{
+l_int32 i, rval, gval, bval;
+l_int32 *curve;
+l_float32 invgamma, x;
+PIXCMAP *cmap;
+
+ if (gamma <= 0.0) gamma = 1.0;
+
+ /* Generate curve for transition part of color map */
+ curve = (l_int32 *)LEPT_CALLOC(64, sizeof(l_int32));
+ invgamma = 1. / gamma;
+ for (i = 0; i < 64; i++) {
+ x = (l_float32)i / 64.;
+ curve[i] = (l_int32)(255. * powf(x, invgamma) + 0.5);
+ }
+
+ cmap = pixcmapCreate(8);
+ 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 cmap;
+}
+
+
+/*!
+ * \brief pixcmapGrayToColor()
+ *
+ * \param[in] color
+ * \return cmap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixcmapColorToGray()
+ *
+ * \param[in] cmaps
+ * \param[in] rwt, gwt, bwt non-negative; these should add to 1.0
+ * \return cmap gray, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.33333f;
+ 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;
+ }
+
+ if ((cmapd = pixcmapCopy(cmaps)) == NULL)
+ return (PIXCMAP *)ERROR_PTR("cmapd not made", procName, NULL);
+ 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;
+}
+
+
+/*!
+ * \brief pixcmapConvertTo4()
+ *
+ * \param[in] cmaps colormap for 2 bpp pix
+ * \return cmapd (4 bpp)
+ *
+ * <pre>
+ * Notes:
+ * (1) This converts a 2 bpp colormap to 4 bpp. The colors
+ * are the same; the output colormap entry array has size 16.
+ * </pre>
+ */
+PIXCMAP *
+pixcmapConvertTo4(PIXCMAP *cmaps)
+{
+l_int32 i, n, rval, gval, bval;
+PIXCMAP *cmapd;
+
+ PROCNAME("pixcmapConvertTo4");
+
+ if (!cmaps)
+ return (PIXCMAP *)ERROR_PTR("cmaps not defined", procName, NULL);
+ if (pixcmapGetDepth(cmaps) != 2)
+ return (PIXCMAP *)ERROR_PTR("cmaps not for 2 bpp pix", procName, NULL);
+
+ cmapd = pixcmapCreate(4);
+ n = pixcmapGetCount(cmaps);
+ for (i = 0; i < n; i++) {
+ pixcmapGetColor(cmaps, i, &rval, &gval, &bval);
+ pixcmapAddColor(cmapd, rval, gval, bval);
+ }
+ return cmapd;
+}
+
+
+/*!
+ * \brief pixcmapConvertTo8()
+ *
+ * \param[in] cmaps colormap for 2 bpp or 4 bpp pix
+ * \return cmapd (8 bpp)
+ *
+ * <pre>
+ * Notes:
+ * (1) This converts a 2 bpp or 4 bpp colormap to 8 bpp. The colors
+ * are the same; the output colormap entry array has size 256.
+ * </pre>
+ */
+PIXCMAP *
+pixcmapConvertTo8(PIXCMAP *cmaps)
+{
+l_int32 i, n, depth, rval, gval, bval;
+PIXCMAP *cmapd;
+
+ PROCNAME("pixcmapConvertTo8");
+
+ if (!cmaps)
+ return (PIXCMAP *)ERROR_PTR("cmaps not defined", procName, NULL);
+ depth = pixcmapGetDepth(cmaps);
+ if (depth == 8) return pixcmapCopy(cmaps);
+ if (depth != 2 && depth != 4)
+ return (PIXCMAP *)ERROR_PTR("cmaps not 2 or 4 bpp", procName, NULL);
+
+ cmapd = pixcmapCreate(8);
+ n = pixcmapGetCount(cmaps);
+ for (i = 0; i < n; i++) {
+ pixcmapGetColor(cmaps, i, &rval, &gval, &bval);
+ pixcmapAddColor(cmapd, rval, gval, bval);
+ }
+ return cmapd;
+}
+
+
+/*-------------------------------------------------------------*
+ * Colormap I/O *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixcmapRead()
+ *
+ * \param[in] 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);
+ cmap = pixcmapReadStream(fp);
+ fclose(fp);
+ if (!cmap)
+ return (PIXCMAP *)ERROR_PTR("cmap not read", procName, NULL);
+ return cmap;
+}
+
+
+/*!
+ * \brief pixcmapReadStream()
+ *
+ * \param[in] fp file 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) {
+ pixcmapDestroy(&cmap);
+ return (PIXCMAP *)ERROR_PTR("invalid entry", procName, NULL);
+ }
+ pixcmapAddRGBA(cmap, rval, gval, bval, aval);
+ }
+ return cmap;
+}
+
+
+/*!
+ * \brief pixcmapReadMem()
+ *
+ * \param[in] data serialization of pixcmap; in ascii
+ * \param[in] size of data in bytes; can use strlen to get it
+ * \return cmap, or NULL on error
+ */
+PIXCMAP *
+pixcmapReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+PIXCMAP *cmap;
+
+ PROCNAME("pixcmapReadMem");
+
+ if (!data)
+ return (PIXCMAP *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (PIXCMAP *)ERROR_PTR("stream not opened", procName, NULL);
+
+ cmap = pixcmapReadStream(fp);
+ fclose(fp);
+ if (!cmap) L_ERROR("cmap not read\n", procName);
+ return cmap;
+}
+
+
+/*!
+ * \brief pixcmapWrite()
+ *
+ * \param[in] filename
+ * \param[in] cmap
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixcmapWrite(const char *filename,
+ const PIXCMAP *cmap)
+{
+l_int32 ret;
+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);
+ ret = pixcmapWriteStream(fp, cmap);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("cmap not written to stream", procName, 1);
+ return 0;
+}
+
+
+
+/*!
+ * \brief pixcmapWriteStream()
+ *
+ * \param[in] fp file stream
+ \param[in] cmap
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixcmapWriteStream(FILE *fp,
+ const 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;
+}
+
+
+/*!
+ * \brief pixcmapWriteMem()
+ *
+ * \param[out] pdata data of serialized pixcmap; ascii
+ * \param[out] psize size of returned data
+ * \param[in] cmap
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a pixcmap in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+pixcmapWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ const PIXCMAP *cmap)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("pixcmapWriteMem");
+
+ 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 (!cmap)
+ return ERROR_INT("cmap not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = pixcmapWriteStream(fp, cmap);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = pixcmapWriteStream(fp, cmap);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Extract colormap arrays and serialization *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixcmapToArrays()
+ *
+ * \param[in] cmap colormap
+ * \param[out] prmap array of red values
+ * \param[out] pgmap array of green values
+ * \param[out] pbmap array of blue values
+ * \param[out] pamap [optional] array of alpha (transparency) values
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+pixcmapToArrays(const 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);
+ rmap = (l_int32 *)LEPT_CALLOC(ncolors, sizeof(l_int32));
+ gmap = (l_int32 *)LEPT_CALLOC(ncolors, sizeof(l_int32));
+ bmap = (l_int32 *)LEPT_CALLOC(ncolors, sizeof(l_int32));
+ *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;
+}
+
+
+/*!
+ * \brief pixcmapToRGBTable()
+ *
+ * \param[in] cmap colormap
+ * \param[out] ptab table of rgba values for the colormap
+ * \param[out] pncolors [optional] size of table
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+ tab = (l_uint32 *)LEPT_CALLOC(ncolors, sizeof(l_uint32));
+ *ptab = tab;
+
+ for (i = 0; i < ncolors; i++) {
+ pixcmapGetRGBA(cmap, i, &rval, &gval, &bval, &aval);
+ composeRGBAPixel(rval, gval, bval, aval, &tab[i]);
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief pixcmapSerializeToMemory()
+ *
+ * \param[in] cmap colormap
+ * \param[in] cpc components/color: 3 for rgb, 4 for rgba
+ * \param[out] pncolors number of colors in table
+ * \param[out] pdata binary string, cpc bytes per color
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) When serializing to store in a pdf, use %cpc = 3.
+ * </pre>
+ */
+l_ok
+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;
+ data = (l_uint8 *)LEPT_CALLOC((size_t)cpc * ncolors, sizeof(l_uint8));
+ *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;
+}
+
+
+/*!
+ * \brief pixcmapDeserializeFromMemory()
+ *
+ * \param[in] data binary string, 3 or 4 bytes per color
+ * \param[in] cpc components/color: 3 for rgb, 4 for rgba
+ * \param[in] ncolors > 0
+ * \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;
+}
+
+
+/*!
+ * \brief pixcmapConvertToHex()
+ *
+ * \param[in] data binary serialized data
+ * \param[in] ncolors in colormap
+ * \return hexdata bracketed, space-separated ascii hex string,
+ * or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixcmapGammaTRC()
+ *
+ * \param[in] cmap colormap
+ * \param[in] gamma gamma correction; must be > 0.0
+ * \param[in] minval input value that gives 0 for output; can be < 0
+ * \param[in] maxval input value that gives 255 for output; can be > 255
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is an in-place transform
+ * (2) See pixGammaTRC() and numaGammaTRC() in enhance.c
+ * for description and use of transform
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapContrastTRC()
+ *
+ * \param[in] cmap colormap
+ * \param[in] factor generally between 0.0 [no enhancement]
+ * and 1.0, but can be larger than 1.0
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is an in-place transform
+ * (2) See pixContrastTRC() and numaContrastTRC() in enhance.c
+ * for description and use of transform
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapShiftIntensity()
+ *
+ * \param[in] cmap colormap
+ * \param[in] fraction between -1.0 and +1.0
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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()).
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapShiftByComponent()
+ *
+ * \param[in] cmap colormap
+ * \param[in] srcval source color: 0xrrggbb00
+ * \param[in] dstval target color: 0xrrggbb00
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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/leptonica/src/colormorph.c b/leptonica/src/colormorph.c
new file mode 100644
index 00000000..e59b7891
--- /dev/null
+++ b/leptonica/src/colormorph.c
@@ -0,0 +1,128 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file colormorph.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*-----------------------------------------------------------------*
+ * Top-level color morphological operations *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixColorMorph()
+ *
+ * \param[in] pixs
+ * \param[in] type L_MORPH_DILATE, L_MORPH_ERODE, L_MORPH_OPEN,
+ * or L_MORPH_CLOSE
+ * \param[in] hsize width of Sel; must be odd; origin implicitly in center
+ * \param[in] vsize ditto for height of Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/colorquant1.c b/leptonica/src/colorquant1.c
new file mode 100644
index 00000000..03eadbe3
--- /dev/null
+++ b/leptonica/src/colorquant1.c
@@ -0,0 +1,4157 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file colorquant1.c
+ * <pre>
+ *
+ * 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()
+ *
+ * Notes:
+ * Leptonica also provides color quantization using a modified
+ * form of median cut. See colorquant2.c for details.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*
+ * <pre>
+ * 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 CqNLevels = 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.
+ * </pre>
+ */
+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 CqNLevels = 5; /* only 4, 5 and 6 are allowed */
+static const l_int32 CqReservedColors = 64; /* to allow for level 2 */
+ /* remainder CTEs */
+static const l_int32 ExtraReservedColors = 25; /* to avoid running out */
+static const l_int32 TreeGenWidth = 350; /* big enough for good stats */
+static const l_int32 MinDitherSize = 250; /* don't dither if smaller */
+
+/*
+ * <pre>
+ * 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.
+ * </pre>
+ * */
+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;
+
+/*
+ * <pre>
+ * This data structure is using for heap sorting octcubes
+ * by population. Sort order is decreasing.
+ * </pre>
+ */
+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;
+
+/*
+ * <pre>
+ * 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.
+ * </pre>
+ */
+
+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 *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief pixOctreeColorQuant()
+ *
+ * \param[in] pixs 32 bpp; 24-bit color
+ * \param[in] colors in colormap; some number in range [128 ... 256];
+ * the actual number of colors used will be smaller
+ * \param[in] ditherflag 1 to dither, 0 otherwise
+ * \return pixd 8 bpp with colormap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.01f, 0.01f);
+}
+
+
+/*!
+ * \brief pixOctreeColorQuantGeneral()
+ *
+ * \param[in] pixs 32 bpp; 24-bit color
+ * \param[in] colors in colormap; some number in range [128 ... 240];
+ * the actual number of colors used will be smaller
+ * \param[in] ditherflag 1 to dither, 0 otherwise
+ * \param[in] 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
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 > TreeGenWidth) {
+ scalefactor = (l_float32)TreeGenWidth / (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 < MinDitherSize && h < MinDitherSize)
+ colors = L_MIN(colors, 220);
+
+ /* Make the pruned octree */
+ cqcaa = octreeGenerateAndPrune(pixsub, colors, CqReservedColors, &cmap);
+ if (!cqcaa) {
+ pixDestroy(&pixsub);
+ 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 < MinDitherSize && h < MinDitherSize && 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) {
+ pixDestroy(&pixsub);
+ cqcellTreeDestroy(&cqcaa);
+ 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;
+}
+
+
+/*!
+ * \brief octreeGenerateAndPrune()
+ *
+ * \param[in] pixs
+ * \param[in] colors number of colors to use between 128 and 256
+ * \param[in] reservedcolors number of reserved colors
+ * \param[out] pcmap colormap returned
+ * \return octree, colormap and number of colors used, or NULL
+ * on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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.01f, 0.01f, 1.0f, 1.0f, 1.0f, 1.0f};
+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);
+
+ if ((cqcaa = cqcellTreeCreate()) == NULL)
+ return (CQCELL ***)ERROR_PTR("cqcaa not made", procName, NULL);
+
+ /* Make the canonical index tables */
+ rtab = gtab = btab = NULL;
+ makeRGBToIndexTables(CqNLevels, &rtab, &gtab, &btab);
+
+ /* 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 - ExtraReservedColors;
+ ppc = npix / ncolor;
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+
+ /* Accumulate the centers of each cluster at level CqNLevels */
+ ncells = 1 << (3 * CqNLevels);
+ cqca = cqcaa[CqNLevels];
+ 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 */
+ nat = numaCreate(0);
+ nar = numaCreate(0);
+
+ /* Prune back from the lowest level and generate the colormap */
+ for (level = CqNLevels - 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. Do something. */
+ L_ERROR("assigning pixels to wrong color\n", procName);
+ pixcmapGetNearestIndex(cmap, 128, 128, 128, &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
+ lept_stderr("Exceeds threshold: colors used = %d, colors remaining = %d\n",
+ cmap->n, ncolor + reservedcolors);
+ lept_stderr(" cell with %d pixels, npix = %d, ppc = %d\n",
+ cqcsub->n, npix, ppc);
+ lept_stderr(" index = %d, level = %d, subindex = %d\n",
+ i, level, j);
+ lept_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
+ * ExtraReservedColors. */
+ 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
+ lept_stderr("By remainder: colors used = %d, colors remaining = %d\n",
+ cmap->n, ncolor + reservedcolors);
+ lept_stderr(" cell with %d pixels, npix = %d, ppc = %d\n",
+ cqc->n, npix, ppc);
+ lept_stderr(" index = %d, level = %d\n", i, level);
+ lept_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]++;
+ }
+ lept_stderr(" Threshold cells formed: %d\n", nt);
+ for (i = 1; i < CqNLevels + 1; i++)
+ lept_stderr(" level %d: %d\n", i, tc[i]);
+ lept_stderr("\n Residual cells formed: %d\n", nr);
+ for (i = 0; i < CqNLevels ; i++)
+ lept_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;
+}
+
+
+/*!
+ * \brief pixOctreeQuantizePixels()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] cqcaa octree in array format
+ * \param[in] ditherflag 1 for dithering, 0 for no dithering
+ * \return pixd or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, success;
+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 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);
+
+ /* Make the canonical index tables */
+ rtab = gtab = btab = NULL;
+ makeRGBToIndexTables(CqNLevels, &rtab, &gtab, &btab);
+
+ /* 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 */
+ success = TRUE;
+ bufu8r = bufu8g = bufu8b = NULL;
+ buf1r = buf1g = buf1b = buf2r = buf2g = buf2b = NULL;
+ 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 || !buf1r || !buf1g ||
+ !buf1b || !buf2r || !buf2g || !buf2b) {
+ L_ERROR("buffer not made\n", procName);
+ success = FALSE;
+ goto buffer_cleanup;
+ }
+
+ /* 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 {
+ 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 {
+ 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 {
+ 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);
+ }
+
+buffer_cleanup:
+ 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);
+ if (!success) pixDestroy(&pixd);
+ }
+
+ LEPT_FREE(rtab);
+ LEPT_FREE(gtab);
+ LEPT_FREE(btab);
+ return pixd;
+}
+
+
+/*!
+ * \brief octreeFindColorCell()
+ *
+ * \param[in] octindex
+ * \param[in] cqcaa
+ * \param[out] pindex index of CTE; returned to set pixel value
+ * \param[out] prval of CTE
+ * \param[out] pgval of CTE
+ * \param[out] pbval of CTE
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 < CqNLevels; 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 == CqNLevels - 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 < CqNLevels; 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 == CqNLevels - 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief cqcellTreeCreate()
+ *
+ * \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(CqNLevels + 1, sizeof(CQCELL **)))
+ == NULL)
+ return (CQCELL ***)ERROR_PTR("cqcaa not made", procName, NULL);
+ for (level = 0; level <= CqNLevels; level++) {
+ ncells = 1 << (3 * level);
+ if ((cqca = (CQCELL **)LEPT_CALLOC(ncells, sizeof(CQCELL *))) == NULL) {
+ cqcellTreeDestroy(&cqcaa);
+ 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) {
+ cqcellTreeDestroy(&cqcaa);
+ return (CQCELL ***)ERROR_PTR("cqc not made", procName, NULL);
+ }
+ }
+ }
+
+ return cqcaa;
+}
+
+
+/*!
+ * \brief cqcellTreeDestroy()
+ *
+ * \param[in,out] pcqcaa will be set to null before returning
+ */
+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 <= CqNLevels; 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief makeRGBToIndexTables()
+ *
+ * \param[in] cqlevels can be 1, 2, 3, 4, 5 or 6
+ * \param[out] prtab, pgtab, pbtab tables
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+makeRGBToIndexTables(l_int32 cqlevels,
+ l_uint32 **prtab,
+ l_uint32 **pgtab,
+ l_uint32 **pbtab)
+{
+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("not all &tabs defined", 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));
+ if (!rtab || !gtab || !btab)
+ return ERROR_INT("calloc fail for tab", 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;
+}
+
+
+/*!
+ * \brief getOctcubeIndexFromRGB()
+ *
+ * \param[in] rval, gval, bval
+ * \param[in] rtab, gtab, btab generated with makeRGBToIndexTables()
+ * \param[out] pindex found index
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * No error checking!
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief getRGBFromOctcube()
+ *
+ * \param[in] cubeindex octcube index
+ * \param[in] level at which index is expressed
+ * \param[out] prval r val of this cube
+ * \param[out] pgval g val of this cube
+ * \param[out] pbval b val of this cube
+ * \return void
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief getOctcubeIndices()
+ *
+ * \param[in] rgbindex
+ * \param[in] level octree level 0, 1, 2, 3, 4, 5
+ * \param[out] pbindex base index index at the octree level
+ * \param[out] psindex sub index index at the next lower level
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * for CqNLevels = 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 CqNLevels = 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 CqNLevels = 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)
+ * </pre>
+ */
+static l_int32
+getOctcubeIndices(l_int32 rgbindex,
+ l_int32 level,
+ l_int32 *pbindex,
+ l_int32 *psindex)
+{
+ PROCNAME("getOctcubeIndex");
+
+ if (level < 0 || level > CqNLevels - 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 * (CqNLevels - level));
+ *psindex = rgbindex >> (3 * (CqNLevels - 1 - level));
+ return 0;
+}
+
+
+/*!
+ * \brief octcubeGetCount()
+ *
+ * \param[in] level valid values are in [1,...6]; there are 2^level
+ * cubes along each side of the rgb cube
+ * \param[out] psize 2^(3 * level) cubes in the entire rgb cube
+ * \return 0 if OK, 1 on error. Caller must check!
+ *
+ * <pre>
+ * level: 1 2 3 4 5 6
+ * size: 8 64 512 4098 32784 262272
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixOctreeQuantByPopulation()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] level significant bits for each of RGB; valid for {3,4}.
+ * Use 0 for default (level 4; recommended
+ * \param[in] ditherflag 1 to dither, 0 otherwise
+ * \return pixd quantized to octcubes or NULL on error
+ *
+ * <pre>
+ * 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 minimum required to support 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.
+ * </pre>
+ */
+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 < MinDitherSize && h < MinDitherSize && 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);
+ rtab = gtab = btab = NULL;
+ makeRGBToIndexTables(level, &rtab, &gtab, &btab);
+
+ pixd = NULL;
+ narray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+ rarray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+ garray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+ barray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+ if (!narray || !rarray || !garray || !barray)
+ goto array_cleanup;
+
+ /* Place the pixels in octcube leaves. */
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+ 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. */
+ iarray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+ 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
+ lept_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. */
+ rtab2 = gtab2 = btab2 = NULL;
+ makeRGBToIndexTables(2, &rtab2, &gtab2, &btab2);
+ 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++)
+ lept_stderr("%d ", iarray[16 * i + j]);
+ lept_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;
+}
+
+
+/*!
+ * \brief pixDitherOctindexWithCmap()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] pixd 8 bpp cmapped
+ * \param[in] rtab, gtab, btab tables from rval to octindex
+ * \param[in] indexmap array mapping octindex to cmap index
+ * \param[in] difcap max allowed dither transfer;
+ * use 0 for infinite cap
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, success;
+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);
+
+ success = TRUE;
+ bufu8r = bufu8g = bufu8b = NULL;
+ buf1r = buf1g = buf1b = buf2r = buf2g = buf2b = NULL;
+ 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 || !buf1r || !buf1g ||
+ !buf1b || !buf2r || !buf2g || !buf2b) {
+ L_ERROR("buffer not made\n", procName);
+ success = FALSE;
+ goto buffer_cleanup;
+ }
+
+ /* 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 {
+ 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 {
+ 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 {
+ 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);
+ }
+
+buffer_cleanup:
+ 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 (success) ? 0 : 1;
+}
+
+
+/*---------------------------------------------------------------------------*
+ * Adaptive octree quantization to 4 and 8 bpp with max colors *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixOctreeQuantNumColors()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] maxcolors 8 to 256; the actual number of colors used
+ * may be less than this
+ * \param[in] subsample factor for computing color distribution;
+ * use 0 for default
+ * \return pixd 4 or 8 bpp, colormapped, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (maxcolors < 8) {
+ L_WARNING("max colors < 8; setting to 8\n", procName);
+ maxcolors = 8;
+ }
+ if (maxcolors > 256) {
+ L_WARNING("max colors > 256; setting to 256\n", procName);
+ maxcolors = 256;
+ }
+
+ 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 <= 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 { /* maxcolors <= 256 */
+ bpp = 8;
+ pixd = pixCreate(w, h, bpp);
+ maxlevel = 3;
+ ncubes = 512; /* 2^9 */
+ nbase = 64;
+ nextra = maxcolors - nbase;
+ }
+
+ 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) {
+ pixDestroy(&pixd);
+ 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;
+ }
+
+ rtab = gtab = btab = NULL;
+ makeRGBToIndexTables(maxlevel - 1, &rtab, &gtab, &btab);
+
+ /* 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);
+/* lept_stderr("rval = %d, gval = %d, bval = %d,"
+ " index = %d\n", rval, gval, bval, index); */
+ if (bpp == 4)
+ SET_DATA_QBIT(lined, j, index);
+ else /* bpp == 8 */
+ SET_DATA_BYTE(lined, j, index);
+ 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) {
+ pixDestroy(&pixd);
+ 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' */
+ rtab = gtab = btab = NULL;
+ makeRGBToIndexTables(maxlevel, &rtab, &gtab, &btab);
+
+ /* 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 */
+ oqca = (OQCELL **)LEPT_CALLOC(maxcolors, sizeof(OQCELL *));
+ 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 */
+ lut1 = (l_int32 *)LEPT_CALLOC(ncubes, sizeof(l_int32));
+ 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 */
+/* lept_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);
+/* lept_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:
+ LEPT_FREE(oqca);
+ LEPT_FREE(lut1);
+ 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 */
+ lut2 = (l_int32 *)LEPT_CALLOC(ncubes, sizeof(l_int32));
+ 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);
+/* lept_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;
+ }
+ }
+ }
+ }
+
+ if (oqca) {
+ 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 *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief pixOctcubeQuantMixedWithGray()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] depth of output pix
+ * \param[in] graylevels graylevels (must be > 1)
+ * \param[in] delta threshold for deciding if a pix is color or gray
+ * \return pixd quantized to octcube and gray levels or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 (graylevels < 2)
+ return (PIX *)ERROR_PTR("invalid graylevels", 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);
+ }
+
+ pixd = NULL;
+
+ /* Make octcube index tables */
+ rtab = gtab = btab = NULL;
+ makeRGBToIndexTables(octlevels, &rtab, &gtab, &btab);
+
+ /* Make octcube arrays for storing points in each cube */
+ carray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+ rarray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+ garray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+ barray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+
+ /* Make lookup table, using computed thresholds */
+ tabval = makeGrayQuantIndexTable(graylevels);
+ if (!rtab || !gtab || !btab ||
+ !carray || !rarray || !garray || !barray || !tabval) {
+ L_ERROR("calloc fail for an array\n", procName);
+ goto array_cleanup;
+ }
+
+ /* Make colormapped output pixd */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ if ((pixd = pixCreate(w, h, depth)) == NULL) {
+ L_ERROR("pixd not made\n", procName);
+ goto array_cleanup;
+ }
+ 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]);
+ }
+ }
+
+array_cleanup:
+ 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 *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief pixFixedOctcubeQuant256()
+ *
+ * \param[in] pixs 32 bpp; 24-bit color
+ * \param[in] ditherflag 1 for dithering; 0 for no dithering
+ * \return pixd 8 bit with colormap, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * 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.
+ * </pre>
+ */
+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 < MinDitherSize && h < MinDitherSize && 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) {
+ pixcmapDestroy(&cmap);
+ 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));
+ if (!rtab || !gtab || !btab || !itab) {
+ pixDestroy(&pixd);
+ return (PIX *)ERROR_PTR("calloc fail for table", procName, NULL);
+ }
+ 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixFewColorsOctcubeQuant1()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] level significant bits for each of RGB; valid in [1...6]
+ * \return pixd quantized to octcube or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ pixd = NULL;
+
+ if (octcubeGetCount(level, &size)) /* array size = 2 ** (3 * level) */
+ return (PIX *)ERROR_PTR("size not returned", procName, NULL);
+ rtab = gtab = btab = NULL;
+ makeRGBToIndexTables(level, &rtab, &gtab, &btab);
+
+ carray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+ rarray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+ garray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+ barray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32));
+ if (!carray || !rarray || !garray || !barray) {
+ L_ERROR("calloc fail for an array\n", procName);
+ goto array_cleanup;
+ }
+
+ /* Place the pixels in octcube leaves. */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+ 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;
+}
+
+
+/*!
+ * \brief pixFewColorsOctcubeQuant2()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] level of octcube indexing, for histogram: 3, 4, 5, 6
+ * \param[in] na histogram of pixel occupation in octree leaves
+ * at given level
+ * \param[in] ncolors number of occupied octree leaves at given level
+ * \param[out] pnerrors [optional] num of pixels not exactly
+ * represented in the colormap
+ * \return pixd 2, 4 or 8 bpp with colormap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+
+ pixd = NULL;
+
+ /* Represent the image with a set of leaf octcubes
+ * at 'level', one for each color. */
+ rtab = gtab = btab = NULL;
+ makeRGBToIndexTables(level, &rtab, &gtab, &btab);
+
+ /* The octarray will give a ptr from the octcube to the colorarray */
+ ncubes = numaGetCount(na);
+ octarray = (l_int32 *)LEPT_CALLOC(ncubes, sizeof(l_int32));
+
+ /* 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. */
+ colorarray = (l_uint32 *)LEPT_CALLOC(ncolors + 1, sizeof(l_uint32));
+ if (!octarray || !colorarray) {
+ L_ERROR("octarray or colorarray not made\n", procName);
+ goto cleanup_arrays;
+ }
+
+ /* 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) {
+ L_ERROR("pixd not made\n", procName);
+ goto cleanup_arrays;
+ }
+ pixCopyResolution(pixd, pixs);
+ pixCopyInputFormat(pixd, pixs);
+ datad = pixGetData(pixd);
+ wpld = pixGetWpl(pixd);
+
+ /* 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
+ lept_stderr("ncubes = %d, ncolors = %d\n", ncubes, ncolors);
+ for (i = 0; i < ncolors; i++)
+ lept_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);
+
+cleanup_arrays:
+ LEPT_FREE(octarray);
+ LEPT_FREE(colorarray);
+ LEPT_FREE(rtab);
+ LEPT_FREE(gtab);
+ LEPT_FREE(btab);
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixFewColorsOctcubeQuantMixed()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] level significant octcube bits for each of RGB;
+ * valid in [1...6]; use 0 for default
+ * \param[in] 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
+ * \param[in] 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
+ * \param[in] diffthresh thresh for the max difference between component
+ * values; for differences below this, the pixel
+ * is considered to be gray; use 0 for default
+ * \param[in] minfract min fraction of pixels for gray histo bin;
+ * use 0.0 for default
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *pix1, *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.05f;
+ if (maxspan <= 2) maxspan = 15;
+
+ /* Start with a simple fixed octcube quantizer. */
+ if ((pix1 = pixFewColorsOctcubeQuant1(pixs, level)) == NULL)
+ return (PIX *)ERROR_PTR("too many colors", procName, NULL);
+ pixc = pixConvertTo8(pix1, 1); /* must be 8 bpp */
+ pixDestroy(&pix1);
+
+ /* 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixFixedOctcubeQuantGenRGB()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] level significant bits for each of r,g,b
+ * \return pixd rgb; quantized to octcube centers, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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(level, &rtab, &gtab, &btab))
+ 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixQuantFromCmap()
+ *
+ * \param[in] pixs 8 bpp grayscale without cmap, or 32 bpp rgb
+ * \param[in] cmap to quantize to; insert copy into dest pix
+ * \param[in] mindepth minimum depth of pixd: can be 2, 4 or 8 bpp
+ * \param[in] level of octcube used for finding nearest color in cmap
+ * \param[in] metric L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE
+ * \return pixd 2, 4 or 8 bpp, colormapped, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+
+/*!
+ * \brief pixOctcubeQuantFromCmap()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] cmap to quantize to; insert copy into dest pix
+ * \param[in] mindepth minimum depth of pixd: can be 2, 4 or 8 bpp
+ * \param[in] level of octcube used for finding nearest color in cmap
+ * \param[in] metric L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE
+ * \return pixd 2, 4 or 8 bpp, colormapped, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+ rtab = gtab = btab = NULL;
+ makeRGBToIndexTables(level, &rtab, &gtab, &btab);
+ cmaptab = pixcmapToOctcubeLUT(cmap, level, metric);
+
+ pixd = pixOctcubeQuantFromCmapLUT(pixs, cmap, mindepth,
+ cmaptab, rtab, gtab, btab);
+
+ LEPT_FREE(cmaptab);
+ LEPT_FREE(rtab);
+ LEPT_FREE(gtab);
+ LEPT_FREE(btab);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixOctcubeQuantFromCmapLUT()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] cmap to quantize to; insert copy into dest pix
+ * \param[in] mindepth minimum depth of pixd: can be 2, 4 or 8 bpp
+ * \param[in] cmaptab table mapping from octindex to colormap index
+ * \param[in] rtab, gtab, btab tables mapping from RGB to octindex
+ * \return pixd 2, 4 or 8 bpp, colormapped, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixOctcubeHistogram()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] level significant bits for each of RGB; valid in [1...6]
+ * \param[out] pncolors [optional] number of occupied cubes
+ * \return numa histogram of color pixels, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Input NULL for &ncolors to prevent computation and return value.
+ * </pre>
+ */
+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);
+ rtab = gtab = btab = NULL;
+ makeRGBToIndexTables(level, &rtab, &gtab, &btab);
+
+ if ((na = numaCreate(size)) == NULL) {
+ L_ERROR("na not made\n", procName);
+ goto cleanup_arrays;
+ }
+ 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)) {
+ lept_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;
+ }
+
+cleanup_arrays:
+ LEPT_FREE(rtab);
+ LEPT_FREE(gtab);
+ LEPT_FREE(btab);
+ return na;
+}
+
+
+/*------------------------------------------------------------------*
+ * Get filled octcube table from colormap *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixcmapToOctcubeLUT()
+ *
+ * \param[in] cmap
+ * \param[in] level significant bits for each of RGB; valid in [1...6]
+ * \param[in] metric L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE
+ * \return tab[2**3 * level]
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRemoveUnusedColors()
+ *
+ * \param[in] pixs colormapped
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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:
+ LEPT_FREE(histo);
+ 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 */
+ map1 = (l_int32 *)LEPT_CALLOC(nc, sizeof(l_int32));
+ map2 = (l_int32 *)LEPT_CALLOC(nc, sizeof(l_int32));
+ 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:
+ LEPT_FREE(histo);
+ LEPT_FREE(map1);
+ LEPT_FREE(map2);
+ 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixNumberOccupiedOctcubes()
+ *
+ * \param[in] pix 32 bpp
+ * \param[in] level of octcube
+ * \param[in] mincount minimum num pixels in an octcube to be counted;
+ * -1 to not use
+ * \param[in] minfract minimum fract of pixels in an octcube to be
+ * counted; -1 to not use
+ * \param[out] pncolors number of occupied octcubes
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+ rtab = gtab = btab = NULL;
+ makeRGBToIndexTables(level, &rtab, &gtab, &btab);
+ if ((carray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL) {
+ L_ERROR("carray not made\n", procName);
+ goto cleanup_arrays;
+ }
+
+ /* 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;
+
+cleanup_arrays:
+ LEPT_FREE(carray);
+ LEPT_FREE(rtab);
+ LEPT_FREE(gtab);
+ LEPT_FREE(btab);
+ return 0;
+}
diff --git a/leptonica/src/colorquant2.c b/leptonica/src/colorquant2.c
new file mode 100644
index 00000000..425b1d16
--- /dev/null
+++ b/leptonica/src/colorquant2.c
@@ -0,0 +1,1692 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file colorquant2.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 DefaultSigBits = 5;
+static const l_int32 MaxItersAllowed = 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 FractByPopulation = 0.85f;
+
+ /* To get the max value of 'dif' in the dithering color transfer,
+ * divide DifCap by 8. */
+static const l_int32 DifCap = 100;
+
+
+#ifndef NO_CONSOLE_IO
+#define DEBUG_MC_COLORS 0
+#define DEBUG_SPLIT_AXES 0
+#endif /* ~NO_CONSOLE_IO */
+
+/*------------------------------------------------------------------------*
+ * High level *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixMedianCutQuant()
+ *
+ * \param[in] pixs 32 bpp; rgb color
+ * \param[in] ditherflag 1 for dither; 0 for no dither
+ * \return pixd 8 bit with colormap, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Simple interface. See pixMedianCutQuantGeneral() for
+ * use of defaulted parameters.
+ * </pre>
+ */
+PIX *
+pixMedianCutQuant(PIX *pixs,
+ l_int32 ditherflag)
+{
+ return pixMedianCutQuantGeneral(pixs, ditherflag,
+ 0, 256, DefaultSigBits, 1, 1);
+}
+
+
+/*!
+ * \brief pixMedianCutQuantGeneral()
+ *
+ * \param[in] pixs 32 bpp; rgb color
+ * \param[in] ditherflag 1 for dither; 0 for no dither
+ * \param[in] outdepth output depth; valid: 0, 1, 2, 4, 8
+ * \param[in] maxcolors between 2 and 256
+ * \param[in] sigbits valid: 5 or 6; use 0 for default
+ * \param[in] maxsub max subsampling, integer; use 0 for default;
+ * 1 for no subsampling
+ * \param[in] 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
+ *
+ * <pre>
+ * 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) %sigbits can be 5 or 6. There are 2^24 colors in the color space.
+ * sigbits # of volume elems # of colors in a volume elem
+ * --------------------------------------------------------------
+ * 5 2^15 2^9 = 512
+ * 6 2^18 2^6 = 64
+ * Volume in color space is measured in the number of volume elements.
+ * (7) 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.
+ * </pre>
+ */
+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 maxprod, prod, norm, 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 = DefaultSigBits;
+ 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 sampled 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)(FractByPopulation * 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++ > MaxItersAllowed) {
+ 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. Normalize to the largest product to avoid
+ * integer overflow. */
+ maxprod = 0.0;
+ for (i = 0; i < lh->n; i++) {
+ if ((vbox = (L_BOX3D *)lheapGetElement(lh, i)) == NULL)
+ continue;
+ prod = (l_float32)vbox->npix * (l_float32)vbox->vol;
+ if (prod > maxprod) maxprod = prod;
+ }
+ norm = (maxprod == 0) ? 1.0 : 1000000.0 / maxprod;
+ lhs = lheapCreate(0, L_SORT_DECREASING);
+ while ((vbox = (L_BOX3D *)lheapRemove(lh))) {
+ vbox->sortparam = norm * 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 = norm * vbox1->npix * vbox1->vol;
+ LEPT_FREE(vbox);
+ lheapAdd(lhs, vbox1);
+ if (vbox2) { /* vbox2 can be NULL */
+ if (vbox2->vol > 1)
+ vbox2->sortparam = norm * vbox2->npix * vbox2->vol;
+ lheapAdd(lhs, vbox2);
+ ncolors++;
+ }
+ if (ncolors >= maxcolors)
+ break;
+ if (niters++ > MaxItersAllowed) {
+ 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;
+}
+
+
+/*!
+ * \brief pixMedianCutQuantMixed()
+ *
+ * \param[in] pixs 32 bpp; rgb color
+ * \param[in] ncolor maximum number of colors assigned to
+ * pixels with significant color
+ * \param[in] ngray number of gray colors to be used; must be >= 2
+ * \param[in] 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
+ * \param[in] 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
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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,
+ DefaultSigBits, 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;
+}
+
+
+/*!
+ * \brief pixFewColorsMedianCutQuantMixed()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] ncolor number of colors to be assigned to pixels
+ * with significant color
+ * \param[in] ngray number of gray colors to be used; must be >= 2
+ * \param[in] maxncolors maximum number of colors to be returned from
+ * pixColorsForQuantization(); use 0 for default
+ * \param[in] 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
+ * \param[in] 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
+ * \param[in] 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, 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
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixMedianCutHisto()
+ *
+ * \param[in] pixs 32 bpp; rgb color
+ * \param[in] sigbits valid: 5 or 6
+ * \param[in] subsample integer > 0
+ * \return histo 1-d array, giving the number of pixels in each
+ * quantized region of color space, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixcmapGenerateFromHisto()
+ *
+ * \param[in] pixs 32 bpp; rgb color
+ * \param[in] depth of colormap
+ * \param[in] histo
+ * \param[in] histosize
+ * \param[in] sigbits
+ * \return colormap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixQuantizeWithColormap()
+ *
+ * \param[in] pixs 32 bpp; rgb color
+ * \param[in] ditherflag 1 for dither; 0 for no dither
+ * \param[in] outdepth depth of the returned pixd
+ * \param[in] cmap colormap
+ * \param[in] indexmap lookup table
+ * \param[in] mapsize size of the lookup table
+ * \param[in] sigbits significant bits in output
+ * \return pixd quantized to colormap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, success;
+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 */
+ success = TRUE;
+ bufu8r = bufu8g = bufu8b = NULL;
+ buf1r = buf1g = buf1b = buf2r = buf2g = buf2b = NULL;
+ 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 || !buf1r || !buf1g ||
+ !buf1b || !buf2r || !buf2g || !buf2b) {
+ L_ERROR("buffer not made\n", procName);
+ success = FALSE;
+ goto buffer_cleanup;
+ }
+
+ /* 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 > 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 {
+ 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 > 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 {
+ 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 > 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 {
+ 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]);
+ }
+
+buffer_cleanup:
+ 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);
+ if (!success) pixDestroy(&pixd);
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * \brief getColorIndexMedianCut()
+ *
+ * \param[in] pixel 32 bit rgb
+ * \param[in] rshift of component: 8 - sigbits
+ * \param[in] mask over sigbits
+ * \param[in] sigbits
+ * \param[out] pindex rgb index value
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) This is used on each pixel in the source image. No checking
+ * is done on input values.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGetColorRegion()
+ *
+ * \param[in] pixs 32 bpp; rgb color
+ * \param[in] sigbits valid: 5, 6
+ * \param[in] subsample integer > 0
+ * \return vbox minimum 3D box in color space enclosing all pixels,
+ * or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Computes the minimum 3D box in color space enclosing all
+ * pixels in the image.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief medianCutApply()
+ *
+ * \param[in] histo array; in rgb colorspace
+ * \param[in] sigbits
+ * \param[in] vbox input 3D box
+ * \param[out] pvbox1, pvbox2 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 (pvbox1) *pvbox1 = NULL;
+ if (pvbox2) *pvbox2 = NULL;
+ 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);
+
+ 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)
+ lept_stderr("red split\n");
+ else if (gw == maxw)
+ lept_stderr("green split\n");
+ else
+ lept_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. */
+ vbox1 = vbox2 = NULL;
+ 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;
+ }
+ }
+ }
+ *pvbox1 = vbox1;
+ *pvbox2 = vbox2;
+ if (!vbox1)
+ return ERROR_INT("vbox1 not made; shouldn't happen", procName, 1);
+ if (!vbox2)
+ return ERROR_INT("vbox2 not made; shouldn't happen", procName, 1);
+ vbox1->npix = vboxGetCount(vbox1, histo, sigbits);
+ vbox2->npix = vboxGetCount(vbox2, histo, sigbits);
+ vbox1->vol = vboxGetVolume(vbox1);
+ vbox2->vol = vboxGetVolume(vbox2);
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixcmapGenerateFromMedianCuts()
+ *
+ * \param[in] lh priority queue of pointers to vboxes
+ * \param[in] histo
+ * \param[in] sigbits valid: 5 or 6
+ * \return cmap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief vboxGetAverageColor()
+ *
+ * \param[in] vbox 3d region of color space for one quantized color
+ * \param[in] histo
+ * \param[in] sigbits valid: 5 or 6
+ * \param[in] index if >= 0, assign to all colors in histo in this vbox
+ * \param[out] prval, pgval, pbval average color
+ * \return cmap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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
+ lept_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;
+}
+
+
+/*!
+ * \brief vboxGetCount()
+ *
+ * \param[in] vbox 3d region of color space for one quantized color
+ * \param[in] histo
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief vboxGetVolume()
+ *
+ * \param[in] 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));
+}
+
+/*!
+ * \brief box3dCreate()
+ *
+ * \param[in] r1, r2, g1, g2, b1, b2 initial values
+ * \return vbox
+ */
+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;
+}
+
+
+/*!
+ * \brief box3dCopy()
+ *
+ * \param[in] vbox
+ * \return vboxc copy of vbox
+ *
+ * <pre>
+ * Notes:
+ * Don't copy the sortparam.
+ * </pre>
+ */
+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/leptonica/src/colorseg.c b/leptonica/src/colorseg.c
new file mode 100644
index 00000000..e4def775
--- /dev/null
+++ b/leptonica/src/colorseg.c
@@ -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.
+ *====================================================================*/
+
+/*!
+ * \file colorseg.c
+ * <pre>
+ *
+ * Unsupervised color segmentation
+ *
+ * PIX *pixColorSegment()
+ * PIX *pixColorSegmentCluster()
+ * static l_int32 pixColorSegmentTryCluster()
+ * l_int32 pixAssignToNearestColor()
+ * l_int32 pixColorSegmentClean()
+ * l_int32 pixColorSegmentRemoveColors()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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.3f;
+
+ /* 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,
+ l_int32 debugflag);
+
+/*------------------------------------------------------------------*
+ * Unsupervised color segmentation *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixColorSegment()
+ *
+ * \param[in] pixs 32 bpp; 24-bit color
+ * \param[in] maxdist max euclidean dist to existing cluster
+ * \param[in] maxcolors max number of colors allowed in first pass
+ * \param[in] selsize linear size of sel for closing to remove noise
+ * \param[in] finalcolors max number of final colors allowed after 4th pass
+ * \param[in] debugflag 1 for debug output; 0 otherwise
+ * \return pixd 8 bit with colormap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixColorSegment(PIX *pixs,
+ l_int32 maxdist,
+ l_int32 maxcolors,
+ l_int32 selsize,
+ l_int32 finalcolors,
+ l_int32 debugflag)
+{
+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 */
+ pixd = pixColorSegmentCluster(pixs, maxdist, maxcolors, debugflag);
+ if (!pixd)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ if (debugflag) {
+ lept_mkdir("lept/segment");
+ pixWriteDebug("/tmp/lept/segment/colorseg1.png", pixd, IFF_PNG);
+ }
+
+ /* Phase 2; refinement in pixel assignment */
+ if ((countarray = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL) {
+ pixDestroy(&pixd);
+ return (PIX *)ERROR_PTR("countarray not made", procName, NULL);
+ }
+ pixAssignToNearestColor(pixd, pixs, NULL, LEVEL_IN_OCTCUBE, countarray);
+ if (debugflag)
+ pixWriteDebug("/tmp/lept/segment/colorseg2.png", pixd, IFF_PNG);
+
+ /* Phase 3: noise removal by separately closing each color */
+ pixColorSegmentClean(pixd, selsize, countarray);
+ LEPT_FREE(countarray);
+ if (debugflag)
+ pixWriteDebug("/tmp/lept/segment/colorseg3.png", pixd, IFF_PNG);
+
+ /* Phase 4: removal of colors with small population and
+ * reassignment of pixels to remaining colors */
+ pixColorSegmentRemoveColors(pixd, pixs, finalcolors);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixColorSegmentCluster()
+ *
+ * \param[in] pixs 32 bpp; 24-bit color
+ * \param[in] maxdist max euclidean dist to existing cluster
+ * \param[in] maxcolors max number of colors allowed in first pass
+ * \param[in] debugflag 1 for debug output; 0 otherwise
+ * \return pixd 8 bit with colormap, or NULL on error
+ *
+ * <pre>
+ * 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!
+ * </pre>
+ */
+PIX *
+pixColorSegmentCluster(PIX *pixs,
+ l_int32 maxdist,
+ l_int32 maxcolors,
+ l_int32 debugflag)
+{
+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);
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ 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, debugflag);
+ niters++;
+ if (!ret) {
+ ncolors = pixcmapGetCount(cmap);
+ if (debugflag)
+ 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;
+}
+
+
+/*!
+ * \brief pixColorSegmentTryCluster()
+ *
+ * \param[in] pixd
+ * \param[in] pixs
+ * \param[in] maxdist
+ * \param[in] maxcolors
+ * \param[in] debugflag 1 for debug output; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * This function should only be called from pixColorSegCluster()
+ * </pre>
+ */
+static l_int32
+pixColorSegmentTryCluster(PIX *pixd,
+ PIX *pixs,
+ l_int32 maxdist,
+ l_int32 maxcolors,
+ l_int32 debugflag)
+{
+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);
+ ncolors = 0;
+ 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);
+/* lept_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 {
+ if (debugflag) {
+ 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;
+}
+
+
+/*!
+ * \brief pixAssignToNearestColor()
+ *
+ * \param[in] pixd 8 bpp, colormapped
+ * \param[in] pixs 32 bpp; 24-bit color
+ * \param[in] pixm [optional] 1 bpp
+ * \param[in] level of octcube used for finding nearest color in cmap
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+pixAssignToNearestColor(PIX *pixd,
+ PIX *pixs,
+ PIX *pixm,
+ l_int32 level,
+ l_int32 *countarray)
+{
+l_int32 w, h, wpls, wpld, wplm, i, j, success;
+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);
+ if (level < 1 || level > 6)
+ return ERROR_INT("level not in [1 ... 6]", procName, 1);
+
+ /* Set up the tables to map rgb to the nearest colormap index */
+ success = TRUE;
+ makeRGBToIndexTables(level, &rtab, &gtab, &btab);
+ cmaptab = pixcmapToOctcubeLUT(cmap, level, L_MANHATTAN_DISTANCE);
+ if (!rtab || !gtab || !btab || !cmaptab) {
+ L_ERROR("failure to make a table\n", procName);
+ success = FALSE;
+ goto cleanup_arrays;
+ }
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ 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);
+ }
+ }
+
+cleanup_arrays:
+ LEPT_FREE(cmaptab);
+ LEPT_FREE(rtab);
+ LEPT_FREE(gtab);
+ LEPT_FREE(btab);
+ return (success) ? 0 : 1;
+}
+
+
+/*!
+ * \brief pixColorSegmentClean()
+ *
+ * \param[in] pixs 8 bpp, colormapped
+ * \param[in] selsize for closing
+ * \param[in] countarray ptr to array containing the number of pixels
+ * found in each color in the colormap
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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]);
+ nasi = numaGetSortIndex(na, L_SORT_DECREASING);
+ numaDestroy(&na);
+ if (!nasi)
+ 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(&nasi);
+ return 0;
+}
+
+
+/*!
+ * \brief pixColorSegmentRemoveColors()
+ *
+ * \param[in] pixd 8 bpp, colormapped
+ * \param[in] pixs 32 bpp rgb, with initial pixel values
+ * \param[in] finalcolors max number of colors to retain
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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/leptonica/src/colorspace.c b/leptonica/src/colorspace.c
new file mode 100644
index 00000000..9bc3f87f
--- /dev/null
+++ b/leptonica/src/colorspace.c
@@ -0,0 +1,2471 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file colorspace.c
+ * <pre>
+ *
+ * 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()
+ *
+ * Gamut display of RGB color space
+ * PIX *pixMakeGamutRGB()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertRGBToHSV()
+ *
+ * \param[in] pixd can be NULL; if not NULL, must == pixs
+ * \param[in] pixs
+ * \return pixd always
+ *
+ * <pre>
+ * 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)
+ * (6) Dividing each component by a constant c > 1 reduces the
+ * brightness v, but leaves the saturation and hue invariant.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvertHSVToRGB()
+ *
+ * \param[in] pixd can be NULL; if not NULL, must == pixs
+ * \param[in] pixs
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief convertRGBToHSV()
+ *
+ * \param[in] rval, gval, bval RGB input
+ * \param[out] phval, psval, pvval comparable HSV values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief convertHSVToRGB()
+ *
+ * \param[in] hval, sval, vval HSV input
+ * \param[out] prval, pgval, pbval comparable RGB values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See convertRGBToHSV() for valid input range of HSV values
+ * and their interpretation in color space.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapConvertRGBToHSV()
+ *
+ * \param[in] cmap
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * ~ in-place transform
+ * ~ See convertRGBToHSV() for def'n of HSV space.
+ * ~ replaces: r --> h, g --> s, b --> v
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapConvertHSVToRGB()
+ *
+ * \param[in] cmap
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * ~ in-place transform
+ * ~ See convertRGBToHSV() for def'n of HSV space.
+ * ~ replaces: h --> r, s --> g, v --> b
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixConvertRGBToHue()
+ *
+ * \param[in] pixs 32 bpp RGB, or 8 bpp with colormap
+ * \return pixd 8 bpp hue of HSV, or NULL on error
+ *
+ * <pre>
+ * 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()
+ * </pre>
+ */
+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;
+}
+
+
+
+/*!
+ * \brief pixConvertRGBToSaturation()
+ *
+ * \param[in] pixs 32 bpp RGB, or 8 bpp with colormap
+ * \return pixd 8 bpp sat of HSV, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvertRGBToValue()
+ *
+ * \param[in] pixs 32 bpp RGB,or 8 bpp with colormap
+ * \return pixd 8 bpp max component intensity of HSV, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixMakeRangeMaskHS()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] huecenter center value of hue range
+ * \param[in] huehw half-width of hue range
+ * \param[in] satcenter center value of saturation range
+ * \param[in] sathw half-width of saturation range
+ * \param[in] regionflag L_INCLUDE_REGION, L_EXCLUDE_REGION
+ * \return pixd 1 bpp mask over selected pixels, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixMakeRangeMaskHV()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] huecenter center value of hue range
+ * \param[in] huehw half-width of hue range
+ * \param[in] valcenter center value of max intensity range
+ * \param[in] valhw half-width of max intensity range
+ * \param[in] regionflag L_INCLUDE_REGION, L_EXCLUDE_REGION
+ * \return pixd 1 bpp mask over selected pixels, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixMakeRangeMaskSV()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] satcenter center value of saturation range
+ * \param[in] sathw half-width of saturation range
+ * \param[in] valcenter center value of max intensity range
+ * \param[in] valhw half-width of max intensity range
+ * \param[in] regionflag L_INCLUDE_REGION, L_EXCLUDE_REGION
+ * \return pixd 1 bpp mask over selected pixels, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixMakeHistoHS()
+ *
+ * \param[in] pixs HSV colorspace
+ * \param[in] factor subsampling factor; integer
+ * \param[out] pnahue [optional] hue histogram
+ * \param[out] pnasat [optional] saturation histogram
+ * \return pixd 32 bpp histogram in hue and saturation, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ lept_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;
+}
+
+
+/*!
+ * \brief pixMakeHistoHV()
+ *
+ * \param[in] pixs HSV colorspace
+ * \param[in] factor subsampling factor; integer
+ * \param[out] pnahue [optional] hue histogram
+ * \param[out] pnaval [optional] max intensity (value) histogram
+ * \return pixd 32 bpp histogram in hue and value, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixMakeHistoSV()
+ *
+ * \param[in] pixs HSV colorspace
+ * \param[in] factor subsampling factor; integer
+ * \param[out] pnasat [optional] sat histogram
+ * \param[out] pnaval [optional] max intensity (value) histogram
+ * \return pixd 32 bpp histogram in sat and value, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFindHistoPeaksHSV()
+ *
+ * \param[in] pixs 32 bpp; HS, HV or SV histogram; not changed
+ * \param[in] type L_HS_HISTO, L_HV_HISTO or L_SV_HISTO
+ * \param[in] width half width of sliding window
+ * \param[in] height half height of sliding window
+ * \param[in] npeaks number of peaks to look for
+ * \param[in] erasefactor ratio of erase window size to sliding window size
+ * \param[out] ppta locations of max for each integrated peak area
+ * \param[out] pnatot integrated peak areas
+ * \param[out] ppixa [optional] pixa for debugging; NULL to skip
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+l_ok
+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, *pix1, *pix2, *pix3;
+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) {
+ pix1 = pixMaxDynamicRange(pixw, L_LINEAR_SCALE);
+ pixaAddPix(*ppixa, pix1, L_INSERT);
+ pix2 = pixConvertGrayToFalseColor(pix1, 1.0);
+ pixaAddPix(*ppixa, pix2, L_INSERT);
+ pix1 = pixMaxDynamicRange(pixw, L_LOG_SCALE);
+ pix2 = pixConvertGrayToFalseColor(pix1, 1.0);
+ pixaAddPix(*ppixa, pix2, L_INSERT);
+ pix3 = pixConvertTo32(pix1);
+ pixRenderHashBoxArb(pix3, box, 6, 2, L_NEG_SLOPE_LINE,
+ 1, 255, 100, 100);
+ pixaAddPix(*ppixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ 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;
+}
+
+
+/*!
+ * \brief displayHSVColorRange()
+ *
+ * \param[in] hval hue center value; in range [0 ... 240]
+ * \param[in] sval saturation center value; in range [0 ... 255]
+ * \param[in] vval max intensity value; in range [0 ... 255]
+ * \param[in] huehw half-width of hue range; > 0
+ * \param[in] sathw half-width of saturation range; > 0
+ * \param[in] nsamp number of samplings in each half-width in hue and sat
+ * \param[in] factor linear size of each color square, in pixels; > 3
+ * \return pixd 32 bpp set of color squares over input range; NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The total number of color samplings in each of the hue
+ * and saturation directions is 2 * nsamp + 1.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertRGBToYUV()
+ *
+ * \param[in] pixd can be NULL; if not NULL, must == pixs
+ * \param[in] pixs
+ * \return pixd always
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvertYUVToRGB()
+ *
+ * \param[in] pixd can be NULL; if not NULL, must == pixs
+ * \param[in] pixs
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief convertRGBToYUV()
+ *
+ * \param[in] rval, gval, bval RGB input
+ * \param[out] pyval, puval, pvval equivalent YUV values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The range of returned values is:
+ * Y [16 ... 235]
+ * U [16 ... 240]
+ * V [16 ... 240]
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief convertYUVToRGB()
+ *
+ * \param[in] yval, uval, vval YUV input
+ * \param[out] prval, pgval, pbval equivalent RGB values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapConvertRGBToYUV()
+ *
+ * \param[in] cmap
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * ~ in-place transform
+ * ~ See convertRGBToYUV() for def'n of YUV space.
+ * ~ replaces: r --> y, g --> u, b --> v
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcmapConvertYUVToRGB()
+ *
+ * \param[in] cmap
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * ~ in-place transform
+ * ~ See convertRGBToYUV() for def'n of YUV space.
+ * ~ replaces: y --> r, u --> g, v --> b
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertRGBToXYZ()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \return fpixa xyz
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief fpixaConvertXYZToRGB()
+ *
+ * \param[in] fpixa three fpix: x,y,z
+ * \return pixd 32 bpp rgb
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief convertRGBToXYZ()
+ *
+ * \param[in] rval, gval, bval rgb input
+ * \param[out] pfxval, pfyval, pfzval equivalent xyz values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) These conversions are for illuminant D65 acting on linear sRGB
+ * values.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief convertXYZToRGB()
+ *
+ * \param[in] fxval, fyval, fzval
+ * \param[in] blackout 0 to output nearest color if out of gamut;
+ * 1 to output black
+ * \param[out] prval, pgval, pbval 32 bpp rgb values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief fpixaConvertXYZToLAB()
+ *
+ * \param[in] fpixas xyz
+ * \return fpixa lab
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief fpixaConvertLABToXYZ()
+ *
+ * \param[in] fpixas lab
+ * \return fpixa xyz
+ *
+ * <pre>
+ * Notes:
+ * (1) The input [l,a,b] and output [x,y,z] values are stored as
+ * float values, each set in three fpix.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief convertXYZToLAB()
+ *
+ * \param[in] xval, yval, zval input xyz
+ * \param[out] plval, paval, pbval equivalent lab values
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief convertLABToXYZ()
+ *
+ * \param[in] lval, aval, bval input lab
+ * \param[out] pxval, pyval, pzval equivalent xyz values
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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.37f; /* x component corresponding to rgb white */
+l_float32 yw = 255.0f; /* y component corresponding to rgb white */
+l_float32 zw = 277.69f; /* 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.008856f; /* (6/29)^3 */
+const l_float32 f_factor = 7.787f; /* (1/3) * (29/6)^2) */
+const l_float32 f_offset = 0.13793f; /* 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.20690f; /* 6/29 */
+const l_float32 r_factor = 0.12842f; /* 3 * (6/29)^2 */
+const l_float32 r_offset = 0.13793f; /* 4/29 */
+
+ if (v > r_thresh) {
+ return v * v * v;
+ } else {
+ return r_factor * (v - r_offset);
+ }
+}
+
+
+/*---------------------------------------------------------------------------*
+ * Colorspace conversion between RGB and LAB *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertRGBToLAB()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \return fpixa lab
+ *
+ * <pre>
+ * Notes:
+ * (1) The [l,a,b] values are stored as float values in three fpix
+ * that are returned in a fpixa.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief fpixaConvertLABToRGB()
+ *
+ * \param[in] fpixa three fpix: l,a,b
+ * \return pixd 32 bpp rgb
+ *
+ * <pre>
+ * Notes:
+ * (1) The lab image is stored in three fpix.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief convertRGBToLAB()
+ *
+ * \param[in] rval, gval, bval rgb input
+ * \param[out] pflval, pfaval, pfbval equivalent lab values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) These conversions are for illuminant D65 acting on linear sRGB
+ * values.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief convertLABToRGB()
+ *
+ * \param[in] flval, faval, fbval input lab
+ * \param[out] prval, pgval, pbval equivalent rgb values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For values of lab that are out of gamut for rgb, the rgb
+ * components are set to the closest valid color.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*---------------------------------------------------------------------------*
+ * Gamut display of RGB color space *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixMakeGamutRGB()
+ *
+ * \param[in] scale default = 4
+ * \return pix2 32 bpp rgb
+ *
+ * <pre>
+ * Notes:
+ * (1) This is an image that has all RGB colors, divided into 2^15
+ * cubical cells with 8x8x8 = 512 pixel values. Each of the 32
+ * subimages has a constant value of B, with R and G varying over
+ * their gamut in 32 steps of size 8.
+ * (2) The %scale parameter determines the replication in both x and y
+ * of each of the 2^15 colors. With a scale factor of 4, the
+ * output image has 4 * 4 * 2^15 = 0.5M pixels.
+ * (3) This useful for visualizing how filters, such as
+ * pixMakeArbMaskFromRGB(), separate colors into sets.
+ * </pre>
+ */
+PIX *
+pixMakeGamutRGB(l_int32 scale)
+{
+l_int32 i, j, k;
+l_uint32 val32;
+PIX *pix1, *pix2;
+PIXA *pixa;
+
+ if (scale <= 0) scale = 8; /* default */
+
+ pixa = pixaCreate(32);
+ for (k = 0; k < 32; k++) {
+ pix1 = pixCreate(32, 32, 32);
+ for (i = 0; i < 32; i++) {
+ for (j = 0; j < 32; j++) {
+ composeRGBPixel(8 * j, 8 * i, 8 * k, &val32);
+ pixSetPixel(pix1, j, i, val32);
+ }
+ }
+ pixaAddPix(pixa, pix1, L_INSERT);
+ }
+ pix2 = pixaDisplayTiledInColumns(pixa, 8, scale, 5, 0);
+ pixaDestroy(&pixa);
+ return pix2;
+}
diff --git a/leptonica/src/compare.c b/leptonica/src/compare.c
new file mode 100644
index 00000000..069a0e52
--- /dev/null
+++ b/leptonica/src/compare.c
@@ -0,0 +1,3611 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file compare.c
+ * <pre>
+ *
+ * Test for pix equality
+ * l_int32 pixEqual()
+ * l_int32 pixEqualWithAlpha()
+ * l_int32 pixEqualWithCmap()
+ * l_int32 cmapEqual()
+ * 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()
+ * static l_int32 findHistoGridDimensions()
+ * 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.
+ *
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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.00001f;
+
+static l_ok findHistoGridDimensions(l_int32 n, l_int32 w, l_int32 h,
+ l_int32 *pnx, l_int32 *pny, l_int32 debug);
+static l_ok pixCompareTilesByHisto(PIX *pix1, PIX *pix2, l_int32 maxgray,
+ l_int32 factor, l_int32 n,
+ l_float32 *pscore, PIXA *pixadebug);
+
+/*------------------------------------------------------------------*
+ * Test for pix equality *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixEqual()
+ *
+ * \param[in] pix1
+ * \param[in] pix2
+ * \param[out] psame 1 if same; 0 if different
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+pixEqual(PIX *pix1,
+ PIX *pix2,
+ l_int32 *psame)
+{
+ return pixEqualWithAlpha(pix1, pix2, 0, psame);
+}
+
+
+/*!
+ * \brief pixEqualWithAlpha()
+ *
+ * \param[in] pix1
+ * \param[in] pix2
+ * \param[in] use_alpha 1 to compare alpha in RGBA; 0 to ignore
+ * \param[out] psame 1 if same; 0 if different
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 || !pix2)
+ return ERROR_INT("pix1 and pix2 not both 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 : (0xffffffff << (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;
+}
+
+
+/*!
+ * \brief pixEqualWithCmap()
+ *
+ * \param[in] pix1
+ * \param[in] pix2
+ * \param[out] psame
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+pixEqualWithCmap(PIX *pix1,
+ PIX *pix2,
+ l_int32 *psame)
+{
+l_int32 d, w, h, wpl1, wpl2, i, j, linebits, fullwords, endbits;
+l_int32 rval1, rval2, gval1, gval2, bval1, bval2, samecmaps;
+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;
+ }
+ pixGetDimensions(pix1, &w, &h, &d);
+ if (d != 1 && d != 2 && d != 4 && d != 8) {
+ L_INFO("pix depth not in {1, 2, 4, 8}\n", procName);
+ return 0;
+ }
+
+ cmapEqual(cmap1, cmap2, 3, &samecmaps);
+ 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 = (endbits == 0) ? 0 : (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;
+}
+
+
+/*!
+ * \brief cmapEqual()
+ *
+ * \param[in] cmap1
+ * \param[in] cmap2
+ * \param[in] ncomps 3 for RGB, 4 for RGBA
+ * \param[out] psame
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This returns %same = TRUE if the colormaps have identical entries.
+ * (2) If %ncomps == 4, the alpha components of the colormaps are also
+ * compared.
+ * </pre>
+ */
+l_ok
+cmapEqual(PIXCMAP *cmap1,
+ PIXCMAP *cmap2,
+ l_int32 ncomps,
+ l_int32 *psame)
+{
+l_int32 n1, n2, i, rval1, rval2, gval1, gval2, bval1, bval2, aval1, aval2;
+
+ PROCNAME("cmapEqual");
+
+ if (!psame)
+ return ERROR_INT("&same not defined", procName, 1);
+ *psame = FALSE;
+ if (!cmap1)
+ return ERROR_INT("cmap1 not defined", procName, 1);
+ if (!cmap2)
+ return ERROR_INT("cmap2 not defined", procName, 1);
+ if (ncomps != 3 && ncomps != 4)
+ return ERROR_INT("ncomps not 3 or 4", procName, 1);
+
+ n1 = pixcmapGetCount(cmap1);
+ n2 = pixcmapGetCount(cmap2);
+ if (n1 != n2) {
+ L_INFO("colormap sizes are different\n", procName);
+ return 0;
+ }
+
+ for (i = 0; i < n1; i++) {
+ pixcmapGetRGBA(cmap1, i, &rval1, &gval1, &bval1, &aval1);
+ pixcmapGetRGBA(cmap2, i, &rval2, &gval2, &bval2, &aval2);
+ if (rval1 != rval2 || gval1 != gval2 || bval1 != bval2)
+ return 0;
+ if (ncomps == 4 && aval1 != aval2)
+ return 0;
+ }
+ *psame = TRUE;
+ return 0;
+}
+
+
+/*!
+ * \brief pixUsesCmapColor()
+ *
+ * \param[in] pixs any depth, colormap
+ * \param[out] pcolor TRUE if color found
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixCorrelationBinary()
+ *
+ * \param[in] pix1 1 bpp
+ * \param[in] pix2 1 bpp
+ * \param[out] pval correlation
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+ if (count1 == 0 || count2 == 0) {
+ LEPT_FREE(tab8);
+ return 0;
+ }
+ 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);
+ pixDestroy(&pixn);
+ return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ * Difference of two images *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixDisplayDiffBinary()
+ *
+ * \param[in] pix1 1 bpp
+ * \param[in] pix2 1 bpp
+ * \return pixd 4 bpp cmapped, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCompareBinary()
+ *
+ * \param[in] pix1 1 bpp
+ * \param[in] pix2 1 bpp
+ * \param[in] comptype L_COMPARE_XOR, L_COMPARE_SUBTRACT
+ * \param[out] pfract fraction of pixels that are different
+ * \param[out] ppixdiff [optional] pix of difference
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * (4) On error, the returned fraction is 1.0.
+ * </pre>
+ */
+l_ok
+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 = 1.0; /* initialize to max difference */
+ 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;
+}
+
+
+/*!
+ * \brief pixCompareGrayOrRGB()
+ *
+ * \param[in] pix1 2,4,8,16 bpp gray, 32 bpp rgb, or colormapped
+ * \param[in] pix2 2,4,8,16 bpp gray, 32 bpp rgb, or colormapped
+ * \param[in] comptype L_COMPARE_SUBTRACT, L_COMPARE_ABS_DIFF
+ * \param[in] plottype gplot plot output type, or 0 for no plot
+ * \param[out] psame [optional] 1 if pixel values are identical
+ * \param[out] pdiff [optional] average difference
+ * \param[out] prmsdiff [optional] rms of difference
+ * \param[out] ppixdiff [optional] pix of difference
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * (10) Because pixel values are compared, pix1 and pix2 can be equal when:
+ * * they are both gray with different depth
+ * * one is colormapped and the other is not
+ * * they are both colormapped and have different size colormaps
+ * </pre>
+ */
+l_ok
+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, d1, d2;
+PIX *pixt1, *pixt2, *pixs1, *pixs2;
+
+ PROCNAME("pixCompareGrayOrRGB");
+
+ if (psame) *psame = 0;
+ if (pdiff) *pdiff = 255.0;
+ if (prmsdiff) *prmsdiff = 255.0;
+ if (ppixdiff) *ppixdiff = NULL;
+ if (!pix1 || pixGetDepth(pix1) == 1)
+ return ERROR_INT("pix1 not defined or 1 bpp", procName, 1);
+ if (!pix2 || pixGetDepth(pix2) == 1)
+ return ERROR_INT("pix2 not defined or 1 bpp", procName, 1);
+ if (comptype != L_COMPARE_SUBTRACT && comptype != L_COMPARE_ABS_DIFF)
+ return ERROR_INT("invalid comptype", procName, 1);
+ if (plottype < 0 || 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);
+ d1 = pixGetDepth(pixt1);
+ d2 = pixGetDepth(pixt2);
+ if (d1 < 8)
+ pixs1 = pixConvertTo8(pixt1, FALSE);
+ else
+ pixs1 = pixClone(pixt1);
+ if (d2 < 8)
+ pixs2 = pixConvertTo8(pixt2, FALSE);
+ else
+ pixs2 = pixClone(pixt2);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ d1 = pixGetDepth(pixs1);
+ d2 = pixGetDepth(pixs2);
+ if (d1 != d2) {
+ pixDestroy(&pixs1);
+ pixDestroy(&pixs2);
+ return ERROR_INT("intrinsic depths are not equal", procName, 1);
+ }
+
+ if (d1 == 8 || d1 == 16)
+ retval = pixCompareGray(pixs1, pixs2, comptype, plottype, psame,
+ pdiff, prmsdiff, ppixdiff);
+ else /* d1 == 32 */
+ retval = pixCompareRGB(pixs1, pixs2, comptype, plottype, psame,
+ pdiff, prmsdiff, ppixdiff);
+ pixDestroy(&pixs1);
+ pixDestroy(&pixs2);
+ return retval;
+}
+
+
+/*!
+ * \brief pixCompareGray()
+ *
+ * \param[in] pix1 8 or 16 bpp, not cmapped
+ * \param[in] pix2 8 or 16 bpp, not cmapped
+ * \param[in] comptype L_COMPARE_SUBTRACT, L_COMPARE_ABS_DIFF
+ * \param[in] plottype gplot plot output type, or 0 for no plot
+ * \param[out] psame [optional] 1 if pixel values are identical
+ * \param[out] pdiff [optional] average difference
+ * \param[out] prmsdiff [optional] rms of difference
+ * \param[out] ppixdiff [optional] pix of difference
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixCompareGrayOrRGB() for details.
+ * (2) Use pixCompareGrayOrRGB() if the input pix are colormapped.
+ * (3) Note: setting %plottype > 0 can result in writing named
+ * output files.
+ * </pre>
+ */
+l_ok
+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 = 255.0;
+ if (prmsdiff) *prmsdiff = 255.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 < 0 || 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) {
+ L_INFO("Images differ: output plots will be generated\n", procName);
+ 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);
+ snprintf(buf, sizeof(buf), "/tmp/lept/comp/compare_gray%d.png",
+ index++);
+ l_fileDisplay(buf, 100, 100, 1.0);
+ 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;
+}
+
+
+/*!
+ * \brief pixCompareRGB()
+ *
+ * \param[in] pix1 32 bpp rgb
+ * \param[in] pix2 32 bpp rgb
+ * \param[in] comptype L_COMPARE_SUBTRACT, L_COMPARE_ABS_DIFF
+ * \param[in] plottype gplot plot output type, or 0 for no plot
+ * \param[out] psame [optional] 1 if pixel values are identical
+ * \param[out] pdiff [optional] average difference
+ * \param[out] prmsdiff [optional] rms of difference
+ * \param[out] ppixdiff [optional] pix of difference
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixCompareGrayOrRGB() for details.
+ * (2) Note: setting %plottype > 0 can result in writing named
+ * output files.
+ * </pre>
+ */
+l_ok
+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 < 0 || 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) {
+ L_INFO("Images differ: output plots will be generated\n", procName);
+ 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);
+ snprintf(buf, sizeof(buf), "/tmp/lept/comp/compare_rgb%d.png",
+ index++);
+ l_fileDisplay(buf, 100, 100, 1.0);
+ 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;
+}
+
+
+/*!
+ * \brief pixCompareTiled()
+ *
+ * \param[in] pix1 8 bpp or 32 bpp rgb
+ * \param[in] pix2 8 bpp 32 bpp rgb
+ * \param[in] sx, sy tile size; must be > 1 in each dimension
+ * \param[in] type L_MEAN_ABSVAL or L_ROOT_MEAN_SQUARE
+ * \param[out] ppixdiff pix of difference
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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.f / 3.f);
+ *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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixCompareRankDifference()
+ *
+ * \param[in] pix1 8 bpp gray or 32 bpp rgb, or colormapped
+ * \param[in] pix2 8 bpp gray or 32 bpp rgb, or colormapped
+ * \param[in] factor subsampling factor; use 0 or 1 for no subsampling
+ * \return narank numa of rank difference, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixTestForSimilarity()
+ *
+ * \param[in] pix1 8 bpp gray or 32 bpp rgb, or colormapped
+ * \param[in] pix2 8 bpp gray or 32 bpp rgb, or colormapped
+ * \param[in] factor subsampling factor; use 0 or 1 for no subsampling
+ * \param[in] mindiff minimum pixel difference to be counted; > 0
+ * \param[in] maxfract maximum fraction of pixels allowed to have
+ * diff greater than or equal to mindiff
+ * \param[in] maxave maximum average difference of pixels allowed for
+ * pixels with diff greater than or equal to
+ * mindiff, after subtracting mindiff
+ * \param[out] psimilar 1 if similar, 0 otherwise
+ * \param[in] details use 1 to give normalized histogram and other data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+pixTestForSimilarity(PIX *pix1,
+ PIX *pix2,
+ l_int32 factor,
+ l_int32 mindiff,
+ l_float32 maxfract,
+ l_float32 maxave,
+ l_int32 *psimilar,
+ l_int32 details)
+{
+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, details))
+ 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;
+}
+
+
+/*!
+ * \brief pixGetDifferenceStats()
+ *
+ * \param[in] pix1 8 bpp gray or 32 bpp rgb, or colormapped
+ * \param[in] pix2 8 bpp gray or 32 bpp rgb, or colormapped
+ * \param[in] factor subsampling factor; use 0 or 1 for no subsampling
+ * \param[in] mindiff minimum pixel difference to be counted; > 0
+ * \param[out] pfractdiff fraction of pixels with diff greater than or
+ * equal to mindiff
+ * \param[out] pavediff average difference of pixels with diff greater
+ * than or equal to mindiff, less mindiff
+ * \param[in] details use 1 to give normalized histogram and other data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * (5) Set %details == 1 to see the difference histogram and get
+ * an output that shows for each value of %mindiff, what are the
+ * minimum values required for fractdiff and avediff in order
+ * that the two pix will be considered similar.
+ * </pre>
+ */
+l_ok
+pixGetDifferenceStats(PIX *pix1,
+ PIX *pix2,
+ l_int32 factor,
+ l_int32 mindiff,
+ l_float32 *pfractdiff,
+ l_float32 *pavediff,
+ l_int32 details)
+{
+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 (details) {
+ lept_mkdir("lept/comp");
+ numaGetNonzeroRange(nan, 0.0, &first, &last);
+ nac = numaClipToInterval(nan, first, last);
+ gplotSimple1(nac, GPLOT_PNG, "/tmp/lept/comp/histo",
+ "Difference histogram");
+ l_fileDisplay("/tmp/lept/comp/histo.png", 500, 0, 1.0);
+ lept_stderr("\nNonzero values in normalized histogram:");
+ numaWriteStderr(nac);
+ numaDestroy(&nac);
+ lept_stderr(" Mindiff fractdiff avediff\n");
+ lept_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;
+ lept_stderr("%5d %7.4f %7.4f\n",
+ diff, fract, ave);
+ }
+ lept_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;
+}
+
+
+/*!
+ * \brief pixGetDifferenceHistogram()
+ *
+ * \param[in] pix1 8 bpp gray or 32 bpp rgb, or colormapped
+ * \param[in] pix2 8 bpp gray or 32 bpp rgb, or colormapped
+ * \param[in] factor subsampling factor; use 0 or 1 for no subsampling
+ * \return na Numa of histogram of differences, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGetPerceptualDiff()
+ *
+ * \param[in] pixs1 8 bpp gray or 32 bpp rgb, or colormapped
+ * \param[in] pixs2 8 bpp gray or 32 bpp rgb, or colormapped
+ * \param[in] sampling subsampling factor; use 0 or 1 for no subsampling
+ * \param[in] dilation size of grayscale or color Sel; odd
+ * \param[in] mindiff minimum pixel difference to be counted; > 0
+ * \param[out] pfract fraction of pixels with diff greater than mindiff
+ * \param[out] ppixdiff1 [optional] showing difference (gray or color)
+ * \param[out] ppixdiff2 [optional] showing pixels of sufficient diff
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 and < 8 bpp", procName, 1);
+ if (!pixGetColormap(pixs2) && d2 < 8)
+ return ERROR_INT("pixs2 not cmapped and < 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 || (d1 != 8 && d1 != 32)) {
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ L_INFO("depths unequal or not in {8,32}: d1 = %d, d2 = %d\n",
+ procName, d1, d2);
+ return 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 = (w <= 0 || h <= 0) ? 0.0 :
+ (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 = (w <= 0 || h <= 0) ? 0.0 :
+ (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;
+}
+
+
+/*!
+ * \brief pixGetPSNR()
+ *
+ * \param[in] pix1, pix2 8 or 32 bpp; no colormap
+ * \param[in] factor sampling factor; >= 1
+ * \param[out] ppsnr power signal/noise ratio difference
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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 += (l_float32)(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 += ((l_float32)(r1 - r2) * (r1 - r2) +
+ (g1 - g2) * (g1 - g2) +
+ (b1 - b2) * (b1 - b2)) / 3.0;
+ }
+ }
+ }
+ mse = mse / ((l_float32)(w) * h);
+
+ *ppsnr = -4.3429448 * log(mse / (255 * 255));
+ return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ * Comparison of photo regions by histogram *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixaComparePhotoRegionsByHisto()
+ *
+ * \param[in] pixa any depth; colormap OK
+ * \param[in] minratio requiring sizes be compatible; < 1.0
+ * \param[in] textthresh threshold for text/photo; use 0 for default
+ * \param[in] factor subsampling; >= 1
+ * \param[in] n in range {1, ... 7}. n^2 is the maximum number
+ * of subregions for histograms; typ. n = 3.
+ * \param[in] simthresh threshold for similarity; use 0 for default
+ * \param[out] pnai array giving similarity class indices
+ * \param[out] pscores [optional] score matrix as 1-D array of size N^2
+ * \param[out] ppixd [optional] pix of similarity classes
+ * \param[in] debug 1 to output histograms; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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) To make the histograms, each image is subdivided in a maximum
+ * of n^2 subimages. The parameter %n specifies the "side" of
+ * an n x n grid of such subimages. If the subimages have an
+ * aspect ratio larger than 2, the grid will change, again using n^2
+ * as a maximum for the number of subimages. For example,
+ * if n == 3, but the image is 600 x 200 pixels, a 3x3 grid
+ * would have subimages of 200 x 67 pixels, which is more
+ * than 2:1, so we change to a 4x2 grid where each subimage
+ * has 150 x 100 pixels.
+ * (3) 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.
+ * (4) 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.
+ * (5) An array %nai of similarity class indices for pix in the
+ * input pixa is returned.
+ * (6) 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.
+ * (7) See the notes in pixComparePhotoRegionsByHisto() for details
+ * on the implementation.
+ * </pre>
+ */
+l_ok
+pixaComparePhotoRegionsByHisto(PIXA *pixa,
+ l_float32 minratio,
+ l_float32 textthresh,
+ l_int32 factor,
+ l_int32 n,
+ l_float32 simthresh,
+ NUMA **pnai,
+ l_float32 **pscores,
+ PIX **ppixd,
+ l_int32 debug)
+{
+char *text;
+l_int32 i, j, nim, w, h, w1, h1, w2, h2, ival, index, classid;
+l_float32 score;
+l_float32 *scores;
+NUMA *nai, *naw, *nah;
+NUMAA *naa;
+NUMAA **n3a; /* array of naa */
+PIX *pix;
+
+ PROCNAME("pixaComparePhotoRegionsByHisto");
+
+ 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.3f;
+ if (factor < 1)
+ return ERROR_INT("subsampling factor must be >= 1", procName, 1);
+ if (n < 1 || n > 7) {
+ L_WARNING("n = %d is invalid; setting to 4\n", procName, n);
+ n = 4;
+ }
+ 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 */
+ nim = pixaGetCount(pixa);
+ if ((n3a = (NUMAA **)LEPT_CALLOC(nim, sizeof(NUMAA *))) == NULL)
+ return ERROR_INT("calloc fail for n3a", procName, 1);
+ naw = numaCreate(0);
+ nah = numaCreate(0);
+ for (i = 0; i < nim; i++) {
+ pix = pixaGetPix(pixa, i, L_CLONE);
+ text = pixGetText(pix);
+ pixSetResolution(pix, 150, 150);
+ index = (debug) ? i : 0;
+ pixGenPhotoHistos(pix, NULL, factor, textthresh, n,
+ &naa, &w, &h, index);
+ n3a[i] = naa;
+ numaAddNumber(naw, w);
+ numaAddNumber(nah, h);
+ if (naa)
+ lept_stderr("Image %s is photo\n", text);
+ else
+ lept_stderr("Image %s is NOT photo\n", text);
+ pixDestroy(&pix);
+ }
+
+ /* Do the comparisons. We are making a set of classes, where
+ * all similar images are placed in the same class. There are
+ * 'nim' input images. The classes are labeled by 'classid' (all
+ * similar images get the same 'classid' value), and 'nai' maps
+ * the classid of the image in the input array to the classid
+ * of the similarity class. */
+ if ((scores =
+ (l_float32 *)LEPT_CALLOC((size_t)nim * nim, sizeof(l_float32)))
+ == NULL) {
+ L_ERROR("calloc fail for scores\n", procName);
+ goto cleanup;
+ }
+ nai = numaMakeConstant(-1, nim); /* classid array */
+ for (i = 0, classid = 0; i < nim; i++) {
+ scores[nim * i + i] = 1.0;
+ numaGetIValue(nai, i, &ival);
+ if (ival != -1) /* already set */
+ continue;
+ numaSetValue(nai, i, classid);
+ if (n3a[i] == NULL) { /* not a photo */
+ classid++;
+ continue;
+ }
+ numaGetIValue(naw, i, &w1);
+ numaGetIValue(nah, i, &h1);
+ for (j = i + 1; j < nim; 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[nim * i + j] = score;
+ scores[nim * j + i] = score; /* the score array is symmetric */
+/* lept_stderr("score = %5.3f\n", score); */
+ if (score > simthresh) {
+ numaSetValue(nai, j, classid);
+ lept_stderr(
+ "Setting %d similar to %d, in class %d; score %5.3f\n",
+ j, i, classid, score);
+ }
+ }
+ classid++;
+ }
+ *pnai = nai;
+
+ /* Debug: optionally save and display the score array.
+ * All images that are photos are represented by a point on
+ * the diagonal. Other images in the same similarity class
+ * are on the same horizontal raster line to the right.
+ * The array has been symmetrized, so images in the same
+ * same similarity class also appear on the same column below. */
+ if (pscores) {
+ l_int32 wpl, fact;
+ l_uint32 *line, *data;
+ PIX *pix2, *pix3;
+ pix2 = pixCreate(nim, nim, 8);
+ data = pixGetData(pix2);
+ wpl = pixGetWpl(pix2);
+ for (i = 0; i < nim; i++) {
+ line = data + i * wpl;
+ for (j = 0; j < nim; j++) {
+ SET_DATA_BYTE(line, j,
+ L_MIN(255, 4.0 * 255 * scores[nim * i + j]));
+ }
+ }
+ fact = L_MAX(2, 1000 / nim);
+ pix3 = pixExpandReplicate(pix2, fact);
+ lept_stderr("Writing to /tmp/lept/comp/scorearray.png\n");
+ lept_mkdir("lept/comp");
+ 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 similarity classes are displayed by column; similar
+ * images are displayed in the same column. */
+ if (ppixd)
+ *ppixd = pixaDisplayTiledByIndex(pixa, nai, 200, 20, 2, 6, 0x0000ff00);
+
+cleanup:
+ numaDestroy(&naw);
+ numaDestroy(&nah);
+ for (i = 0; i < nim; i++)
+ numaaDestroy(&n3a[i]);
+ LEPT_FREE(n3a);
+ return 0;
+}
+
+
+/*!
+ * \brief pixComparePhotoRegionsByHisto()
+ *
+ * \param[in] pix1, pix2 any depth; colormap OK
+ * \param[in] box1, box2 [optional] photo regions from each; can be null
+ * \param[in] minratio requiring sizes be compatible; < 1.0
+ * \param[in] factor subsampling factor; >= 1
+ * \param[in] n in range {1, ... 7}. n^2 is the maximum number
+ * of subregions for histograms; typ. n = 3.
+ * \param[out] pscore similarity score of histograms
+ * \param[in] debugflag 1 for debug output; 0 for no debugging
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 not more than n^2 spatially aligned histograms, which are
+ * aligned using the centroid of the inverse image.
+ * (2) The parameter %n specifies the "side" of an n x n grid
+ * of subimages. If the subimages have an aspect ratio larger
+ * than 2, the grid will change, using n^2 as a maximum for
+ * the number of subimages. For example, if n == 3, but the
+ * image is 600 x 200 pixels, a 3x3 grid would have subimages
+ * of 200 x 67 pixels, which is more than 2:1, so we change
+ * to a 4x2 grid where each subimage has 150 x 100 pixels.
+ * (3) 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.
+ * (4) 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.
+ * (5) The white value counts in the histograms are removed; they
+ * are typically pixels that were padded to achieve alignment.
+ * (6) 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.
+ * (7) 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].
+ * (8) 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.
+ * (9) With debug on, you get a pdf that shows, for each tile,
+ * the images, histograms and score.
+ * </pre>
+ */
+l_ok
+pixComparePhotoRegionsByHisto(PIX *pix1,
+ PIX *pix2,
+ BOX *box1,
+ BOX *box2,
+ l_float32 minratio,
+ l_int32 factor,
+ l_int32 n,
+ l_float32 *pscore,
+ l_int32 debugflag)
+{
+l_int32 w1, h1, w2, h2, w1c, h1c, w2c, h2c, debugindex;
+l_float32 wratio, hratio;
+NUMAA *naa1, *naa2;
+PIX *pix3, *pix4;
+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 (n < 1 || n > 7) {
+ L_WARNING("n = %d is invalid; setting to 4\n", procName, n);
+ n = 4;
+ }
+
+ debugindex = 0;
+ if (debugflag) {
+ lept_mkdir("lept/comp");
+ debugindex = 666; /* arbitrary number used for naming output */
+ }
+
+ /* 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, n, &naa1, &w1c, &h1c, debugindex);
+ pixDestroy(&pix3);
+ if (!naa1) return 0;
+ if (box2)
+ pix4 = pixClipRectangle(pix2, box2, NULL);
+ else
+ pix4 = pixClone(pix2);
+ pixGenPhotoHistos(pix4, NULL, factor, 0, n, &naa2, &w2c, &h2c, debugindex);
+ 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;
+}
+
+
+/*!
+ * \brief pixGenPhotoHistos()
+ *
+ * \param[in] pixs depth > 1 bpp; colormap OK
+ * \param[in] box [optional] region to be selected; can be null
+ * \param[in] factor subsampling; >= 1
+ * \param[in] thresh threshold for photo/text; use 0 for default
+ * \param[in] n in range {1, ... 7}. n^2 is the maximum number
+ * of subregions for histograms; typ. n = 3.
+ * \param[out] pnaa nx * ny 256-entry gray histograms
+ * \param[out] pw width of image used to make histograms
+ * \param[out] ph height of image used to make histograms
+ * \param[in] debugindex 0 for no debugging; positive integer otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 parameter %n specifies the "side" of the n x n grid
+ * of subimages. If the subimages have an aspect ratio larger
+ * than 2, the grid will change, using n^2 as a maximum for
+ * the number of subimages. For example, if n == 3, but the
+ * image is 600 x 200 pixels, a 3x3 grid would have subimages
+ * of 200 x 67 pixels, which is more than 2:1, so we change
+ * to a 4x2 grid where each subimage has 150 x 100 pixels.
+ * (3) The white value in the histogram is removed, because of
+ * the padding.
+ * (4) Use 0 for conservative default (1.3) for thresh.
+ * (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) With %debugindex > 0, this makes a pdf that shows, for each tile,
+ * the images and histograms.
+ * </pre>
+ */
+l_ok
+pixGenPhotoHistos(PIX *pixs,
+ BOX *box,
+ l_int32 factor,
+ l_float32 thresh,
+ l_int32 n,
+ NUMAA **pnaa,
+ l_int32 *pw,
+ l_int32 *ph,
+ l_int32 debugindex)
+{
+char buf[64];
+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 (thresh <= 0.0) thresh = 1.3f; /* default */
+ if (n < 1 || n > 7) {
+ L_WARNING("n = %d is invalid; setting to 4\n", procName, n);
+ n = 4;
+ }
+
+ pixa = NULL;
+ if (debugindex > 0) {
+ 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);
+ pixDestroy(&pixm);
+
+ if (debugindex > 0) {
+ 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);
+
+ /* Test if this is a photoimage */
+ pixDecideIfPhotoImage(pix3, factor, thresh, n, &naa, pixa);
+ if (naa) {
+ *pnaa = naa;
+ *pw = pixGetWidth(pix3);
+ *ph = pixGetHeight(pix3);
+ }
+
+ if (pixa) {
+ snprintf(buf, sizeof(buf), "/tmp/lept/comp/tiledhistos.%d.pdf",
+ debugindex);
+ lept_stderr("Writing to %s\n", buf);
+ pixaConvertToPdf(pixa, 300, 1.0, L_FLATE_ENCODE, 0, NULL, buf);
+ pixaDestroy(&pixa);
+ }
+
+ pixDestroy(&pix3);
+ return 0;
+}
+
+
+/*!
+ * \brief pixPadToCenterCentroid()
+ *
+ * \param[in] pixs any depth, colormap OK
+ * \param[in] factor subsampling for centroid; >= 1
+ * \return pixd padded with white pixels, or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCentroid8()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] factor subsampling factor; >= 1
+ * \param[out] pcx x value of centroid
+ * \param[out] pcy y value of centroid
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 = (l_float32)(w) / 2;
+ *pcy = (l_float32)(h) / 2;
+ } else {
+ *pcx = sumx / sumv;
+ *pcy = sumy / sumv;
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixDecideIfPhotoImage()
+ *
+ * \param[in] pix 8 bpp, centroid in center
+ * \param[in] factor subsampling for histograms; >= 1
+ * \param[in] thresh threshold for photo/text; use 0 for default
+ * \param[in] n in range {1, ... 7}. n^2 is the maximum number
+ * of subregions for histograms; typ. n = 3.
+ * \param[out] pnaa array of normalized histograms
+ * \param[in] pixadebug [optional] use only for debug output
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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) The parameter %n specifies the "side" of the n x n grid
+ * of subimages. If the subimages have an aspect ratio larger
+ * than 2, the grid will change, using n^2 as a maximum for
+ * the number of subimages. For example, if n == 3, but the
+ * image is 600 x 200 pixels, a 3x3 grid would have subimages
+ * of 200 x 67 pixels, which is more than 2:1, so we change
+ * to a 4x2 grid where each subimage has 150 x 100 pixels.
+ * (3) If the pix is not almost certainly a photoimage, the returned
+ * histograms (%naa) are null.
+ * (4) 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.
+ * (5) Default for %thresh is 1.3; this seems sufficiently conservative.
+ * (6) Use %pixadebug == NULL unless debug output is requested.
+ * </pre>
+ */
+l_ok
+pixDecideIfPhotoImage(PIX *pix,
+ l_int32 factor,
+ l_float32 thresh,
+ l_int32 n,
+ NUMAA **pnaa,
+ PIXA *pixadebug)
+{
+char buf[64];
+l_int32 i, w, h, nx, ny, ngrids, istext, isphoto;
+l_float32 maxval, sum1, sum2, ratio;
+L_BMF *bmf;
+NUMA *na1, *na2, *na3, *narv;
+NUMAA *naa;
+PIX *pix1;
+PIXA *pixa1, *pixa2, *pixa3;
+
+ 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 (n < 1 || n > 7) {
+ L_WARNING("n = %d is invalid; setting to 4\n", procName, n);
+ n = 4;
+ }
+ if (thresh <= 0.0) thresh = 1.3f; /* default */
+
+ /* Look for text lines */
+ pixDecideIfText(pix, NULL, &istext, pixadebug);
+ if (istext) {
+ L_INFO("Image is text\n", procName);
+ return 0;
+ }
+
+ /* Determine grid from n */
+ pixGetDimensions(pix, &w, &h, NULL);
+ if (w == 0 || h == 0)
+ return ERROR_INT("invalid pix dimension", procName, 1);
+ findHistoGridDimensions(n, w, h, &nx, &ny, 1);
+
+ /* Evaluate histograms in each tile */
+ pixa1 = pixaSplitPix(pix, nx, ny, 0, 0);
+ ngrids = nx * ny;
+ bmf = (pixadebug) ? bmfCreate(NULL, 6) : NULL;
+ naa = numaaCreate(ngrids);
+ if (pixadebug) {
+ lept_rmdir("lept/compplot");
+ lept_mkdir("lept/compplot");
+ }
+ for (i = 0; i < ngrids; i++) {
+ pix1 = pixaGetPix(pixa1, 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/compplot/plot.%d", i);
+ gplotSimple1(na3, GPLOT_PNG, buf, "Histos");
+ }
+
+ numaaAddNuma(naa, na3, L_INSERT);
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ pixDestroy(&pix1);
+ }
+ if (pixadebug) {
+ pix1 = pixaDisplayTiledInColumns(pixa1, nx, 1.0, 30, 2);
+ pixaAddPix(pixadebug, pix1, L_INSERT);
+ pixa2 = pixaReadFiles("/tmp/lept/compplot", ".png");
+ pixa3 = pixaScale(pixa2, 0.4f, 0.4f);
+ pix1 = pixaDisplayTiledInColumns(pixa3, nx, 1.0, 30, 2);
+ pixaAddPix(pixadebug, pix1, L_INSERT);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ }
+
+ /* 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 */
+ ratio = 0.001f; /* anything very small for debug output */
+ 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(&pixa1);
+ return 0;
+}
+
+
+/*!
+ * \brief findHistoGridDimensions()
+ *
+ * \param[in] n max number of grid elements is n^2; typ. n = 3
+ * \param[in] w width of image to be subdivided
+ * \param[in] h height of image to be subdivided
+ * \param[out] pnx number of grid elements in x direction
+ * \param[out] pny number of grid elements in y direction
+ * \param[in] debug 1 for debug output to stderr
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This determines the number of subdivisions to be used on
+ * the image in each direction. A histogram will be built
+ * for each subimage.
+ * (2) The parameter %n specifies the "side" of the n x n grid
+ * of subimages. If the subimages have an aspect ratio larger
+ * than 2, the grid will change, using n^2 as a maximum for
+ * the number of subimages. For example, if n == 3, but the
+ * image is 600 x 200 pixels, a 3x3 grid would have subimages
+ * of 200 x 67 pixels, which is more than 2:1, so we change
+ * to a 4x2 grid where each subimage has 150 x 100 pixels.
+ * </pre>
+ */
+static l_ok
+findHistoGridDimensions(l_int32 n,
+ l_int32 w,
+ l_int32 h,
+ l_int32 *pnx,
+ l_int32 *pny,
+ l_int32 debug)
+{
+l_int32 nx, ny, max;
+l_float32 ratio;
+
+ ratio = (l_float32)w / (l_float32)h;
+ max = n * n;
+ nx = ny = n;
+ while (nx > 1 && ny > 1) {
+ if (ratio > 2.0) { /* reduce ny */
+ ny--;
+ nx = max / ny;
+ if (debug)
+ lept_stderr("nx = %d, ny = %d, ratio w/h = %4.2f\n",
+ nx, ny, ratio);
+ } else if (ratio < 0.5) { /* reduce nx */
+ nx--;
+ ny = max / nx;
+ if (debug)
+ lept_stderr("nx = %d, ny = %d, ratio w/h = %4.2f\n",
+ nx, ny, ratio);
+ } else { /* we're ok */
+ if (debug)
+ lept_stderr("nx = %d, ny = %d, ratio w/h = %4.2f\n",
+ nx, ny, ratio);
+ break;
+ }
+ ratio = (l_float32)(ny * w) / (l_float32)(nx * h);
+ }
+ *pnx = nx;
+ *pny = ny;
+ return 0;
+}
+
+
+/*!
+ * \brief compareTilesByHisto()
+ *
+ * \param[in] naa1, naa2 each is a set of 256 entry histograms
+ * \param[in] minratio requiring image sizes be compatible; < 1.0
+ * \param[in] w1, h1, w2, h2 image sizes from which histograms were made
+ * \param[out] pscore similarity score of histograms
+ * \param[in] pixadebug [optional] use only for debug output
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * (3) The input pixadebug is null unless debug output is requested.
+ * </pre>
+ */
+l_ok
+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 */
+ 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;
+ }
+ n = numaaGetCount(naa1);
+ if (n != numaaGetCount(naa2)) { /* due to differing w/h ratio */
+ L_INFO("naa1 and naa2 sizes are different\n", procName);
+ return 0;
+ }
+
+ if (pixadebug) {
+ lept_rmdir("lept/comptile");
+ lept_mkdir("lept/comptile");
+ }
+
+
+ /* 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);
+ }
+ lept_stderr("Writing to /tmp/lept/comptile/comparegray.pdf\n");
+ pixaConvertToPdf(pixadebug, 300, 1.0, L_FLATE_ENCODE, 0, NULL,
+ "/tmp/lept/comptile/comparegray.pdf");
+ numaWriteDebug("/tmp/lept/comptile/scores.na", nascore);
+ numaWriteDebug("/tmp/lept/comptile/dists.na", nadist);
+ }
+
+ bmfDestroy(&bmf);
+ numaDestroy(&nadist);
+ numaDestroy(&nascore);
+ return 0;
+}
+
+
+/*!
+ * \brief pixCompareGrayByHisto()
+ *
+ * \param[in] pix1, pix2 any depth; colormap OK
+ * \param[in] box1, box2 [optional] region selected from each; can be null
+ * \param[in] minratio requiring sizes be compatible; < 1.0
+ * \param[in] maxgray max value to keep in histo; >= 200, 255 to keep all
+ * \param[in] factor subsampling factor; >= 1
+ * \param[in] n in range {1, ... 7}. n^2 is the maximum number
+ * of subregions for histograms; typ. n = 3.
+ * \param[out] pscore similarity score of histograms
+ * \param[in] debugflag 1 for debug output; 0 for no debugging
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 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) The parameter %n specifies the "side" of an n x n grid
+ * of subimages. If the subimages have an aspect ratio larger
+ * than 2, the grid will change, using n^2 as a maximum for
+ * the number of subimages. For example, if n == 3, but the
+ * image is 600 x 200 pixels, a 3x3 grid would have subimages
+ * of 200 x 67 pixels, which is more than 2:1, so we change
+ * to a 4x2 grid where each subimage has 150 x 100 pixels.
+ * (3) 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.
+ * (4) 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.
+ * (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 %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].
+ * (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.
+ * (9) 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.
+ * </pre>
+ */
+l_ok
+pixCompareGrayByHisto(PIX *pix1,
+ PIX *pix2,
+ BOX *box1,
+ BOX *box2,
+ l_float32 minratio,
+ l_int32 maxgray,
+ l_int32 factor,
+ l_int32 n,
+ 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 (n < 1 || n > 7) {
+ L_WARNING("n = %d is invalid; setting to 4\n", procName, n);
+ n = 4;
+ }
+
+ if (debugflag)
+ 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);
+ pixa = (debugflag) ? pixaCreate(0) : 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, n, pscore, pixa);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ return 0;
+}
+
+
+/*!
+ * \brief pixCompareTilesByHisto()
+ *
+ * \param[in] pix1, pix2 8 bpp
+ * \param[in] maxgray max value to keep in histo; 255 to keep all
+ * \param[in] factor subsampling factor; >= 1
+ * \param[in] n see pixCompareGrayByHisto()
+ * \param[out] pscore similarity score of histograms
+ * \param[in] pixadebug [optional] use only for debug output
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static l_ok
+pixCompareTilesByHisto(PIX *pix1,
+ PIX *pix2,
+ l_int32 maxgray,
+ l_int32 factor,
+ l_int32 n,
+ l_float32 *pscore,
+ PIXA *pixadebug)
+{
+char buf[64];
+l_int32 w, h, i, j, nx, ny, ngr;
+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);
+
+ /* Determine grid from n */
+ pixGetDimensions(pix1, &w, &h, NULL);
+ findHistoGridDimensions(n, w, h, &nx, &ny, 1);
+ ngr = nx * ny;
+
+ /* Evaluate histograms in each tile */
+ pixa1 = pixaSplitPix(pix1, nx, ny, 0, 0);
+ pixa2 = pixaSplitPix(pix2, nx, ny, 0, 0);
+ na7 = (pixadebug) ? numaCreate(ngr) : NULL;
+ bmf = (pixadebug) ? bmfCreate(NULL, 6) : NULL;
+ minscore = 1.0;
+ for (i = 0; i < ngr; 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");
+ numaWriteDebug("/tmp/lept/comp/tilescores.na", na7);
+ }
+
+ bmfDestroy(&bmf);
+ numaDestroy(&na7);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ return 0;
+}
+
+
+/*!
+ * \brief pixCropAlignedToCentroid()
+ *
+ * \param[in] pix1, pix2 any depth; colormap OK
+ * \param[in] factor subsampling; >= 1
+ * \param[out] pbox1 crop box for pix1
+ * \param[out] pbox2 crop box for pix2
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_compressGrayHistograms()
+ *
+ * \param[in] naa set of 256-entry histograms
+ * \param[in] w, h size of image
+ * \param[out] psize size of byte array
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_uncompressGrayHistograms()
+ *
+ * \param[in] bytea byte array of size 8 + 256 * N, N an integer
+ * \param[in] size size of byte array
+ * \param[out] pw width of the image that generated the histograms
+ * \param[out] ph height of the image
+ * \return numaa representing N histograms, each with 256 bins,
+ * or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixCompareWithTranslation()
+ *
+ * \param[in] pix1, pix2 any depth; colormap OK
+ * \param[in] thresh threshold for converting to 1 bpp
+ * \param[out] pdelx x translation on pix2 to align with pix1
+ * \param[out] pdely y translation on pix2 to align with pix1
+ * \param[out] pscore correlation score at best alignment
+ * \param[in] debugflag 1 for debug output; 0 for no debugging
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) {
+ lept_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;
+}
+
+
+/*!
+ * \brief pixBestCorrelation()
+ *
+ * \param[in] pix1 1 bpp
+ * \param[in] pix2 1 bpp
+ * \param[in] area1 number of on pixels in pix1
+ * \param[in] area2 number of on pixels in pix2
+ * \param[in] etransx estimated x translation of pix2 to align with pix1
+ * \param[in] etransy estimated y translation of pix2 to align with pix1
+ * \param[in] maxshift max x and y shift of pix2, around the estimated
+ * alignment location, relative to pix1
+ * \param[in] tab8 [optional] sum tab for ON pixels in byte; can be NULL
+ * \param[out] pdelx [optional] best x shift of pix2 relative to pix1
+ * \param[out] pdely [optional] best y shift of pix2 relative to pix1
+ * \param[out] pscore [optional] maximum score found; can be NULL
+ * \param[in] debugflag <= 0 to skip; positive to generate output.
+ * The integer is used to label the debug image.
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+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);
+/* lept_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) {
+ char buf[128];
+ lept_mkdir("lept/comp");
+ 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/leptonica/src/conncomp.c b/leptonica/src/conncomp.c
new file mode 100644
index 00000000..5b69d384
--- /dev/null
+++ b/leptonica/src/conncomp.c
@@ -0,0 +1,1243 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file conncomp.c
+ * <pre>
+ *
+ * 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()
+ * static 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*!
+ * \brief 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 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);
+
+ /* 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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixConnComp()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[out] ppixa [optional] pixa of each c.c.
+ * \param[in] connectivity 4 or 8
+ * \return boxa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+BOXA *
+pixConnComp(PIX *pixs,
+ PIXA **ppixa,
+ l_int32 connectivity)
+{
+
+ PROCNAME("pixConnComp");
+
+ if (ppixa) *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);
+
+ if (!ppixa)
+ return pixConnCompBB(pixs, connectivity);
+ else
+ return pixConnCompPixa(pixs, ppixa, connectivity);
+}
+
+
+/*!
+ * \brief pixConnCompPixa()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[out] ppixa pixa of each c.c.
+ * \param[in] connectivity 4 or 8
+ * \return boxa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+BOXA *
+pixConnCompPixa(PIX *pixs,
+ PIXA **ppixa,
+ l_int32 connectivity)
+{
+l_int32 h, iszero;
+l_int32 x, y, xstart, ystart;
+PIX *pix1, *pix2, *pix3, *pix4;
+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);
+
+ pix1 = pix2 = pix3 = pix4 = NULL;
+ stack = NULL;
+ pixa = pixaCreate(0);
+ boxa = NULL;
+ *ppixa = pixa;
+ pixZero(pixs, &iszero);
+ if (iszero)
+ return boxaCreate(1); /* return empty boxa and empty pixa */
+
+ pixSetPadBits(pixs, 0);
+ pix1 = pixCopy(NULL, pixs);
+ pix2 = pixCopy(NULL, pixs);
+ if (!pix1 || !pix2) {
+ L_ERROR("pix1 or pix2 not made\n", procName);
+ pixaDestroy(ppixa);
+ goto cleanup;
+ }
+
+ h = pixGetHeight(pixs);
+ if ((stack = lstackCreate(h)) == NULL) {
+ L_ERROR("stack not made\n", procName);
+ pixaDestroy(ppixa);
+ goto cleanup;
+ }
+ auxstack = lstackCreate(0);
+ stack->auxstack = auxstack;
+ boxa = boxaCreate(0);
+
+ xstart = 0;
+ ystart = 0;
+ while (1) {
+ if (!nextOnPixelInRaster(pix1, xstart, ystart, &x, &y))
+ break;
+
+ if ((box = pixSeedfillBB(pix1, stack, x, y, connectivity)) == NULL) {
+ boxaDestroy(&boxa);
+ pixaDestroy(ppixa);
+ L_ERROR("box not made\n", procName);
+ goto cleanup;
+ }
+ boxaAddBox(boxa, box, L_INSERT);
+
+ /* Save the c.c. and remove from pix2 as well */
+ pix3 = pixClipRectangle(pix1, box, NULL);
+ pix4 = pixClipRectangle(pix2, box, NULL);
+ pixXor(pix3, pix3, pix4);
+ pixRasterop(pix2, box->x, box->y, box->w, box->h, PIX_SRC ^ PIX_DST,
+ pix3, 0, 0);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix4);
+
+ xstart = x;
+ ystart = y;
+ }
+
+#if DEBUG
+ pixCountPixels(pix1, &iszero, NULL);
+ lept_stderr("Number of remaining pixels = %d\n", iszero);
+ lept_mkdir("lept/cc");
+ pixWriteDebug("/tmp/lept/cc/remain.png", pix1, IFF_PNG);
+#endif /* DEBUG */
+
+ /* Remove old boxa of pixa and replace with a copy */
+ boxaDestroy(&pixa->boxa);
+ pixa->boxa = boxaCopy(boxa, L_COPY);
+ *ppixa = pixa;
+
+ /* Cleanup, freeing the fillsegs on each stack */
+cleanup:
+ lstackDestroy(&stack, TRUE);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return boxa;
+}
+
+
+/*!
+ * \brief pixConnCompBB()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] connectivity 4 or 8
+ * \return boxa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+BOXA *
+pixConnCompBB(PIX *pixs,
+ l_int32 connectivity)
+{
+l_int32 h, iszero;
+l_int32 x, y, xstart, ystart;
+PIX *pix1;
+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);
+
+ boxa = NULL;
+ pix1 = NULL;
+ stack = NULL;
+ pixZero(pixs, &iszero);
+ if (iszero)
+ return boxaCreate(1); /* return empty boxa */
+
+ pixSetPadBits(pixs, 0);
+ if ((pix1 = pixCopy(NULL, pixs)) == NULL)
+ return (BOXA *)ERROR_PTR("pix1 not made", procName, NULL);
+
+ h = pixGetHeight(pixs);
+ if ((stack = lstackCreate(h)) == NULL) {
+ L_ERROR("stack not made\n", procName);
+ goto cleanup;
+ }
+ auxstack = lstackCreate(0);
+ stack->auxstack = auxstack;
+ boxa = boxaCreate(0);
+
+ xstart = 0;
+ ystart = 0;
+ while (1) {
+ if (!nextOnPixelInRaster(pix1, xstart, ystart, &x, &y))
+ break;
+
+ if ((box = pixSeedfillBB(pix1, stack, x, y, connectivity)) == NULL) {
+ L_ERROR("box not made\n", procName);
+ boxaDestroy(&boxa);
+ goto cleanup;
+ }
+ boxaAddBox(boxa, box, L_INSERT);
+
+ xstart = x;
+ ystart = y;
+ }
+
+#if DEBUG
+ pixCountPixels(pix1, &iszero, NULL);
+ lept_stderr("Number of remaining pixels = %d\n", iszero);
+ lept_mkdir("lept/cc");
+ pixWriteDebug("/tmp/lept/cc/remain.png", pix1, IFF_PNG);
+#endif /* DEBUG */
+
+ /* Cleanup, freeing the fillsegs on each stack */
+cleanup:
+ lstackDestroy(&stack, TRUE);
+ pixDestroy(&pix1);
+ return boxa;
+}
+
+
+/*!
+ * \brief pixCountConnComp()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] connectivity 4 or 8
+ * \param[out] pcount
+ * \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_ok
+pixCountConnComp(PIX *pixs,
+ l_int32 connectivity,
+ l_int32 *pcount)
+{
+l_int32 h, iszero;
+l_int32 x, y, xstart, ystart;
+PIX *pix1;
+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);
+
+ stack = NULL;
+ pixZero(pixs, &iszero);
+ if (iszero)
+ return 0;
+
+ pixSetPadBits(pixs, 0);
+ if ((pix1 = pixCopy(NULL, pixs)) == NULL)
+ return ERROR_INT("pix1 not made", procName, 1);
+ h = pixGetHeight(pixs);
+ if ((stack = lstackCreate(h)) == NULL) {
+ pixDestroy(&pix1);
+ return ERROR_INT("stack not made\n", procName, 1);
+ }
+ auxstack = lstackCreate(0);
+ stack->auxstack = auxstack;
+
+ xstart = 0;
+ ystart = 0;
+ while (1) {
+ if (!nextOnPixelInRaster(pix1, xstart, ystart, &x, &y))
+ break;
+
+ pixSeedfill(pix1, stack, x, y, connectivity);
+ (*pcount)++;
+ xstart = x;
+ ystart = y;
+ }
+
+ /* Cleanup, freeing the fillsegs on each stack */
+ lstackDestroy(&stack, TRUE);
+ pixDestroy(&pix1);
+ return 0;
+}
+
+
+/*!
+ * \brief nextOnPixelInRaster()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] xstart, ystart starting point for search
+ * \param[out] px, py 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);
+}
+
+
+/*!
+ * \brief nextOnPixelInRasterLow()
+ *
+ * \param[in] data pix data
+ * \param[in] w, h width and height
+ * \param[in] wpl words per line
+ * \param[in] xstart, ystart starting point for search
+ * \param[out] px, py coord value of next ON pixel
+ * \return 1 if a pixel is found; 0 otherwise or on error
+ */
+static 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;
+}
+
+
+/*!
+ * \brief pixSeedfillBB()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] stack for holding fillsegs
+ * \param[in] x,y location of seed pixel
+ * \param[in] connectivity 4 or 8
+ * \return box or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is the high-level interface to Paul Heckbert's
+ * stack-based seedfill algorithm.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixSeedfill4BB()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] stack for holding fillsegs
+ * \param[in] x,y location of seed pixel
+ * \return box or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixSeedfill8BB()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] stack for holding fillsegs
+ * \param[in] x,y location of seed pixel
+ * \return box or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixSeedfill()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] stack for holding fillsegs
+ * \param[in] x,y location of seed pixel
+ * \param[in] connectivity 4 or 8
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This removes the component from pixs with a fg pixel at (x,y).
+ * (2) See pixSeedfill4() and pixSeedfill8() for details.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSeedfill4()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] stack for holding fillsegs
+ * \param[in] x,y location of seed pixel
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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()
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSeedfill8()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] stack for holding fillsegs
+ * \param[in] x,y location of seed pixel
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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()
+ * </pre>
+ */
+l_ok
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pushFillsegBB()
+ *
+ * \param[in] stack
+ * \param[in] xleft, xright
+ * \param[in] y
+ * \param[in] dy
+ * \param[in] ymax
+ * \param[out] pminx minimum x
+ * \param[out] pmaxx maximum x
+ * \param[out] pminy minimum y
+ * \param[out] pmaxy maximum y
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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
+ fseg = (FILLSEG *)LEPT_CALLOC(1, sizeof(FILLSEG));
+ fseg->xleft = xleft;
+ fseg->xright = xright;
+ fseg->y = y;
+ fseg->dy = dy;
+ lstackAdd(stack, fseg);
+ }
+}
+
+
+/*!
+ * \brief pushFillseg()
+ *
+ * \param[in] stack
+ * \param[in] xleft, xright
+ * \param[in] y
+ * \param[in] dy
+ * \param[in] ymax
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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
+ fseg = (FILLSEG *)LEPT_CALLOC(1, sizeof(FILLSEG));
+ fseg->xleft = xleft;
+ fseg->xright = xright;
+ fseg->y = y;
+ fseg->dy = dy;
+ lstackAdd(stack, fseg);
+ }
+}
+
+
+/*!
+ * \brief popFillseg()
+ *
+ * \param[in] stack
+ * \param[out] pxleft left x
+ * \param[out] pxright right x
+ * \param[out] py y coordinate
+ * \param[out] pdy delta y
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
diff --git a/leptonica/src/convertfiles.c b/leptonica/src/convertfiles.c
new file mode 100644
index 00000000..7c229e07
--- /dev/null
+++ b/leptonica/src/convertfiles.c
@@ -0,0 +1,149 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file convertfiles.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*------------------------------------------------------------------*
+ * Conversion to 1 bpp *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief convertFilesTo1bpp()
+ *
+ * \param[in] dirin
+ * \param[in] substr [optional] substring filter on filenames;
+ 8 can be NULL
+ * \param[in] upscaling 1, 2 or 4; only for input color or grayscale
+ * \param[in] thresh global threshold for binarization; 0 for default
+ * \param[in] firstpage
+ * \param[in] npages use 0 to do all from %firstpage to the end
+ * \param[in] dirout
+ * \param[in] outformat IFF_PNG, IFF_TIFF_G4
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Images are sorted lexicographically, and the names in the
+ * output directory are retained except for the extension.
+ * </pre>
+ */
+l_ok
+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/leptonica/src/convolve.c b/leptonica/src/convolve.c
new file mode 100644
index 00000000..1db7a7d7
--- /dev/null
+++ b/leptonica/src/convolve.c
@@ -0,0 +1,2582 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file convolve.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixBlockconv()
+ *
+ * \param[in] pix 8 or 32 bpp; or 2, 4 or 8 bpp with colormap
+ * \param[in] wc, hc half width/height of convolution kernel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The full width and height of the convolution kernel
+ * are (2 * wc + 1) and (2 * hc + 1)
+ * (2) Returns a copy if either wc or hc are 0
+ * (3) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
+ * where (w,h) are the dimensions of pixs. Attempt to
+ * reduce the kernel size if necessary.
+ * </pre>
+ */
+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 || hc <= 0)
+ return pixCopy(NULL, pix);
+ pixGetDimensions(pix, &w, &h, &d);
+ if (w < 2 * wc + 1 || h < 2 * hc + 1) {
+ L_WARNING("kernel too large: wc = %d, hc = %d, w = %d, h = %d; "
+ "reducing!\n", procName, wc, hc, w, h);
+ wc = L_MIN(wc, (w - 1) / 2);
+ hc = L_MIN(hc, (h - 1) / 2);
+ }
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixBlockconvGray()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] pixacc pix 32 bpp; can be null
+ * \param[in] wc, hc half width/height of convolution kernel
+ * \return pix 8 bpp, or NULL on error
+ *
+ * <pre>
+ * 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 either wc or hc are 0
+ * (4) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
+ * where (w,h) are the dimensions of pixs. Attempt to
+ * reduce the kernel size if necessary.
+ * </pre>
+ */
+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 || hc <= 0) /* no-op */
+ return pixCopy(NULL, pixs);
+ if (w < 2 * wc + 1 || h < 2 * hc + 1) {
+ L_WARNING("kernel too large: wc = %d, hc = %d, w = %d, h = %d; "
+ "reducing!\n", procName, wc, hc, w, h);
+ wc = L_MIN(wc, (w - 1) / 2);
+ hc = L_MIN(hc, (h - 1) / 2);
+ }
+ if (wc == 0 || hc == 0)
+ 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);
+ }
+
+ pixSetPadBits(pixt, 0);
+ wpl = pixGetWpl(pixd);
+ wpla = pixGetWpl(pixt);
+ datad = pixGetData(pixd);
+ dataa = pixGetData(pixt);
+ blockconvLow(datad, w, h, wpl, dataa, wpla, wc, hc);
+
+ pixDestroy(&pixt);
+ return pixd;
+}
+
+
+/*!
+ * \brief blockconvLow()
+ *
+ * \param[in] data data of input image, to be convolved
+ * \param[in] w, h, wpl
+ * \param[in] dataa data of 32 bpp accumulator
+ * \param[in] wpla accumulator
+ * \param[in] wc convolution "half-width"
+ * \param[in] hc convolution "half-height"
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.0 / ((l_float32)(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 = L_MAX(1, hc + i);
+ normh = (l_float32)fhc / (l_float32)hn; /* >= 1 */
+ line = data + wpl * i;
+ for (j = 0; j <= wc; j++) {
+ wn = L_MAX(1, 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);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------*
+ * Accumulator for 1, 8 and 32 bpp convolution *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixBlockconvAccum()
+ *
+ * \param[in] pixs 1, 8 or 32 bpp
+ * \return accum pix 32 bpp, or NULL on error.
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+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;
+}
+
+
+/*
+ * \brief blockconvAccumLow()
+ *
+ * \param[in] datad 32 bpp dest
+ * \param[in] w, h, wpld of 32 bpp dest
+ * \param[in] datas 1, 8 or 32 bpp src
+ * \param[in] d bpp of src
+ * \param[in] wpls of src
+ * \return void
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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);
+ }
+}
+
+
+/*----------------------------------------------------------------------*
+ * Un-normalized grayscale block convolution *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixBlockconvGrayUnnormalized()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] 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
+ *
+ * <pre>
+ * 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. Attempt to
+ * reduce the kernel size if necessary.
+ * (3) Returns a copy if either wc or 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.
+ * </pre>
+ */
+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 || hc <= 0) /* no-op */
+ return pixCopy(NULL, pixs);
+ if (w < 2 * wc + 1 || h < 2 * hc + 1) {
+ L_WARNING("kernel too large: wc = %d, hc = %d, w = %d, h = %d; "
+ "reducing!\n", procName, wc, hc, w, h);
+ wc = L_MIN(wc, (w - 1) / 2);
+ hc = L_MIN(hc, (h - 1) / 2);
+ }
+ if (wc == 0 || hc == 0)
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixBlockconvTiled()
+ *
+ * \param[in] pix 8 or 32 bpp; or 2, 4 or 8 bpp with colormap
+ * \param[in] wc, hc half width/height of convolution kernel
+ * \param[in] nx, ny subdivision into tiles
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The full width and height of the convolution kernel
+ * are (2 * wc + 1) and (2 * hc + 1)
+ * (2) Returns a copy if either wc or hc are 0.
+ * (3) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
+ * where (w,h) are the dimensions of pixs. Attempt to
+ * reduce the kernel size if necessary.
+ * (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.
+ * </pre>
+ */
+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 || hc <= 0) /* no-op */
+ return pixCopy(NULL, pix);
+ if (nx <= 1 && ny <= 1)
+ return pixBlockconv(pix, wc, hc);
+ pixGetDimensions(pix, &w, &h, &d);
+ if (w < 2 * wc + 3 || h < 2 * hc + 3) {
+ L_WARNING("kernel too large: wc = %d, hc = %d, w = %d, h = %d; "
+ "reducing!\n", procName, wc, hc, w, h);
+ wc = L_MIN(wc, (w - 1) / 2);
+ hc = L_MIN(hc, (h - 1) / 2);
+ }
+ if (wc == 0 || hc == 0)
+ return pixCopy(NULL, pix);
+
+ /* 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 = pixCreateTemplate(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;
+}
+
+
+/*!
+ * \brief pixBlockconvGrayTile()
+ *
+ * \param[in] pixs 8 bpp gray
+ * \param[in] pixacc 32 bpp accum pix
+ * \param[in] wc, hc half width/height of convolution kernel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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 either wc or hc are 0.
+ * (4) Require that w > 2 * wc + 3 and h > 2 * hc + 3,
+ * where (w,h) are the dimensions of pixs. Attempt to
+ * reduce the kernel size if necessary.
+ * </pre>
+ */
+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 || hc <= 0) /* no-op */
+ return pixCopy(NULL, pixs);
+ if (w < 2 * wc + 3 || h < 2 * hc + 3) {
+ L_WARNING("kernel too large: wc = %d, hc = %d, w = %d, h = %d; "
+ "reducing!\n", procName, wc, hc, w, h);
+ wc = L_MIN(wc, (w - 1) / 2);
+ hc = L_MIN(hc, (h - 1) / 2);
+ }
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixWindowedStats()
+ *
+ * \param[in] pixs 8 bpp grayscale
+ * \param[in] wc, hc half width/height of convolution kernel
+ * \param[in] 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)
+ * \param[out] ppixm [optional] 8 bpp mean value in window
+ * \param[out] ppixms [optional] 32 bpp mean square value in window
+ * \param[out] pfpixv [optional] float variance in window
+ * \param[out] pfpixrv [optional] float rms deviation from the mean
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixWindowedMean()
+ *
+ * \param[in] pixs 8 or 32 bpp grayscale
+ * \param[in] wc, hc half width/height of convolution kernel
+ * \param[in] 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)
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ pixb = pixc = pixd = NULL;
+
+ /* Add border if requested */
+ if (!hasborder)
+ pixb = pixAddBorderGeneral(pixs, wc + 1, wc + 1, hc + 1, hc + 1, 0);
+ else
+ pixb = pixClone(pixs);
+
+ /* Make the accumulator pix from pixb */
+ if ((pixc = pixBlockconvAccum(pixb)) == NULL) {
+ L_ERROR("pixc not made\n", procName);
+ goto cleanup;
+ }
+ wplc = pixGetWpl(pixc);
+ datac = pixGetData(pixc);
+
+ /* 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) {
+ L_ERROR("w or h is too small for the kernel\n", procName);
+ goto cleanup;
+ }
+ if ((pixd = pixCreate(wd, hd, d)) == NULL) {
+ L_ERROR("pixd not made\n", procName);
+ goto cleanup;
+ }
+ wpld = pixGetWpl(pixd);
+ datad = pixGetData(pixd);
+
+ wincr = 2 * wc + 1;
+ hincr = 2 * hc + 1;
+ norm = 1.0; /* use this for sum-in-window */
+ if (normflag)
+ norm = 1.0 / ((l_float32)(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;
+ }
+ }
+ }
+
+cleanup:
+ pixDestroy(&pixb);
+ pixDestroy(&pixc);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixWindowedMeanSquare()
+ *
+ * \param[in] pixs 8 bpp grayscale
+ * \param[in] wc, hc half width/height of convolution kernel
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ pixd = 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) {
+ L_ERROR("dpix not made\n", procName);
+ goto cleanup;
+ }
+ 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) {
+ L_ERROR("w or h too small for kernel\n", procName);
+ goto cleanup;
+ }
+ if ((pixd = pixCreate(wd, hd, 32)) == NULL) {
+ L_ERROR("pixd not made\n", procName);
+ goto cleanup;
+ }
+ wpld = pixGetWpl(pixd);
+ datad = pixGetData(pixd);
+
+ wincr = 2 * wc + 1;
+ hincr = 2 * hc + 1;
+ norm = 1.0 / ((l_float32)(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 + 0.5); /* to round up */
+ lined[j] = ival;
+ }
+ }
+
+cleanup:
+ dpixDestroy(&dpix);
+ pixDestroy(&pixb);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixWindowedVariance()
+ *
+ * \param[in] pixm mean over window; 8 or 32 bpp grayscale
+ * \param[in] pixms mean square over window; 32 bpp
+ * \param[out] pfpixv [optional] float variance -- the ms deviation
+ * from the mean
+ * \param[out] pfpixrv [optional] float rms deviation from the mean
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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,
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixMeanSquareAccum()
+ *
+ * \param[in] pixs 8 bpp grayscale
+ * \return dpix 64 bit array, or NULL on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+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] = (l_float64)(val) * val;
+ else
+ line[j] = line[j - 1] + (l_float64)(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] + (l_float64)(val) * val;
+ else
+ line[j] = line[j - 1] + linep[j] - linep[j - 1]
+ + (l_float64)(val) * val;
+ }
+ }
+
+ return dpix;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Binary block sum/rank *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixBlockrank()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] pixacc pix [optional] 32 bpp
+ * \param[in] wc, hc half width/height of block sum/rank kernel
+ * \param[in] rank between 0.0 and 1.0; 0.5 is median filter
+ * \return pixd 1 bpp
+ *
+ * <pre>
+ * 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. Attempt to
+ * reduce the kernel size if necessary.
+ * </pre>
+ */
+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 || hc <= 0)
+ return pixCopy(NULL, pixs);
+ if (w < 2 * wc + 1 || h < 2 * hc + 1) {
+ L_WARNING("kernel too large: wc = %d, hc = %d, w = %d, h = %d; "
+ "reducing!\n", procName, wc, hc, w, h);
+ wc = L_MIN(wc, (w - 1) / 2);
+ hc = L_MIN(hc, (h - 1) / 2);
+ }
+ 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;
+}
+
+
+/*!
+ * \brief pixBlocksum()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] pixacc pix [optional] 32 bpp
+ * \param[in] wc, hc half width/height of block sum/rank kernel
+ * \return pixd 8 bpp
+ *
+ * <pre>
+ * 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. Attempt to
+ * reduce the kernel size if necessary.
+ * (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.
+ * </pre>
+ */
+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 || hc <= 0)
+ return pixCopy(NULL, pixs);
+ if (w < 2 * wc + 1 || h < 2 * hc + 1) {
+ L_WARNING("kernel too large: wc = %d, hc = %d, w = %d, h = %d; "
+ "reducing!\n", procName, wc, hc, w, h);
+ wc = L_MIN(wc, (w - 1) / 2);
+ hc = L_MIN(hc, (h - 1) / 2);
+ }
+ 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;
+}
+
+
+/*!
+ * \brief blocksumLow()
+ *
+ * \param[in] datad of 8 bpp dest
+ * \param[in] w, h, wpl of 8 bpp dest
+ * \param[in] dataa of 32 bpp accum
+ * \param[in] wpla of 32 bpp accum
+ * \param[in] wc, hc convolution "half-width" and "half-height"
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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. / ((l_float32)(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);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------*
+ * Census transform *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixCensusTransform()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] halfsize of square over which neighbors are averaged
+ * \param[in] pixacc [optional] 32 bpp pix
+ * \return pixd 1 bpp
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixConvolve()
+ *
+ * \param[in] pixs 8, 16, 32 bpp; no colormap
+ * \param[in] kel kernel
+ * \param[in] outdepth of pixd: 8, 16 or 32
+ * \param[in] normflag 1 to normalize kernel to unit sum; 0 otherwise
+ * \return pixd 8, 16 or 32 bpp
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ pixd = 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) {
+ L_ERROR("pixt not made\n", procName);
+ goto cleanup;
+ }
+
+ 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);
+ }
+ }
+
+cleanup:
+ kernelDestroy(&keli);
+ kernelDestroy(&keln);
+ pixDestroy(&pixt);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixConvolveSep()
+ *
+ * \param[in] pixs 8, 16, 32 bpp; no colormap
+ * \param[in] kelx x-dependent kernel
+ * \param[in] kely y-dependent kernel
+ * \param[in] outdepth of pixd: 8, 16 or 32
+ * \param[in] normflag 1 to normalize kernel to unit sum; 0 otherwise
+ * \return pixd 8, 16 or 32 bpp
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.0f);
+ kelyn = kernelNormalize(kely, 0.001f);
+ 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;
+}
+
+
+/*!
+ * \brief pixConvolveRGB()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] kel kernel
+ * \return pixd 32 bpp rgb
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvolveRGBSep()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] kelx x-dependent kernel
+ * \param[in] kely y-dependent kernel
+ * \return pixd 32 bpp rgb
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief fpixConvolve()
+ *
+ * \param[in] fpixs 32 bit float array
+ * \param[in] kel kernel
+ * \param[in] normflag 1 to normalize kernel to unit sum; 0 otherwise
+ * \return fpixd 32 bit float array
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ fpixd = 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) {
+ L_ERROR("fpixt not made\n", procName);
+ goto cleanup;
+ }
+
+ 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;
+ }
+ }
+
+cleanup:
+ kernelDestroy(&keli);
+ kernelDestroy(&keln);
+ fpixDestroy(&fpixt);
+ return fpixd;
+}
+
+
+/*!
+ * \brief fpixConvolveSep()
+ *
+ * \param[in] fpixs 32 bit float array
+ * \param[in] kelx x-dependent kernel
+ * \param[in] kely y-dependent kernel
+ * \param[in] normflag 1 to normalize kernel to unit sum; 0 otherwise
+ * \return fpixd 32 bit float array
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvolveWithBias()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] kel1
+ * \param[in] kel2 can be null; use if separable
+ * \param[in] force8 if 1, force output to 8 bpp; otherwise, determine
+ * output depth by the dynamic range of pixel values
+ * \param[out] pbias applied bias
+ * \return pixd 8 or 16 bpp
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief l_setConvolveSampling()
+
+ *
+ * \param[in] xfact, yfact integer >= 1
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) This sets the x and y output subsampling factors for generic pix
+ * and fpix convolution. The default values are 1 (no subsampling).
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixAddGaussianNoise()
+ *
+ * \param[in] pixs 8 bpp gray or 32 bpp rgb; no colormap
+ * \param[in] stdev of noise
+ * \return pixd 8 or 32 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This adds noise to each pixel, taken from a normal
+ * distribution with zero mean and specified standard deviation.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief gaussDistribSampling()
+ *
+ * \return gaussian distributed variable with zero mean and unit stdev
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_float32
+gaussDistribSampling(void)
+{
+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/leptonica/src/correlscore.c b/leptonica/src/correlscore.c
new file mode 100644
index 00000000..c5b0e06b
--- /dev/null
+++ b/leptonica/src/correlscore.c
@@ -0,0 +1,883 @@
+/*====================================================================*
+ - 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+/* -------------------------------------------------------------------- *
+ * Optimized 2 pix correlators (for jbig2 clustering) *
+ * -------------------------------------------------------------------- */
+/*!
+ * \brief pixCorrelationScore()
+ *
+ * \param[in] pix1 test pix, 1 bpp
+ * \param[in] pix2 exemplar pix, 1 bpp
+ * \param[in] area1 number of on pixels in pix1
+ * \param[in] area2 number of on pixels in pix2
+ * \param[in] delx x comp of centroid difference
+ * \param[in] dely y comp of centroid difference
+ * \param[in] maxdiffw max width difference of pix1 and pix2
+ * \param[in] maxdiffh max height difference of pix1 and pix2
+ * \param[in] tab sum tab for byte
+ * \param[out] pscore correlation score
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+/* lept_stderr("score = %5.3f, count = %d, area1 = %d, area2 = %d\n",
+ *pscore, count, area1, area2); */
+ return 0;
+}
+
+
+/*!
+ * \brief pixCorrelationScoreThresholded()
+ *
+ * \param[in] pix1 test pix, 1 bpp
+ * \param[in] pix2 exemplar pix, 1 bpp
+ * \param[in] area1 number of on pixels in pix1
+ * \param[in] area2 number of on pixels in pix2
+ * \param[in] delx x comp of centroid difference
+ * \param[in] dely y comp of centroid difference
+ * \param[in] maxdiffw max width difference of pix1 and pix2
+ * \param[in] maxdiffh max height difference of pix1 and pix2
+ * \param[in] tab sum tab for byte
+ * \param[in] downcount count of 1 pixels below each row of pix1
+ * \param[in] score_threshold
+ * \return whether the correlation score is >= score_threshold
+ *
+ *
+ * <pre>
+ * Notes:
+ * 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.
+ * </pre>
+ */
+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((l_float64)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) {
+ lept_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) *
+ * -------------------------------------------------------------------- */
+/*!
+ * \brief pixCorrelationScoreSimple()
+ *
+ * \param[in] pix1 test pix, 1 bpp
+ * \param[in] pix2 exemplar pix, 1 bpp
+ * \param[in] area1 number of on pixels in pix1
+ * \param[in] area2 number of on pixels in pix2
+ * \param[in] delx x comp of centroid difference
+ * \param[in] dely y comp of centroid difference
+ * \param[in] maxdiffw max width difference of pix1 and pix2
+ * \param[in] maxdiffh max height difference of pix1 and pix2
+ * \param[in] tab sum tab for byte
+ * \param[out] pscore correlation score, in range [0.0 ... 1.0]
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+/* lept_stderr("score = %5.3f, count = %d, area1 = %d, area2 = %d\n",
+ *pscore, count, area1, area2); */
+ return 0;
+}
+
+
+/*!
+ * \brief pixCorrelationScoreShifted()
+ *
+ * \param[in] pix1 1 bpp
+ * \param[in] pix2 1 bpp
+ * \param[in] area1 number of on pixels in pix1
+ * \param[in] area2 number of on pixels in pix2
+ * \param[in] delx x translation of pix2 relative to pix1
+ * \param[in] dely y translation of pix2 relative to pix1
+ * \param[in] tab sum tab for byte
+ * \param[out] pscore correlation score
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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/leptonica/src/dewarp.h b/leptonica/src/dewarp.h
new file mode 100644
index 00000000..37bfb632
--- /dev/null
+++ b/leptonica/src/dewarp.h
@@ -0,0 +1,191 @@
+/*====================================================================*
+ - 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
+
+/*!
+ * \file dewarp.h
+ *
+ * <pre>
+ * 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.
+ *
+ * At the rendering stage, vertical and horizontal disparities are
+ * treated differently. It is somewhat more robust to generate
+ * vertical disparity models (VDM) than horizontal disparity
+ * models (HDM). A valid VDM is required for any correction to
+ * be made; if a valid VDM is not available, just use the input
+ * image. Otherwise, assuming it is available, the use of the
+ * HDM is controlled by two fields: 'useboth' and 'check_columns'.
+ * (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.
+ * (c) With check_columns == 1, check for multiple columns and if
+ * true, only use the VDM, even if a valid HDM is available.
+ * Note that 'check_columns' takes precedence over 'useboth'
+ * when there is more than 1 column of text. By default,
+ * check_columns == 0.
+ *
+ * The 'maxdist' parameter is input when the dewarpa is created.
+ * The other rendering parameters have default values given in dewarp1.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.
+ * </pre>
+ */
+
+ /*! Dewarp version for serialization
+ * <pre>
+ * 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.
+ * </pre>
+ */
+#define DEWARP_VERSION_NUMBER 4
+
+/*! Data structure to hold a number of Dewarp */
+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 page */
+ 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; only vertical otherwise */
+ l_int32 check_columns; /*!< if there are multiple columns, */
+ /*!< only use the vertical disparity */
+ /*!< array */
+ l_int32 modelsready; /*!< invalid models have been removed */
+ /*!< and refs built against valid set */
+};
+typedef struct L_Dewarpa L_DEWARPA;
+
+
+/*! Data structure for a single dewarp */
+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 *sampydispar; /*!< sampled slope h-disparity array */
+ struct FPix *fullvdispar; /*!< full vert disparity array */
+ struct FPix *fullhdispar; /*!< full horiz disparity array */
+ struct FPix *fullydispar; /*!< full slope h-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 ysuccess; /*!< sets to 1 if slope disparity builds */
+ l_int32 vvalid; /*!< sets to 1 if valid vert disparity */
+ l_int32 hvalid; /*!< sets to 1 if valid horiz disparity */
+ l_int32 skip_horiz; /*!< if 1, skip horiz disparity */
+ /*!< correction */
+ l_int32 debug; /*!< set to 1 if debug output requested */
+};
+typedef struct L_Dewarp L_DEWARP;
+
+#endif /* LEPTONICA_DEWARP_H */
diff --git a/leptonica/src/dewarp1.c b/leptonica/src/dewarp1.c
new file mode 100644
index 00000000..362f2ee0
--- /dev/null
+++ b/leptonica/src/dewarp1.c
@@ -0,0 +1,1715 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file dewarp1.c
+ * <pre>
+ *
+ * Basic operations and serialization
+ *
+ * Create/destroy dewarp
+ * L_DEWARP *dewarpCreate()
+ * L_DEWARP *dewarpCreateRef()
+ * void dewarpDestroy()
+ *
+ * Create/destroy dewarpa
+ * L_DEWARPA *dewarpaCreate()
+ * L_DEWARPA *dewarpaCreateFromPixacomp()
+ * void dewarpaDestroy()
+ * l_int32 dewarpaDestroyDewarp()
+ *
+ * Dewarpa insertion/extraction
+ * l_int32 dewarpaInsertDewarp()
+ * static l_int32 dewarpaExtendArraysToSize()
+ * L_DEWARP *dewarpaGetDewarp()
+ *
+ * Setting parameters to control rendering from the model
+ * l_int32 dewarpaSetCurvatures()
+ * l_int32 dewarpaUseBothArrays()
+ * l_int32 dewarpaSetCheckColumns()
+ * l_int32 dewarpaSetMaxDistance()
+ *
+ * Dewarp serialized I/O
+ * L_DEWARP *dewarpRead()
+ * L_DEWARP *dewarpReadStream()
+ * L_DEWARP *dewarpReadMem()
+ * l_int32 dewarpWrite()
+ * l_int32 dewarpWriteStream()
+ * l_int32 dewarpWriteMem()
+ *
+ * Dewarpa serialized I/O
+ * L_DEWARPA *dewarpaRead()
+ * L_DEWARPA *dewarpaReadStream()
+ * L_DEWARPA *dewarpaReadMem()
+ * l_int32 dewarpaWrite()
+ * l_int32 dewarpaWriteStream()
+ * l_int32 dewarpaWriteMem()
+ *
+ *
+ * Examples of usage
+ * =================
+ *
+ * See dewarpaCreateFromPixacomp() for an example of the basic
+ * operations, starting from a set of 1 bpp images.
+ *
+ * Basic functioning to dewarp a specific single page:
+ * \code
+ * // Make the Dewarpa for the pages
+ * L_Dewarpa *dewa = dewarpaCreate(1, 30, 1, 15, 50);
+ * dewarpaSetCurvatures(dewa, -1, 50, -1, -1, -1, -1);
+ * dewarpaUseBothArrays(dewa, 1); // try to use both disparity
+ * // arrays for this example
+ *
+ * // Do the page: start with a binarized image
+ * Pix *pixb = "binarize"(pixs);
+ * // Initialize a Dewarp for this page (say, page 214)
+ * L_Dewarp *dew = dewarpCreate(pixb, 214);
+ * // Insert in Dewarpa and obtain parameters for building the model
+ * dewarpaInsertDewarp(dewa, dew);
+ * // Do the work
+ * dewarpBuildPageModel(dew, NULL); // no debugging
+ * // Optionally set rendering parameters
+ * // Apply model to the input pixs
+ * Pix *pixd;
+ * dewarpaApplyDisparity(dewa, 214, pixs, 255, 0, 0, &pixd, NULL);
+ * pixDestroy(&pixb);
+ * \endcode
+ *
+ * Basic functioning to dewarp many pages:
+ * \code
+ * // Make the Dewarpa for the set of pages; use fullres 1 bpp
+ * L_Dewarpa *dewa = dewarpaCreate(10, 30, 1, 15, 50);
+ * // Optionally set rendering parameters
+ * dewarpaSetCurvatures(dewa, -1, 30, -1, -1, -1, -1);
+ * dewarpaUseBothArrays(dewa, 0); // just use the vertical disparity
+ * // array for this example
+ *
+ * // Do first page: start with a binarized image
+ * Pix *pixb = "binarize"(pixs);
+ * // Initialize a Dewarp for this page (say, page 1)
+ * L_Dewarp *dew = dewarpCreate(pixb, 1);
+ * // Insert in Dewarpa and obtain parameters for building the model
+ * dewarpaInsertDewarp(dewa, dew);
+ * // Do the work
+ * dewarpBuildPageModel(dew, NULL); // no debugging
+ * dewarpMinimze(dew); // remove most heap storage
+ * pixDestroy(&pixb);
+ *
+ * // Do the other pages the same way
+ * ...
+ *
+ * // Apply models to each page; if the page model is invalid,
+ * // try to use a valid neighboring model. Note that the call
+ * // to dewarpaInsertRefModels() is optional, because it is called
+ * // by dewarpaApplyDisparity() on the first page it acts on.
+ * dewarpaInsertRefModels(dewa, 0, 1); // use debug flag to get more
+ * // detailed information about the page models
+ * [For each page, where pixs is the fullres image to be dewarped] {
+ * L_Dewarp *dew = dewarpaGetDewarp(dewa, pageno);
+ * if (dew) { // disparity model exists
+ * Pix *pixd;
+ * dewarpaApplyDisparity(dewa, pageno, pixs, 255,
+ * 0, 0, &pixd, NULL);
+ * dewarpMinimize(dew); // clean out the pix and fpix arrays
+ * // Squirrel pixd away somewhere ...)
+ * }
+ * }
+ * \endcode
+ *
+ * Basic functioning to dewarp a small set of pages, potentially
+ * using models from nearby pages:
+ * \code
+ * // (1) Generate a set of binarized images in the vicinity of the
+ * // pages to be dewarped. We will attempt to compute models
+ * // for pages from 'firstpage' to 'lastpage'.
+ * // Store the binarized images in a compressed array of
+ * // size 'n', where 'n' is the number of images to be stored,
+ * // and where the offset is the first page.
+ * PixaComp *pixac = pixacompCreateInitialized(n, firstpage, NULL,
+ * IFF_TIFF_G4);
+ * for (i = firstpage; i <= lastpage; i++) {
+ * Pix *pixb = "binarize"(pixs);
+ * pixacompReplacePix(pixac, i, pixb, IFF_TIFF_G4);
+ * pixDestroy(&pixb);
+ * }
+ *
+ * // (2) Make the Dewarpa for the pages.
+ * L_Dewarpa *dewa =
+ * dewarpaCreateFromPixacomp(pixac, 30, 15, 20);
+ * dewarpaUseBothArrays(dewa, 1); // try to use both disparity arrays
+ * // in this example
+ *
+ * // (3) Finally, apply the models. For page 'firstpage' with image pixs:
+ * L_Dewarp *dew = dewarpaGetDewarp(dewa, firstpage);
+ * if (dew) { // disparity model exists
+ * Pix *pixd;
+ * dewarpaApplyDisparity(dewa, firstpage, pixs, 255, 0, 0, &pixd, NULL);
+ * dewarpMinimize(dew);
+ * }
+ * \endcode
+ *
+ * Because in general some pages will not have enough text to build a
+ * model, we fill in for those pages with a reference to the page
+ * model to use. Both the target page and the reference page must
+ * have the same parity. We can also choose to use either a partial model
+ * (with only vertical disparity) or the full model of a nearby page.
+ *
+ * Minimizing the data in a model by stripping out images,
+ * numas, and full resolution disparity arrays:
+ * dewarpMinimize(dew);
+ * This can be done at any time to save memory. Serialization does
+ * not use the data that is stripped.
+ *
+ * You can apply any model (in a dew), stripped or not, to another image:
+ * \code
+ * // For all pages with invalid models, assign the nearest valid
+ * // page model with same parity.
+ * dewarpaInsertRefModels(dewa, 0, 0);
+ * // You can then apply to 'newpix' the page model that was assigned
+ * // to 'pageno', giving the result in pixd:
+ * Pix *pixd;
+ * dewarpaApplyDisparity(dewa, pageno, newpix, 255, 0, 0, &pixd, NULL);
+ * \endcode
+ *
+ * You can apply the disparity arrays to a deliberately undercropped
+ * image. Suppose that you undercrop by (left, right, top, bot), so
+ * that the disparity arrays are aligned with their origin at (left, top).
+ * Dewarp the undercropped image with:
+ * \code
+ * Pix *pixd;
+ * dewarpaApplyDisparity(dewa, pageno, undercropped_pix, 255,
+ * left, top, &pixd, NULL);
+ * \endcode
+ *
+ * Description of the approach to analyzing page image distortion
+ * ==============================================================
+ *
+ * When a book page is scanned, there are several possible causes
+ * for the text lines to appear to be curved:
+ * (1) A barrel (fish-eye) effect because the camera is at
+ * a finite distance from the page. Take the normal from
+ * the camera to the page (the 'optic axis'). Lines on
+ * the page "below" this point will appear to curve upward
+ * (negative curvature); lines "above" this will curve downward.
+ * (2) Radial distortion from the camera lens. Probably not
+ * a big factor.
+ * (3) Local curvature of the page in to (or out of) the image
+ * plane (which is perpendicular to the optic axis).
+ * This has no effect if the page is flat.
+ *
+ * In the following, the optic axis is in the z direction and is
+ * perpendicular to the xy plane;, the book is assumed to be aligned
+ * so that y is approximately along the binding.
+ * The goal is to compute the "disparity" field, D(x,y), which
+ * is actually a vector composed of the horizontal and vertical
+ * disparity fields H(x,y) and V(x,y). Each of these is a local
+ * function that gives the amount each point in the image is
+ * required to move in order to rectify the horizontal and vertical
+ * lines. It would also be nice to "flatten" the page to compensate
+ * for effect (3), foreshortening due to bending of the page into
+ * the z direction, but that is more difficult.
+ *
+ * Effects (1) and (2) can be directly compensated by calibrating
+ * the scene, using a flat page with horizontal and vertical lines.
+ * Then H(x,y) and V(x,y) can be found as two (non-parametric) arrays
+ * of values. Suppose this has been done. Then the remaining
+ * distortion is due to (3).
+ *
+ * We consider the simple situation where the page bending is independent
+ * of y, and is described by alpha(x), where alpha is the angle between
+ * the normal to the page and the optic axis. cos(alpha(x)) is the local
+ * compression factor of the page image in the horizontal direction, at x.
+ * Thus, if we know alpha(x), we can compute the disparity H(x) required
+ * to flatten the image by simply integrating 1/cos(alpha), and we could
+ * compute the remaining disparities, H(x,y) and V(x,y), from the
+ * page content, as described below. Unfortunately, we don't know
+ * alpha. What do we know? If there are horizontal text lines
+ * on the page, we can compute the vertical disparity, V(x,y), which
+ * is the local translation required to make the text lines parallel
+ * to the rasters. If the margins are left and right aligned, we can
+ * also estimate the horizontal disparity, H(x,y), required to have
+ * uniform margins. All that can be done from the image alone,
+ * assuming we have text lines covering a sufficient part of the page.
+ *
+ * What about alpha(x)? The basic question relating to (3) is this:
+ *
+ * Is it possible, using the shape of the text lines alone,
+ * to compute both the vertical and horizontal disparity fields?
+ *
+ * The underlying problem is to separate the line curvature effects due
+ * to the camera view from those due to actual bending of the page.
+ * I believe the proper way to do this is to make some measurements
+ * based on the camera setup, which will depend mostly on the distance
+ * of the camera from the page, and to a smaller extent on the location
+ * of the optic axis with respect to the page.
+ *
+ * Here is the procedure. Photograph a page with a fine 2D line grid
+ * several times, each with a different slope near the binding.
+ * This can be done by placing the grid page on books that have
+ * different shapes z(x) near the binding. For each one you can
+ * measure, near the binding:
+ * (1) ds/dy, the vertical rate of change of slope of the horizontal lines
+ * (2) the local horizontal compression of the vertical lines due
+ * to the page angle dz/dx.
+ * As mentioned above, the local horizontal compression is simply
+ * cos(dz/dx). But the measurement you can make on an actual book
+ * page is (1). The difficulty is to generate (2) from (1).
+ *
+ * Back to the procedure. The function in (1), ds/dy, likely needs
+ * to be measured at a few y locations, because the relation
+ * between (1) and (2) may weakly depend on the y-location with
+ * respect to the y-coordinate of the optic axis of the camera.
+ * From these measurements you can determine, for the camera setup
+ * that you have, the local horizontal compression, cos(dz/dx), as a
+ * function of the both vertical location (y) and your measured vertical
+ * derivative of the text line slope there, ds/dy. Then with
+ * appropriate smoothing of your measured values, you can set up a
+ * horizontal disparity array to correct for the compression due
+ * to dz/dx.
+ *
+ * Now consider V(x,0) and V(x,h), the vertical disparity along
+ * the top and bottom of the image. With a little thought you
+ * can convince yourself that the local foreshortening,
+ * as a function of x, is proportional to the difference
+ * between the slope of V(x,0) and V(x,h). The horizontal
+ * disparity can then be computed by integrating the local foreshortening
+ * over x. Integration of the slope of V(x,0) and V(x,h) gives
+ * the vertical disparity itself. We have to normalize to h, the
+ * height of the page. So the very simple result is that
+ *
+ * H(x) ~ (V(x,0) - V(x,h)) / h [1]
+ *
+ * which is easily computed. There is a proportionality constant
+ * that depends on the ratio of h to the distance to the camera.
+ * Can we actually believe this for the case where the bending
+ * is independent of y? I believe the answer is yes,
+ * as long as you first remove the apparent distortion due
+ * to the camera being at a finite distance.
+ *
+ * If you know the intersection of the optical axis with the page
+ * and the distance to the camera, and if the page is perpendicular
+ * to the optic axis, you can compute the horizontal and vertical
+ * disparities due to (1) and (2) and remove them. The resulting
+ * distortion should be entirely due to bending (3), for which
+ * the relation
+ *
+ * Hx(x) dx = C * ((Vx(x,0) - Vx(x, h))/h) dx [2]
+ *
+ * holds for each point in x (Hx and Vx are partial derivatives w/rt x).
+ * Integrating over x, and using H(0) = 0, we get the result [1].
+ *
+ * I believe this result holds differentially for each value of y, so
+ * that in the case where the bending is not independent of y,
+ * the expression (V(x,0) - V(x,h)) / h goes over to Vy(x,y). Then
+ *
+ * H(x,y) = Integral(0,x) (Vyx(x,y) dx) [3]
+ *
+ * where Vyx() is the partial derivative of V w/rt both x and y.
+ *
+ * It would be nice if there were a simple mathematical relation between
+ * the horizontal and vertical disparities for the situation
+ * where the paper bends without stretching or kinking.
+ * I had hoped to get a relation between H and V, such as
+ * Hx(x,y) ~ Vy(x,y), which would imply that H and V are real
+ * and imaginary parts of a complex potential, each of which
+ * satisfy the laplace equation. But then the gradients of the
+ * two potentials would be normal, and that does not appear to be the case.
+ * Thus, the questions of proving the relations above (for small bending),
+ * or finding a simpler relation between H and V than those equations,
+ * remain open. So far, we have only used [1] for the horizontal
+ * disparity H(x).
+ *
+ * In the version of the code that follows, we first use text lines
+ * to find V(x,y). Then, we try to compute H(x,y) that will align
+ * the text vertically on the left and right margins. This is not
+ * always possible -- sometimes the right margin is not right justified.
+ * By default, we don't require the horizontal disparity to have a
+ * valid page model for dewarping a page, but this requirement can
+ * be forced using dewarpaUseFullModel().
+ *
+ * As described above, one can add a y-independent component of
+ * the horizontal disparity H(x) to counter the foreshortening
+ * effect due to the bending of the page near the binding.
+ * This requires widening the image on the side near the binding,
+ * and we do not provide this option here. However, we do provide
+ * a function that will generate this disparity field:
+ * fpixExtraHorizDisparity()
+ *
+ * Here is the basic outline for building the disparity arrays.
+ *
+ * (1) Find lines going approximately through the center of the
+ * text in each text line. Accept only lines that are
+ * close in length to the longest line.
+ * (2) Use these lines to generate a regular and highly subsampled
+ * vertical disparity field V(x,y).
+ * (3) Interpolate this to generate a full resolution vertical
+ * disparity field.
+ * (4) For lines that are sufficiently long, assume they are approximately
+ * left and right-justified, and construct a highly subsampled
+ * horizontal disparity field H(x,y) that will bring them into alignment.
+ * (5) Interpolate this to generate a full resolution horizontal
+ * disparity field.
+ * (6) Apply the vertical dewarping, followed by the horizontal dewarping.
+ *
+ * Step (1) is clearly described by the code in pixGetTextlineCenters().
+ *
+ * Steps (2) and (3) follow directly from the data in step (1),
+ * and constitute the bulk of the work done in dewarpBuildPageModel().
+ * Virtually all the noise in the data is smoothed out by doing
+ * least-square quadratic fits, first horizontally to the data
+ * points representing the text line centers, and then vertically.
+ * The trick is to sample these lines on a regular grid.
+ * First each horizontal line is sampled at equally spaced
+ * intervals horizontally. We thus get a set of points,
+ * one in each line, that are vertically aligned, and
+ * the data we represent is the vertical distance of each point
+ * from the min or max value on the curve, depending on the
+ * sign of the curvature component. Each of these vertically
+ * aligned sets of points constitutes a sampled vertical disparity,
+ * and we do a LS quartic fit to each of them, followed by
+ * vertical sampling at regular intervals. We now have a subsampled
+ * grid of points, all equally spaced, giving at each point the local
+ * vertical disparity. Finally, the full resolution vertical disparity
+ * is formed by interpolation. All the least square fits do a
+ * great job of smoothing everything out, as can be observed by
+ * the contour maps that are generated for the vertical disparity field.
+ *
+ * Steps (4) through (6) again use the line data in step (1).
+ * By default, we do separate quadratic fits to the left and right
+ * line edges. There is also the option to do linear fits to the
+ * line edges, which typically does not give as good a fit, but is
+ * safer for some pages that have text in the margins, or have multiple
+ * columns of text with a large space between the columns. There is
+ * an option, which is the default, to check for multiple columns and
+ * if found to skip dewarping based on the line edges -- we compute but
+ * do not use the horizontal disparity array.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+static l_int32 dewarpaExtendArraysToSize(L_DEWARPA *dewa, l_int32 size);
+
+ /* Parameter values used in dewarpaCreate() */
+static const l_int32 InitialPtrArraySize = 20; /* n'import quoi */
+static const l_int32 MaxPtrArraySize = 10000;
+static const l_int32 DefaultArraySampling = 30;
+static const l_int32 MinArraySampling = 8;
+static const l_int32 DefaultMinLines = 15;
+static const l_int32 MinMinLines = 4;
+static const l_int32 DefaultMaxRefDist = 16;
+static const l_int32 DefaultUseBoth = TRUE;
+static const l_int32 DefaultCheckColumns = TRUE;
+
+ /* Parameter values used in dewarpaSetCurvatures() */
+static const l_int32 DefaultMaxLineCurv = 150;
+static const l_int32 DefaultMinDiffLineCurv = 0;
+static const l_int32 DefaultMaxDiffLineCurv = 170;
+static const l_int32 DefaultMaxEdgeCurv = 50;
+static const l_int32 DefaultMaxDiffEdgeCurv = 40;
+static const l_int32 DefaultMaxEdgeSlope = 80;
+
+/*----------------------------------------------------------------------*
+ * Create/destroy Dewarp *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpCreate()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] pageno page number
+ * \return dew or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The input pixs is either full resolution or 2x reduced.
+ * (2) The page number is typically 0-based. If scanned from a book,
+ * the even pages are usually on the left. Disparity arrays
+ * built for even pages should only be applied to even pages.
+ * </pre>
+ */
+L_DEWARP *
+dewarpCreate(PIX *pixs,
+ l_int32 pageno)
+{
+L_DEWARP *dew;
+
+ 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);
+
+ dew = (L_DEWARP *)LEPT_CALLOC(1, sizeof(L_DEWARP));
+ dew->pixs = pixClone(pixs);
+ dew->pageno = pageno;
+ dew->w = pixGetWidth(pixs);
+ dew->h = pixGetHeight(pixs);
+ return dew;
+}
+
+
+/*!
+ * \brief dewarpCreateRef()
+ *
+ * \param[in] pageno this page number
+ * \param[in] refpage page number of dewarp disparity arrays to be used
+ * \return dew or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This specifies which dewarp struct should be used for
+ * the given page. It is placed in dewarpa for pages
+ * for which no model can be built.
+ * (2) This page and the reference page have the same parity and
+ * the reference page is the closest page with a disparity model
+ * to this page.
+ * </pre>
+ */
+L_DEWARP *
+dewarpCreateRef(l_int32 pageno,
+ l_int32 refpage)
+{
+L_DEWARP *dew;
+
+ dew = (L_DEWARP *)LEPT_CALLOC(1, sizeof(L_DEWARP));
+ dew->pageno = pageno;
+ dew->hasref = 1;
+ dew->refpage = refpage;
+ return dew;
+}
+
+
+/*!
+ * \brief dewarpDestroy()
+ *
+ * \param[in,out] pdew will be set to null before returning
+ * \return void
+ */
+void
+dewarpDestroy(L_DEWARP **pdew)
+{
+L_DEWARP *dew;
+
+ 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->sampydispar);
+ fpixDestroy(&dew->fullvdispar);
+ fpixDestroy(&dew->fullhdispar);
+ fpixDestroy(&dew->fullydispar);
+ numaDestroy(&dew->namidys);
+ numaDestroy(&dew->nacurves);
+ LEPT_FREE(dew);
+ *pdew = NULL;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Create/destroy Dewarpa *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpaCreate()
+ *
+ * \param[in] nptrs number of dewarp page ptrs; typ. the number of pages
+ * \param[in] sampling use 0 for default value; the minimum allowed is 8
+ * \param[in] redfactor of input images: 1 is full res; 2 is 2x reduced
+ * \param[in] minlines minimum number of lines to accept; use 0 for default
+ * \param[in] maxdist for locating reference disparity; use -1 for default
+ * \return dewa or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The sampling, minlines and maxdist parameters will be
+ * applied to all images.
+ * (2) The sampling factor is used for generating the disparity arrays
+ * from the input image. For 2x reduced input, use a sampling
+ * factor that is half the sampling you want on the full resolution
+ * images.
+ * (3) Use %redfactor = 1 for full resolution; 2 for 2x reduction.
+ * All input images must be at one of these two resolutions.
+ * (4) %minlines is the minimum number of nearly full-length lines
+ * required to generate a vertical disparity array. The default
+ * number is 15. Use a smaller number to accept a questionable
+ * array, but not smaller than 4.
+ * (5) When a model can't be built for a page, it looks up to %maxdist
+ * in either direction for a valid model with the same page parity.
+ * Use -1 for the default value of %maxdist; use 0 to avoid using
+ * a ref model.
+ * (6) The ptr array is expanded as necessary to accommodate page images.
+ * </pre>
+ */
+L_DEWARPA *
+dewarpaCreate(l_int32 nptrs,
+ l_int32 sampling,
+ l_int32 redfactor,
+ l_int32 minlines,
+ l_int32 maxdist)
+{
+L_DEWARPA *dewa;
+
+ PROCNAME("dewarpaCreate");
+
+ if (nptrs <= 0)
+ nptrs = InitialPtrArraySize;
+ if (nptrs > MaxPtrArraySize)
+ 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 = DefaultArraySampling;
+ } else if (sampling < MinArraySampling) {
+ L_WARNING("sampling too small; setting to %d\n", procName,
+ MinArraySampling);
+ sampling = MinArraySampling;
+ }
+ if (minlines == 0) {
+ minlines = DefaultMinLines;
+ } else if (minlines < MinMinLines) {
+ L_WARNING("minlines too small; setting to %d\n", procName,
+ MinMinLines);
+ minlines = DefaultMinLines;
+ }
+ if (maxdist < 0)
+ maxdist = DefaultMaxRefDist;
+
+ dewa = (L_DEWARPA *)LEPT_CALLOC(1, sizeof(L_DEWARPA));
+ dewa->dewarp = (L_DEWARP **)LEPT_CALLOC(nptrs, sizeof(L_DEWARPA *));
+ dewa->dewarpcache = (L_DEWARP **)LEPT_CALLOC(nptrs, sizeof(L_DEWARPA *));
+ if (!dewa->dewarp || !dewa->dewarpcache) {
+ dewarpaDestroy(&dewa);
+ return (L_DEWARPA *)ERROR_PTR("dewarp ptrs not made", procName, NULL);
+ }
+ dewa->nalloc = nptrs;
+ dewa->sampling = sampling;
+ dewa->redfactor = redfactor;
+ dewa->minlines = minlines;
+ dewa->maxdist = maxdist;
+ dewa->max_linecurv = DefaultMaxLineCurv;
+ dewa->min_diff_linecurv = DefaultMinDiffLineCurv;
+ dewa->max_diff_linecurv = DefaultMaxDiffLineCurv;
+ dewa->max_edgeslope = DefaultMaxEdgeSlope;
+ dewa->max_edgecurv = DefaultMaxEdgeCurv;
+ dewa->max_diff_edgecurv = DefaultMaxDiffEdgeCurv;
+ dewa->check_columns = DefaultCheckColumns;
+ dewa->useboth = DefaultUseBoth;
+ return dewa;
+}
+
+
+/*!
+ * \brief dewarpaCreateFromPixacomp()
+ *
+ * \param[in] pixac pixacomp of G4, 1 bpp images; with 1x1x1 placeholders
+ * \param[in] useboth 0 for only vert disparity; 1 for both vert and horiz
+ * \param[in] sampling use -1 or 0 for default value; otherwise minimum of 5
+ * \param[in] minlines minimum number of lines to accept; e.g., 10
+ * \param[in] maxdist for locating reference disparity; use -1 for default
+ * \return dewa or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The returned dewa has disparity arrays calculated and
+ * is ready for serialization or for use in dewarping.
+ * (2) The sampling, minlines and maxdist parameters are
+ * applied to all images. See notes in dewarpaCreate() for details.
+ * (3) The pixac is full. Placeholders, if any, are w=h=d=1 images,
+ * and the real input images are 1 bpp at full resolution.
+ * They are assumed to be cropped to the actual page regions,
+ * and may be arbitrarily sparse in the array.
+ * (4) The output dewarpa is indexed by the page number.
+ * The offset in the pixac gives the mapping between the
+ * array index in the pixac and the page number.
+ * (5) This adds the ref page models.
+ * (6) This can be used to make models for any desired set of pages.
+ * The direct models are only made for pages with images in
+ * the pixacomp; the ref models are made for pages of the
+ * same parity within %maxdist of the nearest direct model.
+ * </pre>
+ */
+L_DEWARPA *
+dewarpaCreateFromPixacomp(PIXAC *pixac,
+ l_int32 useboth,
+ l_int32 sampling,
+ l_int32 minlines,
+ l_int32 maxdist)
+{
+l_int32 i, nptrs, pageno;
+L_DEWARP *dew;
+L_DEWARPA *dewa;
+PIX *pixt;
+
+ 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;
+}
+
+
+/*!
+ * \brief dewarpaDestroy()
+ *
+ * \param[in,out] pdewa will be set to null before returning
+ * \return void
+ */
+void
+dewarpaDestroy(L_DEWARPA **pdewa)
+{
+l_int32 i;
+L_DEWARP *dew;
+L_DEWARPA *dewa;
+
+ 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;
+}
+
+
+/*!
+ * \brief dewarpaDestroyDewarp()
+ *
+ * \param[in] dewa
+ * \param[in] pageno of dew to be destroyed
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+dewarpaDestroyDewarp(L_DEWARPA *dewa,
+ l_int32 pageno)
+{
+L_DEWARP *dew;
+
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpaInsertDewarp()
+ *
+ * \param[in] dewa
+ * \param[in] dew to be added
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This inserts the dewarp into the array, which now owns it.
+ * It also keeps track of the largest page number stored.
+ * It must be done before the disparity model is built.
+ * (2) Note that this differs from the usual method of filling out
+ * arrays in leptonica, where the arrays are compact and
+ * new elements are typically added to the end. Here,
+ * the dewarp can be added anywhere, even beyond the initial
+ * allocation.
+ * </pre>
+ */
+l_ok
+dewarpaInsertDewarp(L_DEWARPA *dewa,
+ L_DEWARP *dew)
+{
+l_int32 pageno, n, newsize;
+L_DEWARP *prevdew;
+
+ 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 > MaxPtrArraySize)
+ 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) {
+ if (dewarpaExtendArraysToSize(dewa, newsize))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+
+ if ((prevdew = dewarpaGetDewarp(dewa, pageno)) != NULL)
+ dewarpDestroy(&prevdew);
+ dewa->dewarp[pageno] = dew;
+
+ dew->sampling = dewa->sampling;
+ dew->redfactor = dewa->redfactor;
+ dew->minlines = dewa->minlines;
+
+ /* Get the dimensions of the sampled array. This will be
+ * stored in an fpix, and the input resolution version is
+ * guaranteed to be larger than pixs. However, if you
+ * want to apply the disparity to an image with a width
+ * w > nx * s - 2 * s + 2
+ * you will need to extend the input res fpix.
+ * And similarly for h. */
+ dew->nx = (dew->w + 2 * dew->sampling - 2) / dew->sampling;
+ dew->ny = (dew->h + 2 * dew->sampling - 2) / dew->sampling;
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpaExtendArraysToSize()
+ *
+ * \param[in] dewa
+ * \param[in] size new size of dewarpa array
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If necessary, reallocs main and cache dewarpa ptr arrays to %size.
+ * </pre>
+ */
+static l_int32
+dewarpaExtendArraysToSize(L_DEWARPA *dewa,
+ l_int32 size)
+{
+ 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;
+}
+
+
+/*!
+ * \brief dewarpaGetDewarp()
+ *
+ * \param[in] dewa populated with dewarp structs for pages
+ * \param[in] index into dewa: this is the pageno
+ * \return dew handle; still owned by dewa, or NULL on error
+ */
+L_DEWARP *
+dewarpaGetDewarp(L_DEWARPA *dewa,
+ l_int32 index)
+{
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpaSetCurvatures()
+ *
+ * \param[in] dewa
+ * \param[in] max_linecurv -1 for default
+ * \param[in] min_diff_linecurv -1 for default; 0 to accept all models
+ * \param[in] max_diff_linecurv -1 for default
+ * \param[in] max_edgecurv -1 for default; 0 to fit a line
+ * \param[in] max_diff_edgecurv -1 for default
+ * \param[in] max_edgeslope -1 for default
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Approximating the line by a quadratic, the coefficient
+ * of the quadratic term is the curvature, and distance
+ * units are in pixels (of course). Curvatures are very
+ * small, so we multiply by 10^6 and express the constraints
+ * on the model curvatures in micro-units. The slope parameter
+ * is multiplied by 10^3 and expressed in milli-units.
+ * (2) This sets five curvature thresholds and a slope threshold
+ * for dewarping to take place. Use -1 for default values.
+ * * max_linecurv: the maximum absolute value of the vertical
+ * disparity line curvatures.
+ * * min_diff_linecurv: the minimum absolute value of the
+ * largest difference in vertical disparity line curvatures.
+ * Use a value of 0 to accept all models.
+ * * max_diff_linecurv: the maximum absolute value of the largest
+ * difference in vertical disparity line curvatures.
+ * * max_edgecurv: the maximum absolute value of the left and right
+ * edge curvature for the horizontal disparity. Use a value of
+ * zero to fit a straight line (zero curvature).
+ * * max_diff_edgecurv: the maximum absolute value of the difference
+ * between left and right edge curvature for the horizontal
+ * disparity. This value is ignored if max_edgecurve = 0.
+ * * max_edgeslope: the maximum slope coefficient for left and
+ * right line edges.
+ * (3) An image with a line curvature less than about 0.00001
+ * has fairly straight textlines. This is 10 micro-units.
+ * (4) For example, if %max_linecurv == 100, this would prevent dewarping
+ * if any of the lines has a curvature exceeding 100 micro-units.
+ * A model having maximum line curvature larger than about 150
+ * micro-units should probably not be used.
+ * (5) A model having a left or right edge curvature larger than
+ * about 50 micro-units should probably not be used. Set the
+ * parameter max_edgecurv = 0 for a linear LSF.
+ * </pre>
+ */
+l_ok
+dewarpaSetCurvatures(L_DEWARPA *dewa,
+ l_int32 max_linecurv,
+ l_int32 min_diff_linecurv,
+ l_int32 max_diff_linecurv,
+ l_int32 max_edgecurv,
+ l_int32 max_diff_edgecurv,
+ l_int32 max_edgeslope)
+{
+ PROCNAME("dewarpaSetCurvatures");
+
+ if (!dewa)
+ return ERROR_INT("dewa not defined", procName, 1);
+
+ if (max_linecurv == -1)
+ dewa->max_linecurv = DefaultMaxLineCurv;
+ else
+ dewa->max_linecurv = L_ABS(max_linecurv);
+
+ if (min_diff_linecurv == -1)
+ dewa->min_diff_linecurv = DefaultMinDiffLineCurv;
+ else
+ dewa->min_diff_linecurv = L_ABS(min_diff_linecurv);
+
+ if (max_diff_linecurv == -1)
+ dewa->max_diff_linecurv = DefaultMaxDiffLineCurv;
+ else
+ dewa->max_diff_linecurv = L_ABS(max_diff_linecurv);
+
+ if (max_edgecurv == -1)
+ dewa->max_edgecurv = DefaultMaxEdgeCurv;
+ else
+ dewa->max_edgecurv = L_ABS(max_edgecurv);
+
+ if (max_diff_edgecurv == -1)
+ dewa->max_diff_edgecurv = DefaultMaxDiffEdgeCurv;
+ else
+ dewa->max_diff_edgecurv = L_ABS(max_diff_edgecurv);
+
+ if (max_edgeslope == -1)
+ dewa->max_edgeslope = DefaultMaxEdgeSlope;
+ else
+ dewa->max_edgeslope = L_ABS(max_edgeslope);
+
+ dewa->modelsready = 0; /* force validation */
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpaUseBothArrays()
+ *
+ * \param[in] dewa
+ * \param[in] useboth 0 for false, 1 for true
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This sets the useboth field. If set, this will attempt
+ * to apply both vertical and horizontal disparity arrays.
+ * Note that a model with only a vertical disparity array will
+ * always be valid.
+ * </pre>
+ */
+l_ok
+dewarpaUseBothArrays(L_DEWARPA *dewa,
+ l_int32 useboth)
+{
+ PROCNAME("dewarpaUseBothArrays");
+
+ if (!dewa)
+ return ERROR_INT("dewa not defined", procName, 1);
+
+ dewa->useboth = useboth;
+ dewa->modelsready = 0; /* force validation */
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpaSetCheckColumns()
+ *
+ * \param[in] dewa
+ * \param[in] check_columns 0 for false, 1 for true
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This sets the 'check_columns" field. If set, and if
+ * 'useboth' is set, this will count the number of text
+ * columns. If the number is larger than 1, this will
+ * prevent the application of horizontal disparity arrays
+ * if they exist.
+ * (2) The check_columns field is set to TRUE by default.
+ * For horizontal disparity correction to take place on a
+ * single column of text, you must have:
+ * - a valid horizontal disparity array
+ * - useboth = 1 (TRUE)
+ * If there are multiple columns, in addition you need
+ * - check_columns = 0 (FALSE)
+ *
+ * </pre>
+ */
+l_ok
+dewarpaSetCheckColumns(L_DEWARPA *dewa,
+ l_int32 check_columns)
+{
+ PROCNAME("dewarpaSetCheckColumns");
+
+ if (!dewa)
+ return ERROR_INT("dewa not defined", procName, 1);
+
+ dewa->check_columns = check_columns;
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpaSetMaxDistance()
+ *
+ * \param[in] dewa
+ * \param[in] maxdist for using ref models
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This sets the maxdist field.
+ * </pre>
+ */
+l_ok
+dewarpaSetMaxDistance(L_DEWARPA *dewa,
+ l_int32 maxdist)
+{
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpRead()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief dewarpReadStream()
+ *
+ * \param[in] fp file stream
+ * \return dew dewarp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The dewarp struct is stored in minimized format, with only
+ * subsampled disparity arrays.
+ * (2) The sampling and extra horizontal disparity parameters are
+ * stored here. During generation of the dewarp struct, they
+ * are passed in from the dewarpa. In readback, it is assumed
+ * that they are (a) the same for each page and (b) the same
+ * as the values used to create the dewarpa.
+ * </pre>
+ */
+L_DEWARP *
+dewarpReadStream(FILE *fp)
+{
+l_int32 version, sampling, redfactor, minlines, pageno, hasref, refpage;
+l_int32 w, h, nx, ny, vdispar, hdispar, nlines;
+l_int32 mincurv, maxcurv, leftslope, rightslope, leftcurv, rightcurv;
+L_DEWARP *dew;
+FPIX *fpixv, *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;
+}
+
+
+/*!
+ * \brief dewarpReadMem()
+ *
+ * \param[in] data serialization of dewarp
+ * \param[in] size of data in bytes
+ * \return dew dewarp, or NULL on error
+ */
+L_DEWARP *
+dewarpReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+L_DEWARP *dew;
+
+ PROCNAME("dewarpReadMem");
+
+ if (!data)
+ return (L_DEWARP *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (L_DEWARP *)ERROR_PTR("stream not opened", procName, NULL);
+
+ dew = dewarpReadStream(fp);
+ fclose(fp);
+ if (!dew) L_ERROR("dew not read\n", procName);
+ return dew;
+}
+
+
+/*!
+ * \brief dewarpWrite()
+ *
+ * \param[in] filename
+ * \param[in] dew
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+dewarpWrite(const char *filename,
+ L_DEWARP *dew)
+{
+l_int32 ret;
+FILE *fp;
+
+ 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);
+ ret = dewarpWriteStream(fp, dew);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("dew not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpWriteStream()
+ *
+ * \param[in] fp file stream opened for "wb"
+ * \param[in] dew
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This should not be written if there is no sampled
+ * vertical disparity array, which means that no model has
+ * been built for this page.
+ * </pre>
+ */
+l_ok
+dewarpWriteStream(FILE *fp,
+ L_DEWARP *dew)
+{
+l_int32 vdispar, hdispar;
+
+ 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;
+}
+
+
+/*!
+ * \brief dewarpWriteMem()
+ *
+ * \param[out] pdata data of serialized dewarp (not ascii)
+ * \param[out] psize size of returned data
+ * \param[in] dew
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a dewarp in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+dewarpWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ L_DEWARP *dew)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("dewarpWriteMem");
+
+ 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 (!dew)
+ return ERROR_INT("dew not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = dewarpWriteStream(fp, dew);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = dewarpWriteStream(fp, dew);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Dewarpa serialized I/O *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpaRead()
+ *
+ * \param[in] filename
+ * \return dewa, or NULL on error
+ */
+L_DEWARPA *
+dewarpaRead(const char *filename)
+{
+FILE *fp;
+L_DEWARPA *dewa;
+
+ 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;
+}
+
+
+/*!
+ * \brief dewarpaReadStream()
+ *
+ * \param[in] fp file stream
+ * \return dewa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The serialized dewarp contains a Numa that gives the
+ * (increasing) page number of the dewarp structs that are
+ * contained.
+ * (2) Reference pages are added in after readback.
+ * </pre>
+ */
+L_DEWARPA *
+dewarpaReadStream(FILE *fp)
+{
+l_int32 i, version, ndewarp, maxpage;
+l_int32 sampling, redfactor, minlines, maxdist, useboth;
+l_int32 max_linecurv, min_diff_linecurv, max_diff_linecurv;
+l_int32 max_edgeslope, max_edgecurv, max_diff_edgecurv;
+L_DEWARP *dew;
+L_DEWARPA *dewa;
+NUMA *namodels;
+
+ 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 (ndewarp < 1)
+ return (L_DEWARPA *)ERROR_PTR("pages not >= 1", procName, NULL);
+ if (ndewarp > MaxPtrArraySize)
+ return (L_DEWARPA *)ERROR_PTR("too many pages", 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);
+ dewarpaDestroy(&dewa);
+ return NULL;
+ }
+ dewarpaInsertDewarp(dewa, dew);
+ numaAddNumber(namodels, dew->pageno);
+ }
+
+ /* Validate the models and insert reference models */
+ dewarpaInsertRefModels(dewa, 0, 0);
+ return dewa;
+}
+
+
+/*!
+ * \brief dewarpaReadMem()
+ *
+ * \param[in] data serialization of dewarpa
+ * \param[in] size of data in bytes
+ * \return dewa dewarpa, or NULL on error
+ */
+L_DEWARPA *
+dewarpaReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+L_DEWARPA *dewa;
+
+ PROCNAME("dewarpaReadMem");
+
+ if (!data)
+ return (L_DEWARPA *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (L_DEWARPA *)ERROR_PTR("stream not opened", procName, NULL);
+
+ dewa = dewarpaReadStream(fp);
+ fclose(fp);
+ if (!dewa) L_ERROR("dewa not read\n", procName);
+ return dewa;
+}
+
+
+/*!
+ * \brief dewarpaWrite()
+ *
+ * \param[in] filename
+ * \param[in] dewa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+dewarpaWrite(const char *filename,
+ L_DEWARPA *dewa)
+{
+l_int32 ret;
+FILE *fp;
+
+ 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);
+ ret = dewarpaWriteStream(fp, dewa);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("dewa not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpaWriteStream()
+ *
+ * \param[in] fp file stream opened for "wb"
+ * \param[in] dewa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+dewarpaWriteStream(FILE *fp,
+ L_DEWARPA *dewa)
+{
+l_int32 ndewarp, i, pageno;
+
+ 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;
+}
+
+
+/*!
+ * \brief dewarpaWriteMem()
+ *
+ * \param[out] pdata data of serialized dewarpa (not ascii)
+ * \param[out] psize size of returned data
+ * \param[in] dewa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a dewarpa in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+dewarpaWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ L_DEWARPA *dewa)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("dewarpaWriteMem");
+
+ 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 (!dewa)
+ return ERROR_INT("dewa not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = dewarpaWriteStream(fp, dewa);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = dewarpaWriteStream(fp, dewa);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
diff --git a/leptonica/src/dewarp2.c b/leptonica/src/dewarp2.c
new file mode 100644
index 00000000..97458865
--- /dev/null
+++ b/leptonica/src/dewarp2.c
@@ -0,0 +1,2017 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file dewarp2.c
+ * <pre>
+ *
+ * Build the page disparity model
+ *
+ * Build basic 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 dewarpFilterLineEndPoints()
+ * static PTA *dewarpRemoveBadEndPoints()
+ * static l_int32 dewarpIsLineCoverageValid()
+ * static l_int32 dewarpLinearLSF()
+ * static l_int32 dewarpQuadraticLSF()
+ *
+ * Build disparity model for slope near binding
+ * l_int32 dewarpFindHorizSlopeDisparity()
+ *
+ * Build the line disparity model
+ * l_int32 dewarpBuildLineModel()
+ *
+ * Query model status
+ * l_int32 dewarpaModelStatus()
+ *
+ * Rendering helpers
+ * static l_int32 pixRenderMidYs()
+ * static l_int32 pixRenderHorizEndPoints
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 dewarpFilterLineEndPoints(L_DEWARP *dew, PTA *ptal1, PTA *ptar1,
+ PTA **pptal2, PTA **pptar2);
+static PTA *dewarpRemoveBadEndPoints(l_int32 w, PTA *ptas);
+static l_int32 dewarpIsLineCoverageValid(PTAA *ptaa2, l_int32 h,
+ l_int32 *pntop, l_int32 *pnbot,
+ l_int32 *pytop, l_int32 *pybot);
+static l_int32 dewarpLinearLSF(PTA *ptad, l_float32 *pa, l_float32 *pb,
+ l_float32 *pmederr);
+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 debugging */
+#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 for reducing horizontal disparity */
+static const l_float32 MinRatioLinesToHeight = 0.45f;
+static const l_int32 MinLinesForHoriz1 = 10; /* initially */
+static const l_int32 MinLinesForHoriz2 = 3; /* after, in each half */
+static const l_float32 AllowedWidthFract = 0.05f; /* no bigger */
+
+
+/*----------------------------------------------------------------------*
+ * Build basic page disparity model *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpBuildPageModel()
+ *
+ * \param[in] dew
+ * \param[in] debugfile use NULL to skip writing this
+ * \return 0 if OK, 1 if unable to build the model or on error
+ *
+ * <pre>
+ * 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 close to 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.
+ * </pre>
+ */
+l_ok
+dewarpBuildPageModel(L_DEWARP *dew,
+ const char *debugfile)
+{
+l_int32 linecount, ntop, nbot, ytop, ybot, 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);
+ pixWriteDebug("/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);
+ pixWriteDebug("/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.8f,
+ 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);
+ pixWriteDebug("/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 page. */
+ if (dewarpIsLineCoverageValid(ptaa2, pixGetHeight(pixs),
+ &ntop, &nbot, &ytop, &ybot) == FALSE) {
+ ptaaDestroy(&ptaa2);
+ L_WARNING("invalid line coverage: ntop = %d, nbot = %d;"
+ " spanning [%d ... %d] in height %d\n", procName,
+ ntop, nbot, ytop, ybot, 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.15f);
+ pixWriteDebug("/tmp/lept/dewmod/0060.png", pix1, IFF_PNG);
+ pixDisplay(pix1, 1000, 0);
+ pixDestroy(&pix1);
+ if (ret == 0) {
+ pix1 = fpixRenderContours(dew->fullhdispar, 3.0, 0.15f);
+ pixWriteDebug("/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);
+ lept_stderr("pdf file: %s\n", debugfile);
+ }
+
+ ptaaDestroy(&ptaa2);
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpFindVertDisparity()
+ *
+ * \param[in] dew
+ * \param[in] ptaa unsmoothed lines, not vertically ordered
+ * \param[in] rotflag 0 if using dew->pixs; 1 if rotated by 90 degrees cw
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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) Note that this builds a vertical disparity model (VDM), but
+ * does not check it against constraints for validity.
+ * Constraint checking is done after building the models,
+ * and before inserting reference models.
+ * (4) This sets the vsuccess flag to 1 on success.
+ * (5) Pix debug output goes to /tmp/dewvert/ for collection into
+ * a pdf. Non-pix debug output goes to /tmp.
+ * </pre>
+ */
+l_ok
+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, meddev, 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);
+
+ 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) {
+ lept_mkdir("lept/dewarp");
+ lept_mkdir("lept/dewdebug");
+ lept_mkdir("lept/dewmod");
+ 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);
+ pixWriteDebug("/tmp/lept/dewmod/0041.png", pix2, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ ptaDestroy(&pta);
+ 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. */
+ numaGetMedianDevFromMedian(nacurve0, &medval, &meddev);
+ L_INFO("\nPage %d\n", procName, dew->pageno);
+ L_INFO("Pass 1: Curvature: medval = %f, meddev = %f\n",
+ procName, medval, meddev);
+ 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 * meddev) /* 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) {
+ numaWriteDebug("/tmp/lept/dewdebug/midys.na", namidys);
+ numaWriteDebug("/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);
+ pixWriteDebug("/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) {
+ ptaaWriteDebug("/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) {
+ ptaaWriteDebug("/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) {
+ ptaaWriteDebug("/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");
+ lept_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;
+}
+
+
+/*!
+ * \brief dewarpFindHorizDisparity()
+ *
+ * \param[in] dew
+ * \param[in] ptaa unsmoothed lines, not vertically ordered
+ * \return 0 if OK, 1 if horizontal disparity array is not built, or on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This builds a horizontal disparity model (HDM), but
+ * does not check it against constraints for validity.
+ * Constraint checking is done at rendering time.
+ * (2) Horizontal disparity 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.
+ * (3) This sets the hsuccess flag to 1 on success.
+ * (4) Internally in ptal1, ptar1, ptal2, ptar2: x and y are reversed,
+ * so the 'y' value is horizontal distance across the image width.
+ * (5) Debug output goes to /tmp/lept/dewmod/ for collection into a pdf.
+ * </pre>
+ */
+l_ok
+dewarpFindHorizDisparity(L_DEWARP *dew,
+ PTAA *ptaa)
+{
+l_int32 i, j, h, nx, ny, sampling, ret, linear_edgefit;
+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 *ptal1, *ptar1; /* left/right end points of lines; initial */
+PTA *ptal2, *ptar2; /* left/right end points; after filtering */
+PTA *ptal3, *ptar3; /* left and right block, fitted, uniform spacing */
+PTA *pta, *ptat, *pta1, *pta2;
+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);
+
+ if (dew->debug) L_INFO("finding horizontal disparity\n", procName);
+
+ /* Get the endpoints of the lines, and sort from top to bottom */
+ h = pixGetHeight(dew->pixs);
+ ret = dewarpGetLineEndPoints(h, ptaa, &ptal1, &ptar1);
+ if (ret) {
+ L_INFO("Horiz disparity not built\n", procName);
+ return 1;
+ }
+ if (dew->debug) {
+ lept_mkdir("lept/dewdebug");
+ lept_mkdir("lept/dewarp");
+ ptaWriteDebug("/tmp/lept/dewdebug/endpts_left1.pta", ptal1, 1);
+ ptaWriteDebug("/tmp/lept/dewdebug/endpts_right1.pta", ptar1, 1);
+ }
+
+ /* Filter the points by x-location to prevent 2-column images
+ * from getting confused about left and right endpoints. We
+ * require valid left points to not be farther than
+ * 0.20 * (remaining distance to the right edge of the image)
+ * to the right of the leftmost endpoint, and similarly for
+ * the right endpoints. (Note: x and y are reversed in the pta.)
+ * Also require end points to be near the medians in the
+ * upper and lower halves. */
+ ret = dewarpFilterLineEndPoints(dew, ptal1, ptar1, &ptal2, &ptar2);
+ ptaDestroy(&ptal1);
+ ptaDestroy(&ptar1);
+ if (ret) {
+ L_INFO("Not enough filtered end points\n", procName);
+ return 1;
+ }
+
+ /* Do either a linear or a quadratic fit to the left and right
+ * endpoints of the longest lines. It is not necessary to use
+ * the noisy LSF fit function, because we've removed outlier
+ * end points by selecting the long lines.
+ * For the linear fit, each line is represented by 2 coefficients:
+ * x(y) = c1 * y + c0.
+ * For the quadratic fit, each line is represented by 3 coefficients:
+ * x(y) = c2 * y^2 + c1 * y + c0.
+ * Then using the coefficients, sample each fitted curve uniformly
+ * along the full height of the image. */
+ sampling = dew->sampling;
+ nx = dew->nx;
+ ny = dew->ny;
+ linear_edgefit = (dew->dewa->max_edgecurv == 0) ? TRUE : FALSE;
+
+ if (linear_edgefit) {
+ /* Fit the left side, using linear LSF on the set of long lines. */
+ dewarpLinearLSF(ptal2, &cl1, &cl0, &mederr);
+ dew->leftslope = lept_roundftoi(1000. * cl1); /* milli-units */
+ dew->leftcurv = 0; /* micro-units */
+ L_INFO("Left linear LSF median error = %5.2f\n", procName, mederr);
+ L_INFO("Left edge slope = %d\n", procName, dew->leftslope);
+ ptal3 = ptaCreate(ny);
+ for (i = 0; i < ny; i++) { /* uniformly sample in y */
+ y = i * sampling;
+ applyLinearFit(cl1, cl0, y, &x);
+ ptaAddPt(ptal3, x, y);
+ }
+
+ /* Do a linear LSF on the right side. */
+ dewarpLinearLSF(ptar2, &cr1, &cr0, &mederr);
+ dew->rightslope = lept_roundftoi(1000.0 * cr1); /* milli-units */
+ dew->rightcurv = 0; /* micro-units */
+ L_INFO("Right linear LSF median error = %5.2f\n", procName, mederr);
+ L_INFO("Right edge slope = %d\n", procName, dew->rightslope);
+ ptar3 = ptaCreate(ny);
+ for (i = 0; i < ny; i++) { /* uniformly sample in y */
+ y = i * sampling;
+ applyLinearFit(cr1, cr0, y, &x);
+ ptaAddPt(ptar3, x, y);
+ }
+ } else { /* quadratic edge fit */
+ /* Fit the left side, using quadratic LSF on the long lines. */
+ dewarpQuadraticLSF(ptal2, &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);
+ ptal3 = ptaCreate(ny);
+ for (i = 0; i < ny; i++) { /* uniformly sample in y */
+ y = i * sampling;
+ applyQuadraticFit(cl2, cl1, cl0, y, &x);
+ ptaAddPt(ptal3, x, y);
+ }
+
+ /* Do a quadratic LSF on the right side. */
+ dewarpQuadraticLSF(ptar2, &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);
+ ptar3 = ptaCreate(ny);
+ for (i = 0; i < ny; i++) { /* uniformly sample in y */
+ y = i * sampling;
+ applyQuadraticFit(cr2, cr1, cr0, y, &x);
+ ptaAddPt(ptar3, x, y);
+ }
+ }
+
+ if (dew->debug) {
+ PTA *ptalft, *ptarft;
+ h = pixGetHeight(dew->pixs);
+ pta1 = ptaCreate(h);
+ pta2 = ptaCreate(h);
+ if (linear_edgefit) {
+ for (i = 0; i < h; i++) {
+ applyLinearFit(cl1, cl0, i, &x);
+ ptaAddPt(pta1, x, i);
+ applyLinearFit(cr1, cr0, i, &x);
+ ptaAddPt(pta2, x, i);
+ }
+ } else { /* quadratic edge fit */
+ 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, ptal2, ptar2, 0xff000000);
+ pixDisplay(pix1, 600, 800);
+ pixWriteDebug("/tmp/lept/dewmod/0051.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+
+ pix1 = pixDisplayPta(NULL, dew->pixs, pta1);
+ pixDisplayPta(pix1, pix1, pta2);
+ ptalft = ptaTranspose(ptal3);
+ ptarft = ptaTranspose(ptar3);
+ pixRenderHorizEndPoints(pix1, ptalft, ptarft, 0x0000ff00);
+ pixDisplay(pix1, 800, 800);
+ pixWriteDebug("/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");
+ lept_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,
+ * ptal3 and ptar3. 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(ptal3, ny / 2, &refl, NULL);
+ ptaGetPt(ptar3, ny / 2, &refr, NULL);
+ nald = numaCreate(ny);
+ nard = numaCreate(ny);
+ for (i = 0; i < ny; i++) {
+ ptaGetPt(ptal3, i, &x, NULL);
+ numaAddNumber(nald, refl - x);
+ ptaGetPt(ptar3, 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(&ptal2);
+ ptaDestroy(&ptar2);
+ ptaDestroy(&ptal3);
+ ptaDestroy(&ptar3);
+ ptaaDestroy(&ptaah);
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpGetTextlineCenters()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] debugflag 1 for debug output
+ * \return ptaa of center values of textlines
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ 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) {
+ lept_mkdir("lept/dewmod");
+ pixWriteDebug("/tmp/lept/dewmod/0011.tif", pix1, IFF_TIFF_G4);
+ pixDisplayWithTitle(pix1, 0, 600, "pix1", 1);
+ pixWriteDebug("/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);
+ pixWriteDebug("/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);
+ pixWriteDebug("/tmp/lept/dewmod/0014.tif", pix2, IFF_PNG);
+ pixDisplayWithTitle(pix2, 0, 1200, "pix3", 1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ return ptaa;
+}
+
+
+/*!
+ * \brief dewarpGetMeanVerticals()
+ *
+ * \param[in] pixs 1 bpp, single c.c.
+ * \param[in] x,y location of UL corner of pixs, relative 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;
+}
+
+
+/*!
+ * \brief dewarpRemoveShortLines()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] ptaas input lines
+ * \param[in] fract minimum fraction of longest line to keep
+ * \param[in] 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);
+
+ 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;
+}
+
+
+/*!
+ * \brief dewarpGetLineEndPoints()
+ *
+ * \param[in] h height of pixs
+ * \param[in] ptaa lines
+ * \param[out] pptal left end points of each line
+ * \param[out] pptar right end points of each line
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * 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. We also require
+ * at least 10 lines of text.
+ * (2) We sort the lines from top to bottom (sort by x in the ptas).
+ * (3) For fitting the endpoints, x = f(y), we transpose x and y.
+ * Thus all these ptas have x and y swapped!
+ * </pre>
+ */
+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, *ptal1, *ptar1;
+
+ 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);
+
+ /* Are there at least 10 lines? */
+ n = ptaaGetCount(ptaa);
+ if (n < MinLinesForHoriz1) {
+ L_INFO("only %d lines; too few\n", procName, n);
+ return 1;
+ }
+
+ /* Extract the line end points, and transpose x and y values */
+ ptal1 = ptaCreate(n);
+ ptar1 = ptaCreate(n);
+ for (i = 0; i < n; i++) {
+ pta = ptaaGetPta(ptaa, i, L_CLONE);
+ ptaGetIPt(pta, 0, &x, &y);
+ ptaAddPt(ptal1, y, x); /* transpose */
+ npt = ptaGetCount(pta);
+ ptaGetIPt(pta, npt - 1, &x, &y);
+ ptaAddPt(ptar1, y, x); /* transpose */
+ ptaDestroy(&pta);
+ }
+
+ /* Use the min and max of the y value on the left side. */
+ ptaGetRange(ptal1, &miny, &maxy, NULL, NULL);
+ ratio = (maxy - miny) / (l_float32)h;
+ if (ratio < MinRatioLinesToHeight) {
+ L_INFO("ratio lines to height, %f, too small\n", procName, ratio);
+ ptaDestroy(&ptal1);
+ ptaDestroy(&ptar1);
+ return 1;
+ }
+
+ /* Sort from top to bottom */
+ *pptal = ptaSort(ptal1, L_SORT_BY_X, L_SORT_INCREASING, NULL);
+ *pptar = ptaSort(ptar1, L_SORT_BY_X, L_SORT_INCREASING, NULL);
+ ptaDestroy(&ptal1);
+ ptaDestroy(&ptar1);
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpFilterLineEndPoints()
+ *
+ * \param[in] dew
+ * \param[in] ptal input left end points of each line
+ * \param[in] ptar input right end points of each line
+ * \param[out] pptalf filtered left end points
+ * \param[out] pptarf filtered right end points
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) Avoid confusion with multiple columns by requiring that line
+ * end points be close enough to leftmost and rightmost end points.
+ * Must have at least 8 points on left and right after this step.
+ * (2) Apply second filtering step, find the median positions in
+ * top and bottom halves, and removing end points that are
+ * displaced too much from these in the x direction.
+ * Must have at least 6 points on left and right after this step.
+ * (3) Reminder: x and y in the pta are transposed; think x = f(y).
+ * </pre>
+ */
+static l_int32
+dewarpFilterLineEndPoints(L_DEWARP *dew,
+ PTA *ptal,
+ PTA *ptar,
+ PTA **pptalf,
+ PTA **pptarf)
+{
+l_int32 w, i, n;
+l_float32 ymin, ymax, xvall, xvalr, yvall, yvalr;
+PTA *ptal1, *ptar1, *ptal2, *ptar2;
+
+ PROCNAME("dewarpFilterLineEndPoints");
+ if (!ptal || !ptar)
+ return ERROR_INT("ptal or ptar not defined", procName, 1);
+ *pptalf = *pptarf = NULL;
+
+ /* First filter for lines near left and right margins */
+ w = pixGetWidth(dew->pixs);
+ ptaGetMinMax(ptal, NULL, &ymin, NULL, NULL);
+ ptaGetMinMax(ptar, NULL, NULL, NULL, &ymax);
+ n = ptaGetCount(ptal); /* ptar is the same size; at least 10 */
+ ptal1 = ptaCreate(n);
+ ptar1 = ptaCreate(n);
+ for (i = 0; i < n; i++) {
+ ptaGetPt(ptal, i, &xvall, &yvall);
+ ptaGetPt(ptar, i, &xvalr, &yvalr);
+ if (yvall < ymin + 0.20 * (w - ymin) &&
+ yvalr > 0.80 * ymax) {
+ ptaAddPt(ptal1, xvall, yvall);
+ ptaAddPt(ptar1, xvalr, yvalr);
+ }
+ }
+ if (dew->debug) {
+ ptaWriteDebug("/tmp/lept/dewdebug/endpts_left2.pta", ptal1, 1);
+ ptaWriteDebug("/tmp/lept/dewdebug/endpts_right2.pta", ptar1, 1);
+ }
+
+ n = L_MIN(ptaGetCount(ptal1), ptaGetCount(ptar1));
+ if (n < MinLinesForHoriz1 - 2) {
+ ptaDestroy(&ptal1);
+ ptaDestroy(&ptar1);
+ L_INFO("First filter: only %d endpoints; needed 8\n", procName, n);
+ return 1;
+ }
+
+ /* Remove outlier points */
+ ptal2 = dewarpRemoveBadEndPoints(w, ptal1);
+ ptar2 = dewarpRemoveBadEndPoints(w, ptar1);
+ ptaDestroy(&ptal1);
+ ptaDestroy(&ptar1);
+ if (!ptal2 || !ptar2) {
+ ptaDestroy(&ptal2);
+ ptaDestroy(&ptar2);
+ L_INFO("Second filter: too few endpoints left after outliers removed\n",
+ procName);
+ return 1;
+ }
+ if (dew->debug) {
+ ptaWriteDebug("/tmp/lept/dewdebug/endpts_left3.pta", ptal2, 1);
+ ptaWriteDebug("/tmp/lept/dewdebug/endpts_right3.pta", ptar2, 1);
+ }
+
+ *pptalf = ptal2;
+ *pptarf = ptar2;
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpRemoveBadEndPoints()
+ *
+ * \param[in] w width of input image
+ * \param[in] ptas left or right line end points
+ * \return ptad filtered left or right end points, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) The input set is sorted by line position (x value).
+ * Break into two (upper and lower); for each find the median
+ * horizontal (y value), and remove all points farther than
+ * a fraction of the image width from this. Make sure each
+ * part still has at least 3 points, and join the two sections
+ * before returning.
+ * (2) Reminder: x and y in the pta are transposed; think x = f(y).
+ * </pre>
+ */
+static PTA *
+dewarpRemoveBadEndPoints(l_int32 w,
+ PTA *ptas)
+{
+l_int32 i, n, nu, nd;
+l_float32 rval, xval, yval, delta;
+PTA *ptau1, *ptau2, *ptad1, *ptad2;
+
+ PROCNAME("dewarpRemoveBadEndPoints");
+
+ if (!ptas)
+ return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+
+ delta = AllowedWidthFract * w;
+ n = ptaGetCount(ptas); /* will be at least 8 */
+
+ /* Check the upper half */
+ ptau1 = ptaSelectRange(ptas, 0, n / 2);
+ ptaGetRankValue(ptau1, 0.5, NULL, L_SORT_BY_Y, &rval);
+ nu = ptaGetCount(ptau1);
+ ptau2 = ptaCreate(nu);
+ for (i = 0; i < nu; i++) {
+ ptaGetPt(ptau1, i, &xval, &yval); /* transposed */
+ if (L_ABS(rval - yval) <= delta)
+ ptaAddPt(ptau2, xval, yval);
+ }
+ ptaDestroy(&ptau1);
+ if (ptaGetCount(ptau2) < MinLinesForHoriz2) {
+ ptaDestroy(&ptau2);
+ L_INFO("Second filter: upper set is too small after outliers removed\n",
+ procName);
+ return NULL;
+ }
+
+ /* Check the lower half */
+ ptad1 = ptaSelectRange(ptas, n / 2 + 1, -1);
+ ptaGetRankValue(ptad1, 0.5, NULL, L_SORT_BY_Y, &rval);
+ nd = ptaGetCount(ptad1);
+ ptad2 = ptaCreate(nd);
+ for (i = 0; i < nd; i++) {
+ ptaGetPt(ptad1, i, &xval, &yval); /* transposed */
+ if (L_ABS(rval - yval) <= delta)
+ ptaAddPt(ptad2, xval, yval);
+ }
+ ptaDestroy(&ptad1);
+ if (ptaGetCount(ptad2) < MinLinesForHoriz2) {
+ ptaDestroy(&ptau2);
+ ptaDestroy(&ptad2);
+ L_INFO("Second filter: lower set is too small after outliers removed\n",
+ procName);
+ return NULL;
+ }
+
+ ptaJoin(ptau2, ptad2, 0, -1);
+ ptaDestroy(&ptad2);
+ return ptau2;
+}
+
+
+/*!
+ * \brief dewarpIsLineCoverageValid()
+ *
+ * \param[in] ptaa of validated lines
+ * \param[in] h height of pix
+ * \param[out] pntop number of lines in top half
+ * \param[out] pnbot number of lines in bottom half
+ * \param[out] pytop location of top line
+ * \param[out] pybot location of bottom line
+ * \return 1 if coverage is valid, 0 if not or on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) The criterion for valid coverage is:
+ * (a) there must be at least 4 lines in each half (top and bottom)
+ * of the image.
+ * (b) the coverage must be at least 50% of the image height
+ * </pre>
+ */
+static l_int32
+dewarpIsLineCoverageValid(PTAA *ptaa,
+ l_int32 h,
+ l_int32 *pntop,
+ l_int32 *pnbot,
+ l_int32 *pytop,
+ l_int32 *pybot)
+{
+l_int32 i, n, iy, both_halves, ntop, nbot, ytop, ybot, nmin;
+l_float32 y, fraction;
+NUMA *na;
+
+ 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 (!pntop || !pnbot)
+ return ERROR_INT("&ntop and &nbot not defined", procName, 0);
+ if (!pytop || !pybot)
+ return ERROR_INT("&ytop and &ybot not defined", procName, 0);
+
+ na = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ ptaaGetPt(ptaa, i, 0, NULL, &y);
+ numaAddNumber(na, y);
+ }
+ numaSort(na, na, L_SORT_INCREASING);
+ for (i = 0, ntop = 0; i < n; i++) {
+ numaGetIValue(na, i, &iy);
+ if (i == 0) ytop = iy;
+ if (i == n - 1) ybot = iy;
+ if (iy < 0.5 * h)
+ ntop++;
+ }
+ numaDestroy(&na);
+ nbot = n - ntop;
+ *pntop = ntop;
+ *pnbot = nbot;
+ *pytop = ytop;
+ *pybot = ybot;
+ nmin = 4; /* minimum number of lines required in each half */
+ both_halves = (ntop >= nmin) && (nbot >= nmin);
+ fraction = (l_float32)(ybot - ytop) / (l_float32)h;
+ if (both_halves && fraction > 0.50)
+ return 1;
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpLinearLSF()
+ *
+ * \param[in] ptad left or right end points of longest lines
+ * \param[out] pa coeff a of LSF: y = ax + b
+ * \param[out] pb coeff b of LSF: y = ax + b
+ * \param[out] pmederr [optional] median error
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This is used for finding the left or right sides of the text
+ * block, computed as a best-fit line. Only the longest lines
+ * are input, so there are no outlier line ends.
+ * (2) The ptas for the end points all have x and y swapped.
+ * </pre>
+ */
+static l_int32
+dewarpLinearLSF(PTA *ptad,
+ l_float32 *pa,
+ l_float32 *pb,
+ l_float32 *pmederr)
+{
+l_int32 i, n;
+l_float32 x, y, xp, c0, c1;
+NUMA *naerr;
+
+ PROCNAME("dewarpLinearLSF");
+
+ if (pmederr) *pmederr = 0.0;
+ if (!pa || !pb)
+ return ERROR_INT("not all ptrs are defined", procName, 1);
+ *pa = *pb = 0.0;
+ if (!ptad)
+ return ERROR_INT("ptad not defined", procName, 1);
+
+ /* Fit to the longest lines */
+ ptaGetLinearLSF(ptad, &c1, &c0, NULL);
+ *pa = c1;
+ *pb = 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);
+ applyLinearFit(c1, c0, y, &x);
+ numaAddNumber(naerr, L_ABS(x - xp));
+ }
+ numaGetMedian(naerr, pmederr);
+ numaDestroy(&naerr);
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpQuadraticLSF()
+ *
+ * \param[in] ptad left or right end points of longest lines
+ * \param[out] pa coeff a of LSF: y = ax^2 + bx + c
+ * \param[out] pb coeff b of LSF: y = ax^2 + bx + c
+ * \param[out] pc coeff c of LSF: y = ax^2 + bx + c
+ * \param[out] pmederr [optional] median error
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This is used for finding the left or right sides of the text
+ * block, computed as a best-fit quadratic curve. Only the
+ * longest lines are input, so there are no outlier line ends.
+ * (2) The ptas for the end points all have x and y swapped.
+ * </pre>
+ */
+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 disparity model for slope near binding *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpFindHorizSlopeDisparity()
+ *
+ * \param[in] dew
+ * \param[in] pixb 1 bpp, with vert and horiz disparity removed
+ * \param[in] fractthresh threshold fractional difference in density
+ * \param[in] parity 0 if even page, 1 if odd page
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) %fractthresh is a threshold on the fractional difference in stroke
+ * density between between left and right sides. Process this
+ * disparity only if the absolute value of the fractional
+ * difference equals or exceeds this threshold.
+ * (2) %parity indicates where the binding is: on the left for
+ * %parity == 0 and on the right for %parity == 1.
+ * (3) This takes a 1 bpp %pixb where both vertical and horizontal
+ * disparity have been applied, so the text lines are straight and,
+ * more importantly, the line end points are vertically aligned.
+ * It estimates the foreshortening of the characters on the
+ * binding side, and if significant, computes a one-dimensional
+ * horizontal disparity function to compensate.
+ * (4) The first attempt was to use the average width of the
+ * connected components (c.c.) in vertical slices. This does not work
+ * reliably, because the horizontal compression of the text is
+ * often accompanied by horizontal joining of c.c.
+ * (5) We use the density of vertical strokes, measured by first using
+ * a vertical opening, which improves the signal. The result
+ * is relatively insensitive to the size of the opening; we use
+ * a 10-pixel opening. The relative density is measured by
+ * finding the number of c.c. in a full height sliding window
+ * of width 50 pixels, and compute every 25 pixels. Similar results
+ * are obtained counting c.c. that either intersect the window
+ * or are fully contained within it.
+ * (6) Debug output goes to /tmp/lept/dewmod/ for collection into a pdf.
+ * </pre>
+ */
+l_ok
+dewarpFindHorizSlopeDisparity(L_DEWARP *dew,
+ PIX *pixb,
+ l_float32 fractthresh,
+ l_int32 parity)
+{
+l_int32 i, j, x, n1, n2, nb, ne, count, w, h, ival, prev;
+l_int32 istart, iend, first, last, x0, x1, nx, ny;
+l_float32 fract, delta, sum, aveval, fval, del, denom;
+l_float32 ca, cb, cc, cd, ce, y;
+BOX *box;
+BOXA *boxa1, *boxa2;
+GPLOT *gplot;
+NUMA *na1, *na2, *na3, *na4, *nasum;
+PIX *pix1;
+PTA *pta1;
+FPIX *fpix;
+
+ PROCNAME("dewarpFindHorizSlopeDisparity");
+
+ if (!dew)
+ return ERROR_INT("dew not defined", procName, 1);
+ if (!dew->vvalid || !dew->hvalid)
+ return ERROR_INT("invalid vert or horiz disparity model", procName, 1);
+ if (!pixb || pixGetDepth(pixb) != 1)
+ return ERROR_INT("pixb not defined or not 1 bpp", procName, 1);
+
+ if (dew->debug) L_INFO("finding slope horizontal disparity\n", procName);
+
+ /* Find the bounding boxes of the vertical strokes; remove noise */
+ pix1 = pixMorphSequence(pixb, "o1.10", 0);
+ pixDisplay(pix1, 100, 100);
+ boxa1 = pixConnCompBB(pix1, 4);
+ boxa2 = boxaSelectBySize(boxa1, 0, 5, L_SELECT_HEIGHT, L_SELECT_IF_GT,
+ NULL);
+ nb = boxaGetCount(boxa2);
+ lept_stderr("number of components: %d\n", nb);
+ boxaDestroy(&boxa1);
+
+ /* Estimate the horizontal density of vertical strokes */
+ na1 = numaCreate(0);
+ numaSetParameters(na1, 0, 25);
+ pixGetDimensions(pixb, &w, &h, NULL);
+ for (x = 0; x + 50 < w; x += 25) {
+ box = boxCreate(x, 0, 50, h);
+ boxaContainedInBoxCount(boxa2, box, &count);
+ numaAddNumber(na1, count);
+ boxDestroy(&box);
+ }
+ if (dew->debug) {
+ lept_mkdir("lept/dew");
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/dew/0091", NULL);
+ lept_mv("/tmp/lept/dew/0091.png", "lept/dewmod", NULL, NULL);
+ pixWriteDebug("/tmp/lept/dewmod/0090.png", pix1, IFF_PNG);
+ }
+ pixDestroy(&pix1);
+ boxaDestroy(&boxa2);
+
+ /* Find the left and right end local maxima; if the difference
+ * is small, quit. */
+ n1 = numaGetCount(na1);
+ prev = 0;
+ istart = 0;
+ first = 0;
+ for (i = 0; i < n1; i++) {
+ numaGetIValue(na1, i, &ival);
+ if (ival >= prev) {
+ prev = ival;
+ continue;
+ } else {
+ first = prev;
+ istart = i - 1;
+ break;
+ }
+ }
+ prev = 0;
+ last = 0;
+ iend = n1 - 1;
+ for (i = n1 - 1; i >= 0; i--) {
+ numaGetIValue(na1, i, &ival);
+ if (ival >= prev) {
+ prev = ival;
+ continue;
+ } else {
+ last = prev;
+ iend = i + 1;
+ break;
+ }
+ }
+ na2 = numaClipToInterval(na1, istart, iend);
+ numaDestroy(&na1);
+ n2 = numaGetCount(na2);
+ delta = (parity == 0) ? last - first : first - last;
+ denom = L_MAX(1.0, (l_float32)(L_MIN(first, last)));
+ fract = (l_float32)delta / denom;
+ if (dew->debug) {
+ L_INFO("Slope-disparity: first = %d, last = %d, fract = %7.3f\n",
+ procName, first, last, fract);
+ gplotSimple1(na2, GPLOT_PNG, "/tmp/lept/dew/0092", NULL);
+ lept_mv("/tmp/lept/dew/0092.png", "lept/dewmod", NULL, NULL);
+ }
+ if (fract < fractthresh) {
+ L_INFO("Small slope-disparity: first = %d, last = %d, fract = %7.3f\n",
+ procName, first, last, fract);
+ numaDestroy(&na2);
+ return 0;
+ }
+
+ /* Find the density far from the binding, and normalize to 1. */
+ ne = n2 - n2 % 2;
+ if (parity == 0)
+ numaGetSumOnInterval(na2, 0, ne / 2 - 1, &sum);
+ else /* parity == 1 */
+ numaGetSumOnInterval(na2, ne / 2, ne - 1, &sum);
+ denom = L_MAX(1.0, (l_float32)(ne / 2));
+ aveval = sum / denom;
+ na3 = numaMakeConstant(aveval, n2);
+ numaArithOp(na2, na2, na3, L_ARITH_DIVIDE);
+ numaDestroy(&na3);
+ if (dew->debug) {
+ L_INFO("Average background density: %5.1f\n", procName, aveval);
+ gplotSimple1(na2, GPLOT_PNG, "/tmp/lept/dew/0093", NULL);
+ lept_mv("/tmp/lept/dew/0093.png", "lept/dewmod", NULL, NULL);
+ }
+
+ /* Fit the normalized density curve to a quartic */
+ pta1 = numaConvertToPta1(na2);
+ ptaWriteStream(stderr, pta1, 0);
+/* ptaGetQuadraticLSF(pta1, NULL, NULL, NULL, &na3); */
+ ptaGetQuarticLSF(pta1, &ca, &cb, &cc, &cd, &ce, &na3);
+ ptaGetArrays(pta1, &na4, NULL);
+ if (dew->debug) {
+ gplot = gplotSimpleXY1(na4, na3, GPLOT_LINES, GPLOT_PNG,
+ "/tmp/lept/dew/0094", NULL);
+ gplotDestroy(&gplot);
+ lept_mv("/tmp/lept/dew/0094.png", "lept/dewmod", NULL, NULL);
+ }
+ ptaDestroy(&pta1);
+
+ /* Integrate from the high point down to 1 (or v.v) to get the
+ * disparity needed to make the density constant. */
+ nasum = numaMakeConstant(0, w); /* area under the curve above 1.0 */
+ if (parity == 0) {
+ for (i = n2 - 1; i >= 0; i--) {
+ numaGetFValue(na3, i, &fval);
+ if (fval < 1.0) break;
+ }
+ numaGetIValue(na4, i + 1, &x0);
+ numaGetIValue(na4, n2 - 1, &x1);
+ numaSetParameters(nasum, x0, 1);
+ sum = 0.0;
+ for (x = x0; x < x1; x++) {
+ applyQuarticFit(ca, cb, cc, cd, ce, (l_float32)x, &y);
+ sum += (y - 1.0);
+ numaReplaceNumber(nasum, x, sum);
+ }
+ for (x = x1; x < w; x++)
+ numaReplaceNumber(nasum, x, sum);
+ } else { /* parity == 1 */
+ for (i = 0; i < n2; i++) {
+ numaGetFValue(na3, i, &fval);
+ if (fval < 1.0) break;
+ }
+ numaGetIValue(na4, 0, &x0);
+ numaGetIValue(na4, i - 1, &x1);
+ numaSetParameters(nasum, x0, 1);
+ sum = 0.0;
+ for (x = x1; x >= x0; x--) {
+ applyQuarticFit(ca, cb, cc, cd, ce, (l_float32)x, &y);
+ sum += (y - 1.0);
+ numaReplaceNumber(nasum, x, sum);
+ }
+ for (x = x0; x >= 0; x--)
+ numaReplaceNumber(nasum, x, sum);
+ }
+
+ /* Save the result in a fpix at the specified subsampling */
+ nx = dew->nx;
+ ny = dew->ny;
+ fpix = fpixCreate(nx, ny);
+ del = (l_float32)w / (l_float32)nx;
+ for (i = 0; i < ny; i++) {
+ for (j = 0; j < nx; j++) {
+ x = del * j;
+ numaGetFValue(nasum, x, &fval);
+ fpixSetPixel(fpix, j, i, fval);
+ }
+ }
+ dew->sampydispar = fpix;
+ dew->ysuccess = 1;
+
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+ numaDestroy(&nasum);
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Build line disparity model *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpBuildLineModel()
+ *
+ * \param[in] dew
+ * \param[in] opensize size of opening to remove perpendicular lines
+ * \param[in] debugfile use NULL to skip writing this
+ * \return 0 if OK, 1 if unable to build the model or on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+ pixWriteDebug("/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);
+ pixWriteDebug(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);
+ pixWriteDebug(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.15f);
+ pixWriteDebug("/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.15f);
+ pixWriteDebug("/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);
+ lept_stderr("pdf file: %s\n", debugfile);
+ }
+
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Query model status *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpaModelStatus()
+ *
+ * \param[in] dewa
+ * \param[in] pageno
+ * \param[out] pvsuccess [optional] 1 on success
+ * \param[out] phsuccess [optional] 1 on success
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This tests if a model has been built, not if it is valid.
+ * </pre>
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixRenderMidYs()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] namidys y location of reference lines for vertical disparity
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixRenderHorizEndPoints()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] ptal left side line end points
+ * \param[in] ptar right side line end points
+ * \param[in] 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/leptonica/src/dewarp3.c b/leptonica/src/dewarp3.c
new file mode 100644
index 00000000..b807037f
--- /dev/null
+++ b/leptonica/src/dewarp3.c
@@ -0,0 +1,1016 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file dewarp3.c
+ * <pre>
+ *
+ * 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()
+ *
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpaApplyDisparity()
+ *
+ * \param[in] dewa
+ * \param[in] pageno of page model to be used; may be a ref model
+ * \param[in] pixs image to be modified; can be 1, 8 or 32 bpp
+ * \param[in] grayin gray value, from 0 to 255, for pixels brought in;
+ * use -1 to use pixels on the boundary of pixs
+ * \param[in] x, y origin for generation of disparity arrays
+ * \param[out] ppixd disparity corrected image
+ * \param[in] debugfile use NULL to skip writing this
+ * \return 0 if OK, 1 on error no models or ref models available
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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");
+ pixWriteDebug("/tmp/lept/dewapply/001.png", pixs, IFF_PNG);
+ pixWriteDebug("/tmp/lept/dewapply/002.png", pixv, IFF_PNG);
+ }
+
+ /* Optionally, correct for horizontal disparity */
+ if (dewa->useboth && dew->hsuccess && !dew->skip_horiz) {
+ 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);
+ pixWriteDebug("/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, 250, 1.0, 0, 0,
+ "Dewarp Apply Disparity", debugfile);
+ lept_stderr("pdf file: %s\n", debugfile);
+ }
+
+ /* Get rid of the large full res disparity arrays */
+ dewarpMinimize(dew);
+
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpaApplyInit()
+ *
+ * \param[in] dewa
+ * \param[in] pageno of page model to be used; may be a ref model
+ * \param[in] pixs image to be modified; can be 1, 8 or 32 bpp
+ * \param[in] x, y origin for generation of disparity arrays
+ * \param[out] pdew dewarp to be used for this page
+ * \param[in] debugfile use NULL to skip writing this
+ * \return 0 if OK, 1 on error no models or ref models available
+ *
+ * <pre>
+ * 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) If both the 'useboth' and 'check_columns' fields are true,
+ * this checks for multiple text columns and if found, sets
+ * the 'skip_horiz' field in the %dew for this page.
+ * </pre>
+ */
+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 ncols, debug;
+L_DEWARP *dew1, *dew2;
+PIX *pix1;
+
+ 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;
+
+ /* If check_columns is TRUE and useboth is TRUE, check for
+ * multiple columns. If there is more than one column, we
+ * only apply vertical disparity. */
+ if (dewa->useboth && dewa->check_columns) {
+ pix1 = pixConvertTo1(pixs, 140);
+ pixCountTextColumns(pix1, 0.3f, 0.5f, 0.1f, &ncols, NULL);
+ pixDestroy(&pix1);
+ if (ncols > 1) {
+ L_INFO("found %d columns; not correcting horiz disparity\n",
+ procName, ncols);
+ dew2->skip_horiz = TRUE;
+ } else {
+ dew2->skip_horiz = FALSE;
+ }
+ }
+
+ /* 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;
+}
+
+
+/*!
+ * \brief pixApplyVertDisparity()
+ *
+ * \param[in] dew
+ * \param[in] pixs 1, 8 or 32 bpp
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ lept_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;
+}
+
+
+/*!
+ * \brief pixApplyHorizDisparity()
+ *
+ * \param[in] dew
+ * \param[in] pixs 1, 8 or 32 bpp
+ * \param[in] 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.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ lept_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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpaApplyDisparityBoxa()
+ *
+ * \param[in] dewa
+ * \param[in] pageno of page model to be used; may be a ref model
+ * \param[in] pixs initial pix reference; for alignment and debugging
+ * \param[in] boxas boxa to be mapped
+ * \param[in] mapdir 1 if mapping forward from original to dewarped;
+ * 0 if backward
+ * \param[in] x, y origin for generation of disparity arrays with
+ * respect to the source region
+ * \param[out] pboxad disparity corrected boxa
+ * \param[in] debugfile use NULL to skip writing this
+ * \return 0 if OK, 1 on error no models or ref models available
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+ pixWriteDebug("/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);
+ pixWriteDebug("/tmp/lept/dewboxa/02.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ }
+
+ /* Optionally, correct for horizontal disparity */
+ if (dewa->useboth && dew->hsuccess && !dew->skip_horiz) {
+ 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);
+ pixWriteDebug("/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);
+ lept_stderr("Dewarp Apply Disparity Boxa pdf file: %s\n",
+ debugfile);
+ }
+
+ /* Get rid of the large full res disparity arrays */
+ dewarpMinimize(dew);
+
+ return 0;
+}
+
+
+/*!
+ * \brief boxaApplyDisparity()
+ *
+ * \param[in] dew
+ * \param[in] boxa
+ * \param[in] direction L_HORIZ or L_VERT
+ * \param[in] 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpMinimize()
+ *
+ * \param[in] dew
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dewarpPopulateFullRes()
+ *
+ * \param[in] dew
+ * \param[in] pix [optional], to give size of actual image
+ * \param[in] x, y origin for generation of disparity arrays
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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(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 == 1, but
+ * not if required to skip running horizontal disparity. */
+ if (!dew->fullhdispar && dew->samphdispar && !dew->skip_horiz) {
+ fpixt1 = fpixCopy(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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief fpixSampledDisparity()
+ *
+ * \param[in] fpixs full resolution disparity model
+ * \param[in] sampling sampling factor
+ * \return fpixd sampled disparity model, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+static const l_float32 DefaultSlopeFactor = 0.1; /* just a guess; fix it */
+
+/*!
+ * \brief fpixExtraHorizDisparity()
+ *
+ * \param[in] fpixv vertical disparity model
+ * \param[in] factor conversion factor for vertical disparity slope;
+ * use 0 for default
+ * \param[out] pxwid extra width to be added to dewarped pix
+ * \return fpixh, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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 = DefaultSlopeFactor;
+
+ /* 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/leptonica/src/dewarp4.c b/leptonica/src/dewarp4.c
new file mode 100644
index 00000000..4471f6b5
--- /dev/null
+++ b/leptonica/src/dewarp4.c
@@ -0,0 +1,1179 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file dewarp4.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 debugging */
+#endif /* !NO_CONSOLE_IO */
+
+ /* Special parameter value */
+static const l_int32 GrayInValue = 200;
+
+/*----------------------------------------------------------------------*
+ * Top-level single page dewarper *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpSinglePage()
+ *
+ * \param[in] pixs with text, any depth
+ * \param[in] thresh for global thresh to 1 bpp; ignore otherwise
+ * \param[in] adaptive 1 for adaptive thresh; 0 for global threshold
+ * \param[in] useboth 1 for both horiz and vert; 0 for vertical only
+ * \param[in] check_columns 1 to skip horizontal if multiple columns;
+ * 0 otherwise; default is to skip
+ * \param[out] ppixd dewarped result
+ * \param[out] pdewa [optional] dewa with single page; NULL to skip
+ * \param[in] debug 1 for debugging output, 0 otherwise
+ * \return 0 if OK, 1 on error list of page numbers, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+dewarpSinglePage(PIX *pixs,
+ l_int32 thresh,
+ l_int32 adaptive,
+ l_int32 useboth,
+ l_int32 check_columns,
+ 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, useboth,
+ check_columns, &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;
+}
+
+
+/*!
+ * \brief dewarpSinglePageInit()
+ *
+ * \param[in] pixs with text, any depth
+ * \param[in] thresh for global thresh to 1 bpp; ignore otherwise
+ * \param[in] adaptive 1 for adaptive thresh; 0 for global threshold
+ * \param[in] useboth 1 for both horiz and vert; 0 for vertical only
+ * \param[in] check_columns 1 to skip horizontal if multiple columns;
+ * 0 otherwise; default is to skip
+ * \param[out] ppixb 1 bpp debug image
+ * \param[out] pdewa initialized dewa
+ * \return 0 if OK, 1 on error list of page numbers, or NULL on error
+ *
+ * <pre>
+ * 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, 1, &pixb, &dewa);
+ * dewarpaSetCurvatures(dewa, 250, -1, -1, 80, 70, 150);
+ * dewarpSinglePageRun(pixs, pixb, dewa, &pixd, 0);
+ * dewarpaDestroy(&dewa);
+ * pixDestroy(&pixb);
+ * </pre>
+ */
+l_ok
+dewarpSinglePageInit(PIX *pixs,
+ l_int32 thresh,
+ l_int32 adaptive,
+ l_int32 useboth,
+ l_int32 check_columns,
+ PIX **ppixb,
+ L_DEWARPA **pdewa)
+{
+PIX *pix1, *pix2;
+
+ 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);
+
+ /* Generate a binary image, if necessary */
+ if (pixGetDepth(pixs) > 1) {
+ if ((pix1 = pixConvertTo8(pixs, 0)) == NULL)
+ return ERROR_INT("pix1 not made", procName, 1);
+ if (adaptive)
+ pix2 = pixAdaptThresholdToBinary(pix1, NULL, 1.0);
+ else
+ pix2 = pixThresholdToBinary(pix1, thresh);
+ pixDestroy(&pix1);
+ if (!pix2)
+ return ERROR_INT("pix2 not made", procName, 1);
+ *ppixb = pix2;
+ } else {
+ *ppixb = pixClone(pixs);
+ }
+
+ *pdewa = dewarpaCreate(1, 0, 1, 0, -1);
+ dewarpaUseBothArrays(*pdewa, useboth);
+ dewarpaSetCheckColumns(*pdewa, check_columns);
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpSinglePageRun()
+ *
+ * \param[in] pixs any depth
+ * \param[in] pixb 1 bpp
+ * \param[in] dewa initialized
+ * \param[out] ppixd dewarped result
+ * \param[in] debug 1 for debugging output, 0 otherwise
+ * \return 0 if OK, 1 on error list of page numbers, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Dewarps pixs and returns the result in &pixd.
+ * (2) The 1 bpp version %pixb and %dewa are conveniently generated by
+ * dewarpSinglePageInit().
+ * (3) Non-default model parameters must be set before calling this.
+ * (4) If a model cannot be built, this returns a copy of pixs in &pixd.
+ * </pre>
+ */
+l_ok
+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("pixb not defined", procName, 1);
+ if (!dewa)
+ return ERROR_INT("dewa not defined", procName, 1);
+
+ if (debug)
+ lept_mkdir("lept/dewarp");
+
+ /* Generate the page model */
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpaListPages()
+ *
+ * \param[in] dewa populated with dewarp structs for pages
+ * \return 0 if OK, 1 on error list of page numbers, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dewarpaSetValidModels()
+ *
+ * \param[in] dewa
+ * \param[in] notests
+ * \param[in] debug 1 to output information on invalid page models
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dewarpaInsertRefModels()
+ *
+ * \param[in] dewa
+ * \param[in] notests if 1, ignore curvature constraints on model
+ * \param[in] debug 1 to output information on invalid page models
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 dewarpaRestoreModels()
+ * to bring real models from the cache back to the primary array.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dewarpaStripRefModels()
+ *
+ * \param[in] dewa populated with dewarp structs for pages
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dewarpaRestoreModels()
+ *
+ * \param[in] dewa populated with dewarp structs for pages
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This puts all real models (and only real models) in the
+ * primary dewarpa 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief dewarpaInfo()
+ *
+ * \param[in] fp
+ * \param[in] dewa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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);
+ lept_stderr("Total number of pages with a dew = %d\n", n);
+ lept_stderr("Number of pages without any models = %d\n", nnone);
+ lept_stderr("Number of pages with a vert model = %d\n", nvsuccess);
+ lept_stderr("Number of pages with a valid vert model = %d\n", nvvalid);
+ lept_stderr("Number of pages with both models = %d\n", nhsuccess);
+ lept_stderr("Number of pages with both models valid = %d\n", nhvalid);
+ lept_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;
+ lept_stderr("Page: %d\n", dew->pageno);
+ lept_stderr(" hasref = %d, refpage = %d\n",
+ dew->hasref, dew->refpage);
+ lept_stderr(" nlines = %d\n", dew->nlines);
+ lept_stderr(" w = %d, h = %d, nx = %d, ny = %d\n",
+ dew->w, dew->h, dew->nx, dew->ny);
+ if (dew->sampvdispar)
+ lept_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)
+ lept_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;
+}
+
+
+/*!
+ * \brief dewarpaModelStats()
+ *
+ * \param[in] dewa
+ * \param[out] pnnone [optional] number without any model
+ * \param[out] pnvsuccess [optional] number with a vert model
+ * \param[out] pnvvalid [optional] number with a valid vert model
+ * \param[out] pnhsuccess [optional] number with both models
+ * \param[out] pnhvalid [optional] number with both models valid
+ * \param[out] pnref [optional] number with a reference model
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dewarpaTestForValidModel()
+ *
+ * \param[in] dewa
+ * \param[in] dew
+ * \param[in] notests
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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
+ lept_stderr(" max line curv = %d, max allowed = %d\n",
+ maxcurv, dewa->max_linecurv);
+ lept_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
+ lept_stderr(" left edge slope = %d, max allowed = %d\n",
+ dew->leftslope, dewa->max_edgeslope);
+ lept_stderr(" right edge slope = %d, max allowed = %d\n",
+ dew->rightslope, dewa->max_edgeslope);
+ lept_stderr(" left edge curv = %d, max allowed = %d\n",
+ dew->leftcurv, dewa->max_edgecurv);
+ lept_stderr(" right edge curv = %d, max allowed = %d\n",
+ dew->rightcurv, dewa->max_edgecurv);
+ lept_stderr(" diff edge curv = %d, max allowed = %d\n",
+ diffedge, dewa->max_diff_edgecurv);
+#endif /* DEBUG_INVALID_MODELS */
+ }
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpaShowArrays()
+ *
+ * \param[in] dewa
+ * \param[in] scalefact on contour images; typ. 0.5
+ * \param[in] first first page model to render
+ * \param[in] last last page model to render; use 0 to go to end
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Generates a pdf of contour plots of the disparity arrays.
+ * (2) This only shows actual models; not ref models
+ * </pre>
+ */
+l_ok
+dewarpaShowArrays(L_DEWARPA *dewa,
+ l_float32 scalefact,
+ l_int32 first,
+ l_int32 last)
+{
+char buf[256];
+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);
+
+ lept_stderr("Generating contour plots\n");
+ for (i = first; i <= last; i++) {
+ if (i && ((i % 10) == 0))
+ lept_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.15f);
+ pixvs = pixScaleBySampling(pixv, scalefact, scalefact);
+ pixDestroy(&pixv);
+ if (shd) {
+ pixh = fpixRenderContours(dew->fullhdispar, 3.0, 0.15f);
+ 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), "/tmp/lept/dewarp1/arrays_%04d.png", i);
+ pixWriteDebug(buf, pixd, IFF_PNG);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixt);
+ pixDestroy(&pixd);
+ }
+ bmfDestroy(&bmf);
+ lept_stderr("\n");
+
+ lept_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");
+ lept_stderr("Output written to: /tmp/lept/disparity_arrays.pdf\n");
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpDebug()
+ *
+ * \param[in] dew
+ * \param[in] subdirs one or more subdirectories of /tmp; e.g., "dew1"
+ * \param[in] index to help label output images; e.g., the page number
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Prints dewarp fields and generates disparity array contour images.
+ * The contour images are written to file:
+ * /tmp/[subdirs]/pixv_[index].png
+ * </pre>
+ */
+l_ok
+dewarpDebug(L_DEWARP *dew,
+ const char *subdirs,
+ l_int32 index)
+{
+char fname[256];
+char *outdir;
+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);
+
+ lept_stderr("pageno = %d, hasref = %d, refpage = %d\n",
+ dew->pageno, dew->hasref, dew->refpage);
+ lept_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;
+ lept_stderr("sampv = %d, samph = %d\n", svd, shd);
+ lept_stderr("w = %d, h = %d\n", dew->w, dew->h);
+ lept_stderr("nx = %d, ny = %d\n", dew->nx, dew->ny);
+ lept_stderr("nlines = %d\n", dew->nlines);
+ if (svd) {
+ lept_stderr("(min,max,abs-diff) line curvature = (%d,%d,%d)\n",
+ dew->mincurv, dew->maxcurv, dew->maxcurv - dew->mincurv);
+ }
+ if (shd) {
+ lept_stderr("(left edge slope = %d, right edge slope = %d\n",
+ dew->leftslope, dew->rightslope);
+ lept_stderr("(left,right,abs-diff) edge curvature = "
+ "(%d,%d,%d)\n", dew->leftcurv, dew->rightcurv,
+ L_ABS(dew->leftcurv - dew->rightcurv));
+ }
+ }
+ if (!svd && !shd) {
+ lept_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.15f);
+ snprintf(fname, sizeof(fname), "%s/pixv_%d.png", outdir, index);
+ pixWriteDebug(fname, pixv, IFF_PNG);
+ pixDestroy(&pixv);
+ }
+ if (shd) {
+ pixh = fpixRenderContours(dew->fullhdispar, 3.0, 0.15f);
+ snprintf(fname, sizeof(fname), "%s/pixh_%d.png", outdir, index);
+ pixWriteDebug(fname, pixh, IFF_PNG);
+ pixDestroy(&pixh);
+ }
+ LEPT_FREE(outdir);
+ return 0;
+}
+
+
+/*!
+ * \brief dewarpShowResults()
+ *
+ * \param[in] dewa
+ * \param[in] sa of indexed input images
+ * \param[in] boxa crop boxes for input images; can be null
+ * \param[in] firstpage
+ * \param[in] lastpage
+ * \param[in] pdfout filename
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+dewarpShowResults(L_DEWARPA *dewa,
+ SARRAY *sa,
+ BOXA *boxa,
+ l_int32 firstpage,
+ l_int32 lastpage,
+ const char *pdfout)
+{
+char bufstr[256];
+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);
+
+ lept_stderr("Dewarping and generating s/by/s view\n");
+ for (i = firstpage; i <= lastpage; i++) {
+ if (i && (i % 10 == 0)) lept_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,
+ GrayInValue, 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);
+ pixWriteDebug(bufstr, pixt2, IFF_JFIF_JPEG);
+ pixaDestroy(&pixa);
+ pixDestroy(&pixs);
+ pixDestroy(&pixt1);
+ pixDestroy(&pixt2);
+ }
+ lept_stderr("\n");
+
+ lept_stderr("Generating pdf of result\n");
+ convertFilesToPdf("/tmp/lept/dewarp_pdfout", NULL, 100, 1.0, L_JPEG_ENCODE,
+ 0, "Dewarp sequence", pdfout);
+ lept_stderr("Output written to: %s\n", pdfout);
+ bmfDestroy(&bmf);
+ return 0;
+}
diff --git a/leptonica/src/dnabasic.c b/leptonica/src/dnabasic.c
new file mode 100644
index 00000000..258021ed
--- /dev/null
+++ b/leptonica/src/dnabasic.c
@@ -0,0 +1,1728 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file dnabasic.c
+ * <pre>
+ *
+ * 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()
+ *
+ * (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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+ /* Bounds on initial array size */
+static const l_uint32 MaxDoubleArraySize = 100000000; /* for dna */
+static const l_uint32 MaxPtrArraySize = 1000000; /* for dnaa */
+static const l_int32 InitialArraySize = 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. *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaCreate()
+ *
+ * \param[in] n 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 > MaxDoubleArraySize)
+ n = InitialArraySize;
+
+ da = (L_DNA *)LEPT_CALLOC(1, sizeof(L_DNA));
+ if ((da->array = (l_float64 *)LEPT_CALLOC(n, sizeof(l_float64))) == NULL) {
+ l_dnaDestroy(&da);
+ 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;
+}
+
+
+/*!
+ * \brief l_dnaCreateFromIArray()
+ *
+ * \param[in] iarray integer array
+ * \param[in] size of the array
+ * \return da, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_dnaCreateFromDArray()
+ *
+ * \param[in] darray float
+ * \param[in] size of the array
+ * \param[in] copyflag L_INSERT or L_COPY
+ * \return da, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_dnaMakeSequence()
+ *
+ * \param[in] startval
+ * \param[in] increment
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief l_dnaDestroy()
+ *
+ * \param[in,out] pda will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Decrements the ref count and, if 0, destroys the l_dna.
+ * (2) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_dnaCopy()
+ *
+ * \param[in] da
+ * \return copy of da, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This removes unused ptrs above da->n.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_dnaClone()
+ *
+ * \param[in] da
+ * \return ptr to same da, 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;
+}
+
+
+/*!
+ * \brief l_dnaEmpty()
+ *
+ * \param[in] da
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaAddNumber()
+ *
+ * \param[in] da
+ * \param[in] val float or int to be added; stored as a float
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ if (l_dnaExtendArray(da))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ da->array[n] = val;
+ da->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief l_dnaExtendArray()
+ *
+ * \param[in] da
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The max number of doubles is 100M.
+ * </pre>
+ */
+static l_int32
+l_dnaExtendArray(L_DNA *da)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("l_dnaExtendArray");
+
+ if (!da)
+ return ERROR_INT("da not defined", procName, 1);
+ if (da->nalloc > MaxDoubleArraySize) /* belt & suspenders */
+ return ERROR_INT("da has too many ptrs", procName, 1);
+ oldsize = da->nalloc * sizeof(l_float64);
+ newsize = 2 * oldsize;
+ if (newsize > 8 * MaxDoubleArraySize)
+ return ERROR_INT("newsize > 800 MB; too large", procName, 1);
+
+ if ((da->array = (l_float64 *)reallocNew((void **)&da->array,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+
+ da->nalloc *= 2;
+ return 0;
+}
+
+
+/*!
+ * \brief l_dnaInsertNumber()
+ *
+ * \param[in] da
+ * \param[in] index location in da to insert new value
+ * \param[in] val float64 or integer to be added
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ *
+ * </pre>
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n);
+ return 1;
+ }
+
+ if (n >= da->nalloc) {
+ if (l_dnaExtendArray(da))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ for (i = n; i > index; i--)
+ da->array[i] = da->array[i - 1];
+ da->array[index] = val;
+ da->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief l_dnaRemoveNumber()
+ *
+ * \param[in] da
+ * \param[in] index element to be removed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n - 1);
+ return 1;
+ }
+
+ for (i = index + 1; i < n; i++)
+ da->array[i - 1] = da->array[i];
+ da->n--;
+ return 0;
+}
+
+
+/*!
+ * \brief l_dnaReplaceNumber()
+ *
+ * \param[in] da
+ * \param[in] index element to be replaced
+ * \param[in] val new value to replace old one
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n - 1);
+ return 1;
+ }
+
+ da->array[index] = val;
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Dna accessors *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief l_dnaSetCount()
+ *
+ * \param[in] da
+ * \param[in] newcount
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaGetDValue()
+ *
+ * \param[in] da
+ * \param[in] index into l_dna
+ * \param[out] pval double value; 0.0 on error
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Caller may need to check the function return value to
+ * decide if a 0.0 in the returned ival is valid.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaGetIValue()
+ *
+ * \param[in] da
+ * \param[in] index into l_dna
+ * \param[out] pival integer value; 0 on error
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Caller may need to check the function return value to
+ * decide if a 0 in the returned ival is valid.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaSetValue()
+ *
+ * \param[in] da
+ * \param[in] index to element to be set
+ * \param[in] val to set element
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaShiftValue()
+ *
+ * \param[in] da
+ * \param[in] index to element to change relative to the current value
+ * \param[in] diff increment if diff > 0 or decrement if diff < 0
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaGetIArray()
+ *
+ * \param[in] da
+ * \return a copy of the bare internal array, integerized
+ * by rounding, or NULL on error
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_dnaGetDArray()
+ *
+ * \param[in] da
+ * \param[in] copyflag L_NOCOPY or L_COPY
+ * \return either the bare internal array or a copy of it, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_dnaGetRefCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief l_dnaChangeRefCount()
+ *
+ * \param[in] da
+ * \param[in] delta change to be applied
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaGetParameters()
+ *
+ * \param[in] da
+ * \param[out] pstartx [optional] startx
+ * \param[out] pdelx [optional] delx
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaSetParameters()
+ *
+ * \param[in] da
+ * \param[in] startx x value corresponding to da[0]
+ * \param[in] 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_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaCopyParameters()
+ *
+ * \param[in] dad destination DNuma
+ * \param[in] das source DNuma
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaRead()
+ *
+ * \param[in] 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);
+ da = l_dnaReadStream(fp);
+ fclose(fp);
+ if (!da)
+ return (L_DNA *)ERROR_PTR("da not read", procName, NULL);
+ return da;
+}
+
+
+/*!
+ * \brief l_dnaReadStream()
+ *
+ * \param[in] fp file stream
+ * \return da, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) fscanf takes %lf to read a double; fprintf takes %f to write it.
+ * (2) It is OK for the dna to be empty.
+ * </pre>
+ */
+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 (n < 0)
+ return (L_DNA *)ERROR_PTR("num doubles < 0", procName, NULL);
+ if (n > MaxDoubleArraySize)
+ return (L_DNA *)ERROR_PTR("too many doubles", procName, NULL);
+ if (n == 0) L_INFO("the dna is empty\n", procName);
+
+ 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) {
+ l_dnaDestroy(&da);
+ 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;
+}
+
+
+/*!
+ * \brief l_dnaWrite()
+ *
+ * \param[in] filename
+ * \param[in] da
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+l_dnaWrite(const char *filename,
+ L_DNA *da)
+{
+l_int32 ret;
+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);
+ ret = l_dnaWriteStream(fp, da);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("da not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief l_dnaWriteStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] da
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaaCreate()
+ *
+ * \param[in] n 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 > MaxPtrArraySize)
+ n = InitialArraySize;
+
+ daa = (L_DNAA *)LEPT_CALLOC(1, sizeof(L_DNAA));
+ if ((daa->dna = (L_DNA **)LEPT_CALLOC(n, sizeof(L_DNA *))) == NULL) {
+ l_dnaaDestroy(&daa);
+ return (L_DNAA *)ERROR_PTR("l_dna ptr array not made", procName, NULL);
+ }
+ daa->nalloc = n;
+ daa->n = 0;
+ return daa;
+}
+
+
+/*!
+ * \brief l_dnaaCreateFull()
+ *
+ * \param[in] nptr size of dna ptr array to be alloc'd
+ * \param[in] n size of individual dna arrays to be alloc'd 0 for default
+ * \return daa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_dnaaTruncate()
+ *
+ * \param[in] daa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaaDestroy()
+ *
+ * \param[in,out] pdaa will be set to null before returning
+ * \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;
+}
+
+
+/*--------------------------------------------------------------------------*
+ * Add Dna to Dnaa *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaaAddDna()
+ *
+ * \param[in] daa
+ * \param[in] da to be added
+ * \param[in] copyflag L_INSERT, L_COPY, L_CLONE
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ if (l_dnaaExtendArray(daa)) {
+ if (copyflag != L_INSERT)
+ l_dnaDestroy(&dac);
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ }
+ daa->dna[n] = dac;
+ daa->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief l_dnaaExtendArray()
+ *
+ * \param[in] daa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Doubles the number of dna ptrs.
+ * (2) The max size of the dna array is 1M ptrs.
+ * </pre>
+ */
+static l_int32
+l_dnaaExtendArray(L_DNAA *daa)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("l_dnaaExtendArray");
+
+ if (!daa)
+ return ERROR_INT("daa not defined", procName, 1);
+ if (daa->nalloc > MaxPtrArraySize) /* belt & suspenders */
+ return ERROR_INT("daa has too many ptrs", procName, 1);
+ oldsize = daa->nalloc * sizeof(L_DNA *);
+ newsize = 2 * oldsize;
+ if (newsize > 8 * MaxPtrArraySize)
+ return ERROR_INT("newsize > 8 MB; too large", procName, 1);
+
+ if ((daa->dna = (L_DNA **)reallocNew((void **)&daa->dna,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+
+ daa->nalloc *= 2;
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * DNumaa accessors *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaaGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief l_dnaaGetDnaCount()
+ *
+ * \param[in] daa
+ * \param[in] 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]);
+}
+
+
+/*!
+ * \brief l_dnaaGetNumberCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief l_dnaaGetDna()
+ *
+ * \param[in] daa
+ * \param[in] index to the index-th l_dna
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief l_dnaaReplaceDna()
+ *
+ * \param[in] daa
+ * \param[in] index to the index-th l_dna
+ * \param[in] da insert and replace any existing one
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Any existing l_dna is destroyed, and the input one
+ * is inserted in its place.
+ * (2) If %index is invalid, return 1 (error)
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaaGetValue()
+ *
+ * \param[in] daa
+ * \param[in] i index of l_dna within l_dnaa
+ * \param[in] j index into l_dna
+ * \param[out] pval double value
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaaAddNumber()
+ *
+ * \param[in] daa
+ * \param[in] index of l_dna within l_dnaa
+ * \param[in] val number to be added; stored as a double
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Adds to an existing l_dna only.
+ * </pre>
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaaRead()
+ *
+ * \param[in] 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);
+ daa = l_dnaaReadStream(fp);
+ fclose(fp);
+ if (!daa)
+ return (L_DNAA *)ERROR_PTR("daa not read", procName, NULL);
+ return daa;
+}
+
+
+/*!
+ * \brief l_dnaaReadStream()
+ *
+ * \param[in] fp file stream
+ * \return daa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is OK for the dnaa to be empty.
+ * </pre>
+ */
+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 (n < 0)
+ return (L_DNAA *)ERROR_PTR("num l_dna <= 0", procName, NULL);
+ if (n > MaxPtrArraySize)
+ return (L_DNAA *)ERROR_PTR("too many l_dna", procName, NULL);
+ if (n == 0) L_INFO("the dnaa is empty\n", procName);
+
+ 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) {
+ l_dnaaDestroy(&daa);
+ return (L_DNAA *)ERROR_PTR("invalid l_dna header", procName, NULL);
+ }
+ if ((da = l_dnaReadStream(fp)) == NULL) {
+ l_dnaaDestroy(&daa);
+ return (L_DNAA *)ERROR_PTR("da not made", procName, NULL);
+ }
+ l_dnaaAddDna(daa, da, L_INSERT);
+ }
+
+ return daa;
+}
+
+
+/*!
+ * \brief l_dnaaWrite()
+ *
+ * \param[in] filename
+ * \param[in] daa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+l_dnaaWrite(const char *filename,
+ L_DNAA *daa)
+{
+l_int32 ret;
+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);
+ ret = l_dnaaWriteStream(fp, daa);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("daa not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief l_dnaaWriteStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] daa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
diff --git a/leptonica/src/dnafunc1.c b/leptonica/src/dnafunc1.c
new file mode 100644
index 00000000..01bde1a8
--- /dev/null
+++ b/leptonica/src/dnafunc1.c
@@ -0,0 +1,452 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file dnafunc1.c
+ * <pre>
+ *
+ * Rearrangements
+ * l_int32 *l_dnaJoin()
+ * l_int32 *l_dnaaFlattenToDna()
+ *
+ * Conversion between numa and dna
+ * NUMA *l_dnaConvertToNuma()
+ * L_DNA *numaConvertToDna()
+ *
+ * Conversion from pix data to dna
+ * L_DNA *pixConvertDataToDna()
+ *
+ * Set operations using aset (rbtree)
+ * L_DNA *l_dnaUnionByAset()
+ * L_DNA *l_dnaRemoveDupsByAset()
+ * L_DNA *l_dnaIntersectionByAset()
+ * L_ASET *l_asetCreateFromDna()
+ *
+ * Miscellaneous operations
+ * L_DNA *l_dnaDiffAdjValues()
+ *
+ *
+ * This file contains an implementation on sets of doubles (or integers)
+ * that uses an underlying tree (rbtree). The keys stored in the tree
+ * are simply the double array values in the dna. Use of a DnaHash
+ * is typically more efficient, with O(1) in lookup and insertion.
+ *
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*----------------------------------------------------------------------*
+ * Rearrangements *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaJoin()
+ *
+ * \param[in] dad dest dna; add to this one
+ * \param[in] das [optional] source dna; add from this one
+ * \param[in] istart starting index in das
+ * \param[in] iend ending index in das; use -1 to cat all
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaaFlattenToDna()
+ *
+ * \param[in] daa
+ * \return dad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This 'flattens' the dnaa to a dna, by joining successively
+ * each dna in the dnaa.
+ * (2) It leaves the input dnaa unchanged.
+ * </pre>
+ */
+L_DNA *
+l_dnaaFlattenToDna(L_DNAA *daa)
+{
+l_int32 i, nalloc;
+L_DNA *da, *dad;
+L_DNA **array;
+
+ PROCNAME("l_dnaaFlattenToDna");
+
+ if (!daa)
+ return (L_DNA *)ERROR_PTR("daa not defined", procName, NULL);
+
+ nalloc = daa->nalloc;
+ array = daa->dna;
+ dad = l_dnaCreate(0);
+ for (i = 0; i < nalloc; i++) {
+ da = array[i];
+ if (!da) continue;
+ l_dnaJoin(dad, da, 0, -1);
+ }
+
+ return dad;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Conversion between numa and dna *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaConvertToNuma()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaConvertToDna
+ *
+ * \param[in] 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;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Conversion from pix data to dna *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertDataToDna()
+ *
+ * \param[in] pix 32 bpp RGB(A)
+ * \return da, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This writes the RGBA pixel values into the dna, in row-major order.
+ * </pre>
+ */
+L_DNA *
+pixConvertDataToDna(PIX *pix)
+{
+l_int32 i, j, w, h, wpl;
+l_uint32 *data, *line;
+L_DNA *da;
+
+ PROCNAME("pixConvertDataToDna");
+
+ if (!pix)
+ return (L_DNA *)ERROR_PTR("pix not defined", procName, NULL);
+ if (pixGetDepth(pix) != 32)
+ return (L_DNA *)ERROR_PTR("pix not 32 bpp", procName, NULL);
+
+ pixGetDimensions(pix, &w, &h, NULL);
+ data = pixGetData(pix);
+ wpl = pixGetWpl(pix);
+ da = l_dnaCreate(w * h);
+ for (i = 0; i < h; i++) {
+ line = data + i * wpl;
+ for (j = 0; j < w; j++)
+ l_dnaAddNumber(da, (l_float64)line[j]);
+ }
+ return da;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Set operations using aset (rbtree) *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaUnionByAset()
+ *
+ * \param[in] da1, da2
+ * \return dad with the union of the set of numbers, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See sarrayUnionByAset() for the approach.
+ * (2) Here, the key in building the sorted tree is the number itself.
+ * (3) Operations using an underlying tree are O(nlogn), which is
+ * typically less efficient than hashing, which is O(n).
+ * </pre>
+ */
+L_DNA *
+l_dnaUnionByAset(L_DNA *da1,
+ L_DNA *da2)
+{
+L_DNA *da3, *dad;
+
+ PROCNAME("l_dnaUnionByAset");
+
+ if (!da1)
+ return (L_DNA *)ERROR_PTR("da1 not defined", procName, NULL);
+ if (!da2)
+ return (L_DNA *)ERROR_PTR("da2 not defined", procName, NULL);
+
+ /* Join */
+ da3 = l_dnaCopy(da1);
+ l_dnaJoin(da3, da2, 0, -1);
+
+ /* Eliminate duplicates */
+ dad = l_dnaRemoveDupsByAset(da3);
+ l_dnaDestroy(&da3);
+ return dad;
+}
+
+
+/*!
+ * \brief l_dnaRemoveDupsByAset()
+ *
+ * \param[in] das
+ * \return dad with duplicates removed, or NULL on error
+ */
+L_DNA *
+l_dnaRemoveDupsByAset(L_DNA *das)
+{
+l_int32 i, n;
+l_float64 val;
+L_DNA *dad;
+L_ASET *set;
+RB_TYPE key;
+
+ PROCNAME("l_dnaRemoveDupsByAset");
+
+ if (!das)
+ return (L_DNA *)ERROR_PTR("das not defined", procName, NULL);
+
+ set = l_asetCreate(L_FLOAT_TYPE);
+ dad = l_dnaCreate(0);
+ n = l_dnaGetCount(das);
+ for (i = 0; i < n; i++) {
+ l_dnaGetDValue(das, i, &val);
+ key.ftype = val;
+ if (!l_asetFind(set, key)) {
+ l_dnaAddNumber(dad, val);
+ l_asetInsert(set, key);
+ }
+ }
+
+ l_asetDestroy(&set);
+ return dad;
+}
+
+
+/*!
+ * \brief l_dnaIntersectionByAset()
+ *
+ * \param[in] da1, da2
+ * \return dad with the intersection of the two arrays, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See sarrayIntersection() for the approach.
+ * (2) Here, the key in building the sorted tree is the number itself.
+ * (3) Operations using an underlying tree are O(nlogn), which is
+ * typically less efficient than hashing, which is O(n).
+ * </pre>
+ */
+L_DNA *
+l_dnaIntersectionByAset(L_DNA *da1,
+ L_DNA *da2)
+{
+l_int32 n1, n2, i, n;
+l_float64 val;
+L_ASET *set1, *set2;
+RB_TYPE key;
+L_DNA *da_small, *da_big, *dad;
+
+ PROCNAME("l_dnaIntersectionByAset");
+
+ 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 largest array into a set */
+ 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 */
+ set1 = l_asetCreateFromDna(da_big);
+
+ /* Build up the intersection of floats */
+ dad = l_dnaCreate(0);
+ n = l_dnaGetCount(da_small);
+ set2 = l_asetCreate(L_FLOAT_TYPE);
+ for (i = 0; i < n; i++) {
+ l_dnaGetDValue(da_small, i, &val);
+ key.ftype = val;
+ if (l_asetFind(set1, key) && !l_asetFind(set2, key)) {
+ l_dnaAddNumber(dad, val);
+ l_asetInsert(set2, key);
+ }
+ }
+
+ l_asetDestroy(&set1);
+ l_asetDestroy(&set2);
+ return dad;
+}
+
+
+/*!
+ * \brief l_asetCreateFromDna()
+ *
+ * \param[in] da source dna
+ * \return set using the doubles in %da as keys
+ */
+L_ASET *
+l_asetCreateFromDna(L_DNA *da)
+{
+l_int32 i, n;
+l_float64 val;
+L_ASET *set;
+RB_TYPE key;
+
+ PROCNAME("l_asetCreateFromDna");
+
+ if (!da)
+ return (L_ASET *)ERROR_PTR("da not defined", procName, NULL);
+
+ set = l_asetCreate(L_FLOAT_TYPE);
+ n = l_dnaGetCount(da);
+ for (i = 0; i < n; i++) {
+ l_dnaGetDValue(da, i, &val);
+ key.ftype = val;
+ l_asetInsert(set, key);
+ }
+
+ return set;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Miscellaneous operations *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaDiffAdjValues()
+ *
+ * \param[in] das input l_dna
+ * \return dad of difference values val[i+1] - val[i],
+ * or NULL on error
+ */
+L_DNA *
+l_dnaDiffAdjValues(L_DNA *das)
+{
+l_int32 i, n, prev, cur;
+L_DNA *dad;
+
+ PROCNAME("l_dnaDiffAdjValues");
+
+ 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;
+}
+
diff --git a/leptonica/src/dnahash.c b/leptonica/src/dnahash.c
new file mode 100644
index 00000000..bd8a45fb
--- /dev/null
+++ b/leptonica/src/dnahash.c
@@ -0,0 +1,605 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file dnahash.c
+ * <pre>
+ *
+ * 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()
+ * l_int32 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()
+ *
+ * (1) The DnaHash is an array of Dna. It is useful for fast
+ * storage and lookup for sets and maps. If the set or map
+ * is on a Dna itself, the hash is a simple function that
+ * maps a double to a l_uint64; otherwise the function will
+ * map a string or a (x,y) point to a l_uint64. The result of
+ * the map is the "key", which is then used with the mod
+ * function to select which Dna array is to be used. The
+ * number of arrays in a DnaHash should be a prime number.
+ * If there are N items, we set up the DnaHash array to have
+ * approximately N/20 Dna, so the average size of these arrays
+ * will be about 20 when fully populated. The number 20 was
+ * found empirically to be in a broad maximum of efficiency.
+ * (2) Note that the word "hash" is overloaded. There are actually
+ * two hashing steps: the first hashes the object to a l_uint64,
+ * called the "key", and the second uses the mod function to
+ * "hash" the "key" to the index of a particular Dna in the
+ * DnaHash array.
+ * (3) Insertion and lookup time for DnaHash is O(1). Hash collisions
+ * are easily handled (we expect an average of 20 for each key),
+ * so we can use simple (fast) hash functions: we deal with
+ * collisions by storing an array for each hash key.
+ * This can be contrasted with using rbtree for sets and
+ * maps, where insertion and lookup are O(logN) and hash functions
+ * are slower because they must be good enough (i.e, random
+ * enough with arbitrary input) to avoid collisions.
+ * (4) Hash functions that map points, strings and floats to l_uint64
+ * are given in utils.c.
+ * (5) The use of the DnaHash (and RBTree) with strings and
+ * (x,y) points can be found in string2.c and ptafunc2.c, rsp.
+ * This file has similar hash set functions, using DnaHash on
+ * two input Dna, for removing duplicates and finding the
+ * intersection. It also uses DnaHash as a hash map to find
+ * a histogram of counts from an input Dna.
+ * (6) Comparisons in running time, between DnaHash and RBTree, for
+ * large sets of strings and points, are given in prog/hashtest.c.
+ * (7) This is a very simple implementation, that expects that you
+ * know approximately (i.e., within a factor of 2 or 3) how many
+ * items are to be stored when you initialize the DnaHash.
+ * (It would be nice to modify the l_dnaHashAdd() function
+ * to increase the number of bins when the average occupation
+ * exceeds 40 or so.)
+ * (8) Useful rule of thumb for hashing collisions:
+ * For a random hashing function (say, from strings to l_uint64),
+ * the probability of a collision increases as N^2 for N much
+ * less than 2^32. The quadratic behavior switches over to
+ * approaching 1.0 around 2^32, which is the square root of 2^64.
+ * So, for example, if you have 10^7 strings, the probability
+ * of a single collision using an l_uint64 key is on the order of
+ * (10^7/10^9)^2 ~ 10^-4.
+ * For a million strings you don't need to worry about collisons
+ * (~10-6 probability), and for most applications can use the
+ * RBTree (sorting) implementation with confidence.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*--------------------------------------------------------------------------*
+ * Dna hash: Creation and destruction *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaHashCreate()
+ *
+ * \param[in] nbuckets the number of buckets in the hash table,
+ * which should be prime.
+ * \param[in] initsize initial size of each allocated dna; 0 for default
+ * \return ptr to new dnahash, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Actual dna are created only as required by l_dnaHashAdd()
+ * </pre>
+ */
+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);
+ dahash = (L_DNAHASH *)LEPT_CALLOC(1, sizeof(L_DNAHASH));
+ 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;
+}
+
+
+/*!
+ * \brief l_dnaHashDestroy()
+ *
+ * \param[in,out] pdahash will be set to null before returning
+ * \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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaHashGetCount()
+ *
+ * \param[in] dahash
+ * \return nbuckets allocated, or 0 on error
+ */
+l_int32
+l_dnaHashGetCount(L_DNAHASH *dahash)
+{
+
+ PROCNAME("l_dnaHashGetCount");
+
+ if (!dahash)
+ return ERROR_INT("dahash not defined", procName, 0);
+ return dahash->nbuckets;
+}
+
+
+/*!
+ * \brief l_dnaHashGetTotalCount()
+ *
+ * \param[in] dahash
+ * \return n number of numbers in all dna, or 0 on error
+ */
+l_int32
+l_dnaHashGetTotalCount(L_DNAHASH *dahash)
+{
+l_int32 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;
+}
+
+
+/*!
+ * \brief l_dnaHashGetDna()
+ *
+ * \param[in] dahash
+ * \param[in] key key to be hashed into a bucket number
+ * \param[in] copyflag L_NOCOPY, L_COPY, L_CLONE
+ * \return ptr to dna
+ */
+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;
+}
+
+
+/*!
+ * \brief l_dnaHashAdd()
+ *
+ * \param[in] dahash
+ * \param[in] key key to be hashed into a bucket number
+ * \param[in] value float value to be appended to the specific dna
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief l_dnaHashCreateFromDna()
+ *
+ * \param[in] da
+ * \return dahash if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The values stored in the %dahash are indices into %da;
+ * %dahash has no use without %da.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_dnaRemoveDupsByHash()
+ *
+ * \param[in] das
+ * \param[out] pdad hash set
+ * \param[out] pdahash [optional] dnahash used for lookup
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaMakeHistoByHash()
+ *
+ * \param[in] das
+ * \param[out] pdahash hash map: val --> index
+ * \param[out] pdav [optional] array of values: index --> val
+ * \param[out] pdac [optional] histo array of counts: index --> count
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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) If you only need to make a histogram and get the number of
+ * non-zero entries, here are two methods:
+ * (a) l_dnaMakeHistoByHash(da, &dahash, NULL, NULL);
+ * count = l_dnaHashGetTotalCount(dahash);
+ * (b) l_dnaRemoveDupsByHash(da, &da_nodups, NULL);
+ count = l_dnaGetCount(da_nodups);
+ * (4) Lookup is simple:
+ * l_dnaFindValByHash(dav, dahash, val, &index);
+ * if (index >= 0)
+ * l_dnaGetIValue(dac, index, &icount);
+ * else
+ * icount = 0;
+ * </pre>
+ */
+l_ok
+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)
+ return ERROR_INT("&dahash not 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;
+ if (pdac)
+ *pdac = dac;
+ else
+ l_dnaDestroy(&dac);
+ if (pdav)
+ *pdav = dav;
+ else
+ l_dnaDestroy(&dav);
+ return 0;
+}
+
+
+/*!
+ * \brief l_dnaIntersectionByHash()
+ *
+ * \param[in] da1, da2
+ * \return dad intersection of the number arrays, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This uses the same method for building the intersection set
+ * as ptaIntersectionByHash() and sarrayIntersectionByHash().
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_dnaFindValByHash()
+ *
+ * \param[in] da
+ * \param[in] dahash containing indices into %da
+ * \param[in] val searching for this number in %da
+ * \param[out] pindex index into da if found; -1 otherwise
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
diff --git a/leptonica/src/dwacomb.2.c b/leptonica/src/dwacomb.2.c
new file mode 100644
index 00000000..4f48897d
--- /dev/null
+++ b/leptonica/src/dwacomb.2.c
@@ -0,0 +1,299 @@
+/*====================================================================*
+ - 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()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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"};
+
+/*!
+ * \brief pixMorphDwa_2()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] operation L_MORPH_DILATE, L_MORPH_ERODE,
+ * L_MORPH_OPEN, L_MORPH_CLOSE
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFMorphopGen_2()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] operation L_MORPH_DILATE, L_MORPH_ERODE,
+ * L_MORPH_OPEN, L_MORPH_CLOSE
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/dwacomblow.2.c b/leptonica/src/dwacomblow.2.c
new file mode 100644
index 00000000..e47684ff
--- /dev/null
+++ b/leptonica/src/dwacomblow.2.c
@@ -0,0 +1,4970 @@
+/*====================================================================*
+ - 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_*()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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/leptonica/src/edge.c b/leptonica/src/edge.c
new file mode 100644
index 00000000..764ef3ed
--- /dev/null
+++ b/leptonica/src/edge.c
@@ -0,0 +1,647 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file edge.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*----------------------------------------------------------------------*
+ * Sobel edge detecting filter *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixSobelEdgeFilter()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] orientflag L_HORIZONTAL_EDGES, L_VERTICAL_EDGES, L_ALL_EDGES
+ * \return pixd 8 bpp, edges are brighter, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixTwoSidedEdgeFilter()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] orientflag L_HORIZONTAL_EDGES, L_VERTICAL_EDGES
+ * \return pixd 8 bpp, edges are brighter, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixMeasureEdgeSmoothness()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] side L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT
+ * \param[in] minjump minimum jump to be counted; >= 1
+ * \param[in] minreversal minimum reversal size for new peak or valley
+ * \param[out] pjpl [optional] jumps/length: number of jumps,
+ * normalized to length of component side
+ * \param[out] pjspl [optional] jumpsum/length: sum of all
+ * sufficiently large jumps, normalized to length
+ * of component side
+ * \param[out] prpl [optional] reversals/length: number of
+ * peak-to-valley or valley-to-peak reversals,
+ * normalized to length of component side
+ * \param[in] debugfile [optional] displays constructed edge; use NULL
+ * for no output
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, NULL);
+ nreversal = numaGetCount(nae) - 1;
+ *prpl = (l_float32)nreversal / (l_float32)(n - 1);
+ numaDestroy(&nae);
+ }
+
+ numaDestroy(&na);
+ return 0;
+}
+
+
+/*!
+ * \brief pixGetEdgeProfile()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] side L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT
+ * \param[in] 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;
+}
+
+
+/*
+ * \brief pixGetLastOffPixelInRun()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] x, y starting location
+ * \param[in] direction L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT
+ * \param[out] ploc location in scan direction coordinate
+ * of last OFF pixel found
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*
+ * \brief pixGetLastOnPixelInRun()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] x, y starting location
+ * \param[in] direction L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT
+ * \param[out] ploc location in scan direction coordinate
+ * of first ON pixel found
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/encoding.c b/leptonica/src/encoding.c
new file mode 100644
index 00000000..8102ca97
--- /dev/null
+++ b/leptonica/src/encoding.c
@@ -0,0 +1,738 @@
+/*====================================================================*
+ - 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()
+ *
+ * char *encodeAscii85WithComp()
+ * l_uint8 *decodeAscii85WithComp()
+ *
+ * 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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <ctype.h>
+#include <string.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(const l_uint8 *inarray, size_t insize,
+ l_int32 *pindex, char *outbuf,
+ l_int32 *pnbout);
+
+/*-------------------------------------------------------------*
+ * Utility for encoding and decoding data with base64 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief encodeBase64()
+ *
+ * \param[in] inarray input binary data
+ * \param[in] insize number of bytes in input array
+ * \param[out] poutsize number of bytes in output char array
+ * \return chara with MAX_BASE64_LINE characters + \n in each line
+ *
+ * <pre>
+ * Notes:
+ * (1) The input character data is unrestricted binary.
+ * The output encoded data consists of the 64 characters
+ * in the base64 set, plus newlines and the pad character '='.
+ * </pre>
+ */
+char *
+encodeBase64(const l_uint8 *inarray,
+ l_int32 insize,
+ l_int32 *poutsize)
+{
+char *chara;
+const 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;
+}
+
+
+/*!
+ * \brief decodeBase64()
+ *
+ * \param[in] inarray input encoded char data, with 72 chars/line)
+ * \param[in] insize number of bytes in input array
+ * \param[out] poutsize number of bytes in output byte array
+ * \return bytea decoded byte data, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief isBase64()
+ */
+static l_int32
+isBase64(char c)
+{
+ return (isalnum(((int)c)) || ((c) == '+') || ((c) == '/')) ? 1 : 0;
+}
+
+/*!
+ * \brief 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;
+}
+
+/*!
+ * \brief 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;
+}
+
+/*!
+ * \brief 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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief encodeAscii85()
+ *
+ * \param[in] inarray input data
+ * \param[in] insize number of bytes in input array
+ * \param[out] poutsize number of bytes in output char array
+ * \return chara with 64 characters + \n in each line
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+char *
+encodeAscii85(const l_uint8 *inarray,
+ size_t insize,
+ size_t *poutsize)
+{
+char *chara;
+char outbuf[8];
+l_int32 maxsize, i, index, linecount, nbout, eof;
+size_t outindex;
+
+ 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);
+
+ 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;
+ }
+ }
+
+ *poutsize = outindex;
+ return chara;
+}
+
+
+/*!
+ * \brief convertChunkToAscii85()
+ *
+ * \param[in] inarray input data
+ * \param[in] insize number of bytes in input array
+ * \param[out] pindex use and -- ptr
+ * \param[in] outbuf holds 8 ascii chars; we use no more than 7
+ * \param[out] pnbsout number of bytes written to outbuf
+ * \return boolean for eof 0 if more data, 1 if end of file
+ *
+ * <pre>
+ * Notes:
+ * (1) Attempts to read 4 bytes and write 5.
+ * (2) Writes 1 byte if the value is 0.
+ * </pre>
+ */
+static l_int32
+convertChunkToAscii85(const l_uint8 *inarray,
+ size_t 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 += (l_uint32)inbyte << (8 * (3 - i));
+ }
+
+#if 0
+ lept_stderr("index = %d, nread = %d\n", index, nread);
+ lept_stderr("inword = %x\n", inword);
+ lept_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;
+}
+
+
+/*!
+ * \brief decodeAscii85()
+ *
+ * \param[in] inarray ascii85 input data
+ * \param[in] insize number of bytes in input array
+ * \param[out] poutsize number of bytes in output l_uint8 array
+ * \return outarray binary
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_uint8 *
+decodeAscii85(const char *inarray,
+ size_t insize,
+ size_t *poutsize)
+{
+char inc;
+const 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;
+}
+
+
+/*!
+ * \brief encodeAscii85WithComp)
+ *
+ * \param[in] indata input binary data
+ * \param[in] insize number of bytes in input data
+ * \param[out] poutsize number of bytes in output string
+ * \return outstr with 64 characters + \n in each line
+ *
+ * <pre>
+ * Notes:
+ * (1) Compress the input data; then encode ascii85. For ascii
+ * input, a first compression step will significantly reduce
+ * the final encoded output size.
+ * </pre>
+ */
+char *
+encodeAscii85WithComp(const l_uint8 *indata,
+ size_t insize,
+ size_t *poutsize)
+{
+char *outstr;
+size_t size1;
+l_uint8 *data1;
+
+ PROCNAME("encodeAscii85WithComp");
+
+ if (!poutsize)
+ return (char *)ERROR_PTR("&outsize not defined", procName, NULL);
+ *poutsize = 0;
+ if (!indata)
+ return (char *)ERROR_PTR("indata not defined", procName, NULL);
+
+ if ((data1 = zlibCompress(indata, insize, &size1)) == NULL)
+ return (char *)ERROR_PTR("data1 not made", procName, NULL);
+ outstr = encodeAscii85(data1, size1, poutsize);
+ LEPT_FREE(data1);
+ return outstr;
+}
+
+
+/*!
+ * \brief decodeAscii85WithComp()
+ *
+ * \param[in] instr ascii85 input data string
+ * \param[in] insize number of bytes in input data
+ * \param[out] poutsize number of bytes in output binary data
+ * \return outdata binary data before compression and ascii85 encoding
+ *
+ * <pre>
+ * Notes:
+ * (1) We assume the input data has been zlib compressed and then
+ * properly encoded, so we reverse the procedure. This is the
+ * inverse of encodeAscii85WithComp().
+ * (2) Set %insize == 0 to use strlen(%instr).
+ * </pre>
+ */
+l_uint8 *
+decodeAscii85WithComp(const char *instr,
+ size_t insize,
+ size_t *poutsize)
+{
+size_t size1;
+l_uint8 *data1, *outdata;
+
+ PROCNAME("decodeAscii85WithComp");
+
+ if (!poutsize)
+ return (l_uint8 *)ERROR_PTR("&outsize not defined", procName, NULL);
+ *poutsize = 0;
+ if (!instr)
+ return (l_uint8 *)ERROR_PTR("instr not defined", procName, NULL);
+
+ if (insize == 0) insize = strlen(instr);
+ if ((data1 = decodeAscii85(instr, insize, &size1)) == NULL)
+ return (l_uint8 *)ERROR_PTR("data1 not made", procName, NULL);
+ outdata = zlibUncompress(data1, size1, poutsize);
+ LEPT_FREE(data1);
+ return outdata;
+}
+
+
+/*-------------------------------------------------------------*
+ * String reformatting for base 64 encoded data *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief reformatPacked64()
+ *
+ * \param[in] inarray base64 encoded string with newlines
+ * \param[in] insize number of bytes in input array
+ * \param[in] leadspace number of spaces in each line before the data
+ * \param[in] linechars number of bytes of data in each line; multiple of 4
+ * \param[in] addquotes 1 to add quotes to each line of data; 0 to skip
+ * \param[out] poutsize number of bytes in output char array
+ * \return outarray ascii
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+char *
+reformatPacked64(const 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((size_t)nlines * linewithpad,
+ sizeof(char))) == NULL) {
+ LEPT_FREE(flata);
+ 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/leptonica/src/endianness.h.in b/leptonica/src/endianness.h.in
new file mode 100644
index 00000000..e6b1d454
--- /dev/null
+++ b/leptonica/src/endianness.h.in
@@ -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/leptonica/src/enhance.c b/leptonica/src/enhance.c
new file mode 100644
index 00000000..0b903780
--- /dev/null
+++ b/leptonica/src/enhance.c
@@ -0,0 +1,2360 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file enhance.c
+ * <pre>
+ *
+ * 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
+ * l_int32 pixTRCMap()
+ * l_int32 pixTRCMapGeneral()
+ *
+ * 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 *pixMosaicColorShiftRGB()
+ * PIX *pixColorShiftRGB()
+ *
+ * Darken gray (unsaturated) pixels
+ * PIX *pixDarkenGray()
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 EnhanceScaleFactor = 5.0;
+
+/*-------------------------------------------------------------*
+ * Gamma TRC (tone reproduction curve) mapping *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixGammaTRC()
+ *
+ * \param[in] pixd [optional] null or equal to pixs
+ * \param[in] pixs 8 or 32 bpp; or 2, 4 or 8 bpp with colormap
+ * \param[in] gamma gamma correction; must be > 0.0
+ * \param[in] minval input value that gives 0 for output; can be < 0
+ * \param[in] maxval input value that gives 255 for output; can be > 255
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * (12) Use pixTRCMapGeneral() if applying different mappings
+ * to each channel in an RGB image.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGammaTRCMasked()
+ *
+ * \param[in] pixd [optional] null or equal to pixs
+ * \param[in] pixs 8 or 32 bpp; not colormapped
+ * \param[in] pixm [optional] null or 1 bpp
+ * \param[in] gamma gamma correction; must be > 0.0
+ * \param[in] minval input value that gives 0 for output; can be < 0
+ * \param[in] maxval input value that gives 255 for output; can be > 255
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGammaTRCWithAlpha()
+ *
+ * \param[in] pixd [optional] null or equal to pixs
+ * \param[in] pixs 32 bpp
+ * \param[in] gamma gamma correction; must be > 0.0
+ * \param[in] minval input value that gives 0 for output; can be < 0
+ * \param[in] maxval input value that gives 255 for output; can be > 255
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaGammaTRC()
+ *
+ * \param[in] gamma gamma factor; must be > 0.0
+ * \param[in] minval input value that gives 0 for output
+ * \param[in] maxval input value that gives 255 for output
+ * \return na, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The map is returned as a numa; values are clipped to [0, 255].
+ * (2) For a linear mapping, set gamma = 1.0.
+ * (3) To force all intensities into a range within fraction delta
+ * of white, use: minval = -256 * (1 - delta) / delta
+ * maxval = 255
+ * (4) To force all intensities into a range within fraction delta
+ * of black, use: minval = 0
+ * maxval = 256 * (1 - delta) / delta
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixContrastTRC()
+ *
+ * \param[in] pixd [optional] null or equal to pixs
+ * \param[in] pixs 8 or 32 bpp; or 2, 4 or 8 bpp with colormap
+ * \param[in] factor 0.0 is no enhancement
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixContrastTRCMasked()
+ *
+ * \param[in] pixd [optional] null or equal to pixs
+ * \param[in] pixs 8 or 32 bpp; or 2, 4 or 8 bpp with colormap
+ * \param[in] pixm [optional] null or 1 bpp
+ * \param[in] factor 0.0 is no enhancement
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaContrastTRC()
+ *
+ * \param[in] factor generally between 0.0 [no enhancement]
+ * and 1.0, but can be larger than 1.0
+ * \return na, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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 = EnhanceScaleFactor;
+ 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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixEqualizeTRC()
+ *
+ * \param[in] pixd [optional] null or equal to pixs
+ * \param[in] pixs 8 bpp gray, 32 bpp rgb, or colormapped
+ * \param[in] fract fraction of equalization movement of pixel values
+ * \param[in] factor subsampling factor; integer >= 1
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaEqualizeTRC()
+ *
+ * \param[in] pix 8 bpp, no colormap
+ * \param[in] fract fraction of equalization movement of pixel values
+ * \param[in] factor subsampling factor; integer >= 1
+ * \return nad, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixTRCMap()
+ *
+ * \param[in] pixs 8 grayscale or 32 bpp rgb; not colormapped
+ * \param[in] pixm [optional] 1 bpp mask
+ * \param[in] na mapping array
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_int32
+pixTRCMap(PIX *pixs,
+ PIX *pixm,
+ NUMA *na)
+{
+l_int32 w, h, d, wm, hm, wpl, wplm, i, j, sval8, dval8;
+l_uint32 sval32, dval32;
+l_uint32 *data, *datam, *line, *linem, *tab;
+
+ 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 = (l_uint32 *)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;
+}
+
+
+/*!
+ * \brief pixTRCMapGeneral()
+ *
+ * \param[in] pixs 32 bpp rgb; not colormapped
+ * \param[in] pixm [optional] 1 bpp mask
+ * \param[in] nar, nag, nab mapping arrays
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This operation is in-place on %pixs.
+ * (2) Each of the r,g,b mapping arrays is of size 256. They map the
+ * input value for that color component into values in the
+ * range [0, 255].
+ * (3) In the special case where the r, g and b mapping arrays are
+ * all the same, call pixTRCMap() instead.
+ * (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) The alpha channel is not saved.
+ * </pre>
+ */
+l_int32
+pixTRCMapGeneral(PIX *pixs,
+ PIX *pixm,
+ NUMA *nar,
+ NUMA *nag,
+ NUMA *nab)
+{
+l_int32 w, h, wm, hm, wpl, wplm, i, j;
+l_uint32 sval32, dval32;
+l_uint32 *data, *datam, *line, *linem, *tabr, *tabg, *tabb;
+
+ PROCNAME("pixTRCMapGeneral");
+
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return ERROR_INT("pixs not defined or not 32 bpp", procName, 1);
+ if (pixm && pixGetDepth(pixm) != 1)
+ return ERROR_INT("pixm defined and not 1 bpp", procName, 1);
+ if (!nar || !nag || !nab)
+ return ERROR_INT("na{r,g,b} not all defined", procName, 1);
+ if (numaGetCount(nar) != 256 || numaGetCount(nag) != 256 ||
+ numaGetCount(nab) != 256)
+ return ERROR_INT("na{r,g,b} not all of size 256", procName, 1);
+
+ /* Get the arrays for efficiency */
+ tabr = (l_uint32 *)numaGetIArray(nar);
+ tabg = (l_uint32 *)numaGetIArray(nag);
+ tabb = (l_uint32 *)numaGetIArray(nab);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ wpl = pixGetWpl(pixs);
+ data = pixGetData(pixs);
+ if (!pixm) {
+ for (i = 0; i < h; i++) {
+ line = data + i * wpl;
+ for (j = 0; j < w; j++) {
+ sval32 = *(line + j);
+ dval32 =
+ tabr[(sval32 >> L_RED_SHIFT) & 0xff] << L_RED_SHIFT |
+ tabg[(sval32 >> L_GREEN_SHIFT) & 0xff] << L_GREEN_SHIFT |
+ tabb[(sval32 >> L_BLUE_SHIFT) & 0xff] << L_BLUE_SHIFT;
+ *(line + j) = dval32;
+ }
+ }
+ } else {
+ datam = pixGetData(pixm);
+ wplm = pixGetWpl(pixm);
+ pixGetDimensions(pixm, &wm, &hm, NULL);
+ 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 =
+ tabr[(sval32 >> L_RED_SHIFT) & 0xff] << L_RED_SHIFT |
+ tabg[(sval32 >> L_GREEN_SHIFT) & 0xff] << L_GREEN_SHIFT |
+ tabb[(sval32 >> L_BLUE_SHIFT) & 0xff] << L_BLUE_SHIFT;
+ *(line + j) = dval32;
+ }
+ }
+ }
+
+ LEPT_FREE(tabr);
+ LEPT_FREE(tabg);
+ LEPT_FREE(tabb);
+ return 0;
+}
+
+
+
+/*-----------------------------------------------------------------------*
+ * Unsharp masking *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixUnsharpMasking()
+ *
+ * \param[in] pixs all depths except 1 bpp; with or without colormaps
+ * \param[in] halfwidth "half-width" of smoothing filter
+ * \param[in] fract fraction of edge added back into image
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixUnsharpMasking(PIX *pixs,
+ l_int32 halfwidth,
+ l_float32 fract)
+{
+l_int32 d;
+PIX *pix1, *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 ((pix1 = pixConvertTo8Or32(pixs, L_CLONE, 0)) == NULL)
+ return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+
+ /* Sharpen */
+ d = pixGetDepth(pix1);
+ if (d == 8) {
+ pixd = pixUnsharpMaskingGray(pix1, halfwidth, fract);
+ } else { /* d == 32 */
+ pixr = pixGetRGBComponent(pix1, COLOR_RED);
+ pixrs = pixUnsharpMaskingGray(pixr, halfwidth, fract);
+ pixDestroy(&pixr);
+ pixg = pixGetRGBComponent(pix1, COLOR_GREEN);
+ pixgs = pixUnsharpMaskingGray(pixg, halfwidth, fract);
+ pixDestroy(&pixg);
+ pixb = pixGetRGBComponent(pix1, COLOR_BLUE);
+ pixbs = pixUnsharpMaskingGray(pixb, halfwidth, fract);
+ pixDestroy(&pixb);
+ pixd = pixCreateRGBImage(pixrs, pixgs, pixbs);
+ pixDestroy(&pixrs);
+ pixDestroy(&pixgs);
+ pixDestroy(&pixbs);
+ if (pixGetSpp(pixs) == 4)
+ pixCopyRGBComponent(pixd, pixs, L_ALPHA_CHANNEL);
+ }
+
+ pixDestroy(&pix1);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixUnsharpMaskingGray()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] halfwidth "half-width" of smoothing filter
+ * \param[in] fract fraction of edge added back into image
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixUnsharpMaskingFast()
+ *
+ * \param[in] pixs all depths except 1 bpp; with or without colormaps
+ * \param[in] halfwidth "half-width" of smoothing filter; 1 and 2 only
+ * \param[in] fract fraction of high frequency added to image
+ * \param[in] direction L_HORIZ, L_VERT, L_BOTH_DIRECTIONS
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, L_CLONE, 0)) == 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)
+ pixCopyRGBComponent(pixd, pixs, L_ALPHA_CHANNEL);
+ pixDestroy(&pixrs);
+ pixDestroy(&pixgs);
+ pixDestroy(&pixbs);
+ }
+
+ pixDestroy(&pixt);
+ return pixd;
+}
+
+
+
+/*!
+ * \brief pixUnsharpMaskingGrayFast()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] halfwidth "half-width" of smoothing filter: 1 or 2
+ * \param[in] fract fraction of high frequency added to image
+ * \param[in] direction L_HORIZ, L_VERT, L_BOTH_DIRECTIONS
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For usage and explanation of the algorithm, see notes
+ * in pixUnsharpMaskingFast().
+ * (2) Returns a clone if no sharpening is requested.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixUnsharpMaskingGray1D()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] halfwidth "half-width" of smoothing filter: 1 or 2
+ * \param[in] fract fraction of high frequency added to image
+ * \param[in] direction filtering direction; use L_HORIZ or L_VERT
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For usage and explanation of the algorithm, see notes
+ * in pixUnsharpMaskingFast().
+ * (2) Returns a clone if no sharpening is requested.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixUnsharpMaskingGray2D()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] halfwidth "half-width" of smoothing filter: 1 or 2
+ * \param[in] fract fraction of high frequency added to image
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is for %halfwidth == 1, 2.
+ * (2) The lowpass filter is implemented separably.
+ * (3) Returns a clone if no sharpening is requested.
+ * </pre>
+ */
+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, *lined;
+l_float32 val, norm;
+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);
+
+ if ((pixd = pixCopyBorder(NULL, pixs, halfwidth, halfwidth,
+ halfwidth, halfwidth)) == NULL)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ datad = pixGetData(pixd);
+ wpld = pixGetWpl(pixd);
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+
+ /* Do the low pass separably. Store the result of horizontal
+ * smoothing in an intermediate fpix. */
+ if ((fpix = fpixCreate(w, h)) == NULL) {
+ pixDestroy(&pixd);
+ return (PIX *)ERROR_PTR("fpix not made", procName, NULL);
+ }
+ dataf = fpixGetData(fpix);
+ wplf = fpixGetWpl(fpix);
+ if (halfwidth == 1) {
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ linef = dataf + i * wplf;
+ for (j = 1; j < w - 1; j++) {
+ val = GET_DATA_BYTE(lines, j - 1) +
+ GET_DATA_BYTE(lines, j) +
+ GET_DATA_BYTE(lines, j + 1);
+ linef[j] = val;
+ }
+ }
+ } else {
+ for (i = 0; i < h; 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. */
+ if (halfwidth == 1) {
+ for (i = 1; i < h - 1; i++) {
+ linef0 = dataf + (i - 1) * wplf;
+ linef1 = dataf + i * wplf;
+ linef2 = dataf + (i + 1) * wplf;
+ lined = datad + i * wpld;
+ lines = datas + i * wpls;
+ norm = 1.0f / 9.0f;
+ for (j = 1; j < w - 1; j++) {
+ val = norm * (linef0[j] + linef1[j] +
+ linef2[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);
+ }
+ }
+ } else {
+ 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;
+ norm = 1.0f / 25.0f;
+ for (j = 2; j < w - 2; j++) {
+ val = norm * (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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixModifyHue()
+ *
+ * \param[in] pixd [optional] can be null or equal to pixs
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] fract between -1.0 and 1.0
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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) 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.
+ * (3) 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.
+ * (4) This leaves saturation and intensity invariant.
+ * (5) See discussion of color-modification methods, in coloring.c.
+ * </pre>
+ */
+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)
+ pixCopyRGBComponent(pixd, pixs, L_ALPHA_CHANNEL);
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixModifySaturation()
+ *
+ * \param[in] pixd [optional] can be null, existing or equal to pixs
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] fract between -1.0 and 1.0
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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) This leaves hue and intensity invariant.
+ * (4) See discussion of color-modification methods, in coloring.c.
+ * </pre>
+ */
+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)
+ pixCopyRGBComponent(pixd, pixs, L_ALPHA_CHANNEL);
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixMeasureSaturation()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[out] psat average saturation
+ * \return 0 if OK, 1 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;
+}
+
+
+/*!
+ * \brief pixModifyBrightness()
+ *
+ * \param[in] pixd [optional] can be null, existing or equal to pixs
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] fract between -1.0 and 1.0
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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) This leaves hue and saturation invariant.
+ * (4) See discussion of color-modification methods, in coloring.c.
+ * </pre>
+ */
+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)
+ pixCopyRGBComponent(pixd, pixs, L_ALPHA_CHANNEL);
+
+ return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ * Color shifting *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixMosaicColorShiftRGB()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] roff center offset of red component
+ * \param[in] goff center offset of green component
+ * \param[in] boff center offset of blue component
+ * \param[in] delta increments from center offsets [0.0 - 0.1];
+ * use 0.0 to get the default (0.04)
+ * \param[in] nincr number of increments in each (positive and negative)
+ * direction; use 0 to get the default (2).
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates a mosaic view of the effect of shifting the RGB
+ * components. See pixColorShiftRGB() for details on the shifting.
+ * (2) The offsets (%roff, %goff, %boff) set the color center point,
+ * and the deviations from this are shown separately for deltas
+ * in r, g and b. For each component, we show 2 * %nincr + 1
+ * images.
+ * (3) Usage: color prints differ from the original due to three factors:
+ * illumination, calibration of the camera in acquisition,
+ * and calibration of the printer. This function can be used
+ * to iteratively match a color print to the original. On each
+ * iteration, the center offsets are set to the best match so
+ * far, and the %delta increments are typically reduced.
+ * </pre>
+ */
+PIX *
+pixMosaicColorShiftRGB(PIX *pixs,
+ l_float32 roff,
+ l_float32 goff,
+ l_float32 boff,
+ l_float32 delta,
+ l_int32 nincr)
+{
+char buf[64];
+l_int32 i;
+l_float32 del;
+L_BMF *bmf;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa;
+
+ PROCNAME("pixMosaicColorShiftRGB");
+
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return (PIX *)ERROR_PTR("pixs undefined or not rgb", procName, NULL);
+ if (roff < -1.0 || roff > 1.0)
+ return (PIX *)ERROR_PTR("roff not in [-1.0, 1.0]", procName, NULL);
+ if (goff < -1.0 || goff > 1.0)
+ return (PIX *)ERROR_PTR("goff not in [-1.0, 1.0]", procName, NULL);
+ if (boff < -1.0 || boff > 1.0)
+ return (PIX *)ERROR_PTR("boff not in [-1.0, 1.0]", procName, NULL);
+ if (delta < 0.0 || delta > 0.1)
+ return (PIX *)ERROR_PTR("delta not in [0.0, 0.1]", procName, NULL);
+ if (delta == 0.0) delta = 0.04f;
+ if (nincr < 0 || nincr > 6)
+ return (PIX *)ERROR_PTR("nincr not in [0, 6]", procName, NULL);
+ if (nincr == 0) nincr = 2;
+
+ pixa = pixaCreate(3 * (2 * nincr + 1));
+ bmf = bmfCreate(NULL, 8);
+ pix1 = pixScaleToSize(pixs, 400, 0);
+ for (i = 0, del = - nincr * delta; i < 2 * nincr + 1; i++, del += delta) {
+ pix2 = pixColorShiftRGB(pix1, roff + del, goff, boff);
+ snprintf(buf, sizeof(buf), "%4.2f, %4.2f, %4.2f",
+ roff + del, goff, boff);
+ pix3 = pixAddSingleTextblock(pix2, bmf, buf, 0xff000000,
+ L_ADD_BELOW, 0);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ for (i = 0, del = - nincr * delta; i < 2 * nincr + 1; i++, del += delta) {
+ pix2 = pixColorShiftRGB(pix1, roff, goff + del, boff);
+ snprintf(buf, sizeof(buf), "%4.2f, %4.2f, %4.2f",
+ roff, goff + del, boff);
+ pix3 = pixAddSingleTextblock(pix2, bmf, buf, 0xff000000,
+ L_ADD_BELOW, 0);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ for (i = 0, del = - nincr * delta; i < 2 * nincr + 1; i++, del += delta) {
+ pix2 = pixColorShiftRGB(pix1, roff, goff, boff + del);
+ snprintf(buf, sizeof(buf), "%4.2f, %4.2f, %4.2f",
+ roff, goff, boff + del);
+ pix3 = pixAddSingleTextblock(pix2, bmf, buf, 0xff000000,
+ L_ADD_BELOW, 0);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+
+ pix1 = pixaDisplayTiledAndScaled(pixa, 32, 300, 2 * nincr + 1, 0, 30, 2);
+ pixaDestroy(&pixa);
+ bmfDestroy(&bmf);
+ return pix1;
+}
+
+
+/*!
+ * \brief pixColorShiftRGB()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] rfract fractional shift in red component
+ * \param[in] gfract fractional shift in green component
+ * \param[in] bfract fractional shift in blue component
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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;
+}
+
+/*-----------------------------------------------------------------------*
+ * Darken gray (unsaturated) pixels
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixDarkenGray()
+ *
+ * \param[in] pixd [optional] can be null or equal to pixs
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] thresh pixels with max component >= %thresh are unchanged
+ * \param[in] satlimit pixels with saturation >= %satlimit are unchanged
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This darkens gray pixels, by a fraction (sat/%satlimit), where
+ * the saturation, sat, is the component difference (max - min).
+ * The pixel value is unchanged if sat >= %satlimit. A typical
+ * value of %satlimit might be 40; the larger the value, the
+ * more that pixels with a smaller saturation will be darkened.
+ * (2) Pixels with max component >= %thresh are unchanged. This can be
+ * used to prevent bright pixels with low saturation from being
+ * darkened. Setting thresh == 0 is a no-op; setting %thresh == 255
+ * causes the darkening to be applied to all pixels.
+ * (3) This function is useful to enhance pixels relative to a
+ * gray background.
+ * (4) A related function that builds a 1 bpp mask over the gray
+ * pixels is pixMaskOverGrayPixels().
+ * </pre>
+ */
+PIX *
+pixDarkenGray(PIX *pixd,
+ PIX *pixs,
+ l_int32 thresh,
+ l_int32 satlimit)
+{
+l_int32 w, h, i, j, wpls, wpld;
+l_int32 rval, gval, bval, minrg, min, maxrg, max, sat;
+l_uint32 *datas, *datad, *lines, *lined;
+l_float32 ratio;
+
+ PROCNAME("pixDarkenGray");
+
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+ if (thresh < 0 || thresh > 255)
+ return (PIX *)ERROR_PTR("invalid thresh", procName, NULL);
+ if (satlimit < 1)
+ return (PIX *)ERROR_PTR("invalid satlimit", procName, NULL);
+ if (pixd && (pixs != pixd))
+ return (PIX *)ERROR_PTR("not new or in-place", procName, NULL);
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+ if ((pixd = pixCopy(pixd, pixs)) == 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++) {
+ extractRGBValues(lines[j], &rval, &gval, &bval);
+ minrg = L_MIN(rval, gval);
+ min = L_MIN(minrg, bval);
+ maxrg = L_MAX(rval, gval);
+ max = L_MAX(maxrg, bval);
+ sat = max - min;
+ if (max >= thresh || sat >= satlimit)
+ continue;
+ ratio = (l_float32)sat / (l_float32)satlimit;
+ composeRGBPixel((l_int32)(ratio * rval), (l_int32)(ratio * gval),
+ (l_int32)(ratio * bval), &lined[j]);
+ }
+ }
+ return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ * General multiplicative constant color transform *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixMultConstantColor()
+ *
+ * \param[in] pixs colormapped or rgb
+ * \param[in] rfact red multiplicative factor
+ * \param[in] gfact green multiplicative factor
+ * \param[in] bfact blue multiplicative factor
+ * \return pixd colormapped or rgb, with colors scaled, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixMultMatrixColor()
+ *
+ * \param[in] pixs colormapped or rgb
+ * \param[in] kel kernel 3x3 matrix of floats
+ * \return pixd colormapped or rgb, or NULL on error
+ *
+ * <pre>
+ * 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].
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixHalfEdgeByBandpass()
+ *
+ * \param[in] pixs 8 bpp gray or 32 bpp rgb
+ * \param[in] sm1h, sm1v "half-widths" of smoothing filter sm1
+ * \param[in] sm2h, sm2v "half-widths" of smoothing filter sm2;
+ * require sm2 != sm1
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ pixDestroy(&pixg);
+ return (PIX *)ERROR_PTR("pixacc not made", procName, NULL);
+ }
+ if ((pixc1 = pixBlockconvGray(pixg, pixacc, sm1h, sm1v)) == NULL) {
+ pixDestroy(&pixg);
+ pixDestroy(&pixacc);
+ return (PIX *)ERROR_PTR("pixc1 not made", procName, NULL);
+ }
+ pixc2 = pixBlockconvGray(pixg, pixacc, sm2h, sm2v);
+ pixDestroy(&pixg);
+ pixDestroy(&pixacc);
+ if (!pixc2) {
+ pixDestroy(&pixc1);
+ return (PIX *)ERROR_PTR("pixc2 not made", procName, NULL);
+ }
+
+ /* Compute the half-edge using pixc1 - pixc2. */
+ pixSubtractGray(pixc1, pixc1, pixc2);
+ pixDestroy(&pixc2);
+ return pixc1;
+}
diff --git a/leptonica/src/environ.h b/leptonica/src/environ.h
new file mode 100644
index 00000000..96925053
--- /dev/null
+++ b/leptonica/src/environ.h
@@ -0,0 +1,586 @@
+/*====================================================================*
+ - 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 */
+
+#ifndef LEPT_DLL
+ /* 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 */
+ #define LEPT_DLL
+ #endif /* _WIN32 */
+#endif /* LEPT_DLL */
+
+#ifndef _WIN32 /* non-Windows specifics */
+ #include <stdint.h>
+#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) && !defined(OS_IOS)
+
+ #if !defined(HAVE_LIBJPEG)
+ #define HAVE_LIBJPEG 1
+ #endif
+ #if !defined(HAVE_LIBTIFF)
+ #define HAVE_LIBTIFF 1
+ #endif
+ #if !defined(HAVE_LIBPNG)
+ #define HAVE_LIBPNG 1
+ #endif
+ #if !defined(HAVE_LIBZ)
+ #define HAVE_LIBZ 1
+ #endif
+ #if !defined(HAVE_LIBGIF)
+ #define HAVE_LIBGIF 0
+ #endif
+ #if !defined(HAVE_LIBUNGIF)
+ #define HAVE_LIBUNGIF 0
+ #endif
+ #if !defined(HAVE_LIBWEBP)
+ #define HAVE_LIBWEBP 0
+ #endif
+ #if !defined(HAVE_LIBWEBP_ANIM)
+ #define HAVE_LIBWEBP_ANIM 0
+ #endif
+ #if !defined(HAVE_LIBJP2K)
+ #define HAVE_LIBJP2K 0
+ #endif
+
+
+ /*-----------------------------------------------------------------------*
+ * Leptonica supports OpenJPEG 2.0+. If you have a version of openjpeg *
+ * (HAVE_LIBJP2K == 1) that is >= 2.0, set the path to the openjpeg.h *
+ * header in angle brackets here. *
+ *-----------------------------------------------------------------------*/
+ #define LIBJP2K_HEADER <openjpeg-2.3/openjpeg.h>
+
+#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
+
+
+/*-------------------------------------------------------------------------*
+ * 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(OS_IOS) && \
+ !defined(_WIN32)
+#define HAVE_FMEMOPEN 1
+#endif /* ! HAVE_CONFIG_H etc. */
+
+/*-------------------------------------------------------------------------*
+ * fstatat() is defined by POSIX, but some systems do not support it. *
+ * One example is older macOS systems (pre-10.10). *
+ * Play it safe and set the default value to 0. *
+ *-------------------------------------------------------------------------*/
+#if !defined(HAVE_CONFIG_H)
+#define HAVE_FSTATAT 0
+#endif /* ! HAVE_CONFIG_H */
+
+/*--------------------------------------------------------------------*
+ * 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 int l_ok; /*!< return type 0 if OK, 1 on error */
+typedef signed char l_int8; /*!< signed 8-bit value */
+typedef unsigned char l_uint8; /*!< unsigned 8-bit value */
+typedef short l_int16; /*!< signed 16-bit value */
+typedef unsigned short l_uint16; /*!< unsigned 16-bit value */
+typedef int l_int32; /*!< signed 32-bit value */
+typedef unsigned int l_uint32; /*!< unsigned 32-bit value */
+typedef float l_float32; /*!< 32-bit floating point value */
+typedef double l_float64; /*!< 64-bit floating point value */
+#ifdef COMPILER_MSVC
+typedef __int64 l_int64; /*!< signed 64-bit value */
+typedef unsigned __int64 l_uint64; /*!< unsigned 64-bit value */
+#else
+typedef long long l_int64; /*!< signed 64-bit value */
+typedef unsigned long long l_uint64; /*!< unsigned 64-bit value */
+#endif /* COMPILER_MSVC */
+
+
+/*-------------------------------------------------------------------------*
+ * For security, the library is distributed in a configuration that does *
+ * not permit (1) forking with 'system', which is used for displaying *
+ * images and generating gnuplots, and (2) writing files with specified *
+ * compiled-in file names. All such writes are with functions such as *
+ * pixWriteDebug() where the "Debug" is appended to the usual name. *
+ * Whether the "Debug" version defaults to the standard version or is a *
+ * no-op depends on the value of this global variable. The default value *
+ * of LeptDebugOK is 0, and it is set in writefile.c. This value can be *
+ * over-ridden, for development and debugging, by setLeptDebugOK(). *
+ *-------------------------------------------------------------------------*/
+LEPT_DLL extern l_int32 LeptDebugOK; /* default is 0 */
+
+
+/*------------------------------------------------------------------------*
+ * Standard macros *
+ *------------------------------------------------------------------------*/
+#ifndef L_MIN
+/*! Minimum of %x and %y */
+#define L_MIN(x, y) (((x) < (y)) ? (x) : (y))
+#endif
+
+#ifndef L_MAX
+/*! Maximum of %x and %y */
+#define L_MAX(x, y) (((x) > (y)) ? (x) : (y))
+#endif
+
+#ifndef L_ABS
+/*! Absolute value of %x */
+#define L_ABS(x) (((x) < 0) ? (-1 * (x)) : (x))
+#endif
+
+#ifndef L_SIGN
+/*! Sign of %x */
+#define L_SIGN(x) (((x) < 0) ? -1 : 1)
+#endif
+
+#ifndef UNDEF
+/*! Undefined value */
+#define UNDEF -1
+#endif
+
+#ifndef NULL
+/*! NULL value */
+#define NULL 0
+#endif
+
+#ifndef TRUE
+/*! True value */
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+/*! False value */
+#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 *
+ *------------------------------------------------------------------------*/
+/*! Search State */
+enum {
+ L_NOT_FOUND = 0,
+ L_FOUND = 1
+};
+
+
+/*------------------------------------------------------------------------*
+ * Path separator conversion *
+ *------------------------------------------------------------------------*/
+/*! Path Separators */
+enum {
+ UNIX_PATH_SEPCHAR = 0,
+ WIN_PATH_SEPCHAR = 1
+};
+
+
+/*------------------------------------------------------------------------*
+ * Timing structs *
+ *------------------------------------------------------------------------*/
+typedef void *L_TIMER;
+
+/*! Timing struct */
+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 *
+ * *
+ * All default heap allocation is through the system malloc and free. *
+ * *
+ * Leptonica also provides non-default allocation in two situations: *
+ * *
+ * (1) A special allocator/deallocator pair can be provided for the *
+ * pix image data array. This might be useful to prevent memory *
+ * fragmentation when large images are repeatedly allocated and *
+ * freed. See the PixMemoryManager in pix1.c for details, *
+ * where the default is defined. *
+ * *
+ * (2) Special allocator/deallocators can be provided for ALL heap *
+ * allocation if required, for example, for embedded systems. *
+ * For such builds, define LEPTONICA_INTERCEPT_ALLOC, and provide *
+ * custom leptonica_{malloc, calloc, realloc, free} functions. *
+ *------------------------------------------------------------------------*/
+#ifdef LEPTONICA_INTERCEPT_ALLOC
+ #define LEPT_MALLOC(blocksize) leptonica_malloc(blocksize)
+ #define LEPT_CALLOC(numelem, elemsize) leptonica_calloc(numelem, elemsize)
+ #define LEPT_REALLOC(ptr, blocksize) leptonica_realloc(ptr, blocksize)
+ #define LEPT_FREE(ptr) leptonica_free(ptr)
+ void *leptonica_malloc(size_t blocksize);
+ void *leptonica_calloc(size_t numelem, size_t elemsize);
+ void *leptonica_realloc(void *ptr, size_t blocksize);
+ void leptonica_free(void *ptr);
+#else
+ #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)
+#endif /* LEPTONICA_INTERCEPT_ALLOC */
+
+/*------------------------------------------------------------------------*
+ * Control printing of error, warning, and info messages *
+ * *
+ * Leptonica never sends output to stdout. By default, all messages *
+ * go to stderr. However, we provide a mechanism for runtime *
+ * redirection of output, using a custom stderr handler defined *
+ * by the user. See utils1.c for details and examples. *
+ * *
+ * 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." *
+ *------------------------------------------------------------------------*/
+
+ /*! Control printing of error, warning and info messages */
+/*! Message Control */
+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 ERROR 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.
+ *
+ * This is a complex mechanism and a few examples may help.
+ * (1) No output permitted under any circumstances.
+ * Use: -DNO_CONSOLE_IO or -DMINIMUM_SEVERITY=6
+ * (2) Suppose you want to only allow error messages, and you don't
+ * want to permit info or warning messages at runtime.
+ * Use: -DMINIMUM_SEVERITY=5
+ * (3) Suppose you want to only allow error messages by default,
+ * but you will permit this to be over-ridden at runtime.
+ * Use: -DDEFAULT_SEVERITY=5
+ * and to allow info and warning override:
+ * setMsgSeverity(L_SEVERITY_INFO);
+ */
+
+#ifdef NO_CONSOLE_IO
+ #undef MINIMUM_SEVERITY
+ #undef DEFAULT_SEVERITY
+
+ #define MINIMUM_SEVERITY L_SEVERITY_NONE /*!< Compile-time default */
+ #define DEFAULT_SEVERITY L_SEVERITY_NONE /*!< Run-time default */
+
+#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 utils1.c. */
+LEPT_DLL extern l_int32 LeptMsgSeverity;
+
+/*
+ * <pre>
+ * 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.
+ * </pre>
+ */
+
+#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)lept_stderr("Error in %s: " a, __VA_ARGS__), \
+ (void)0)
+ #define L_WARNING(a, ...) \
+ IF_SEV(L_SEVERITY_WARNING, \
+ (void)lept_stderr("Warning in %s: " a, __VA_ARGS__), \
+ (void)0)
+ #define L_INFO(a, ...) \
+ IF_SEV(L_SEVERITY_INFO, \
+ (void)lept_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) \
+ lept_stderr("Error in %s: " a, __VA_ARGS__) \
+ }
+ #define L_WARNING(a, ...) \
+ { if (L_SEVERITY_WARNING >= MINIMUM_SEVERITY && \
+ L_SEVERITY_WARNING >= LeptMsgSeverity) \
+ lept_stderr("Warning in %s: " a, __VA_ARGS__) \
+ }
+ #define L_INFO(a, ...) \
+ { if (L_SEVERITY_INFO >= MINIMUM_SEVERITY && \
+ L_SEVERITY_INFO >= LeptMsgSeverity) \
+ lept_stderr("Info in %s: " a, __VA_ARGS__) \
+ }
+#endif
+
+#endif /* NO_CONSOLE_IO */
+
+
+/*------------------------------------------------------------------------*
+ * snprintf() renamed in MSVC (pre-VS2015) *
+ *------------------------------------------------------------------------*/
+#if defined _MSC_VER && _MSC_VER < 1900
+#define snprintf(buf, size, ...) _snprintf_s(buf, size, _TRUNCATE, __VA_ARGS__)
+#endif
+
+
+#endif /* LEPTONICA_ENVIRON_H */
diff --git a/leptonica/src/fhmtauto.c b/leptonica/src/fhmtauto.c
new file mode 100644
index 00000000..dc1802e8
--- /dev/null
+++ b/leptonica/src/fhmtauto.c
@@ -0,0 +1,823 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file fhmtauto.c
+ * <pre>
+ *
+ * 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 single high-level interface for the hmt, and
+ * the lower-level functions 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);
+ *
+ * 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.
+ *
+ * N.B. Although pixFHMTGen_1() is global, you must NOT
+ * use it, because it assumes that 32 or 64 border pixels
+ * have been added to each side, and it will crash without those
+ * added pixels.
+
+ * As an example, see the file prog/fhmtauto_reg.c, which
+ * verifies the correctness of the implementation by
+ * comparing the dwa result with that of full-image
+ * rasterops.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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);"
+
+#define 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"};
+
+
+/*!
+ * \brief fhmtautogen()
+ *
+ * \param[in] sela
+ * \param[in] fileindex
+ * \param[in] filename [optional]; can be null
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fhmtautogen1()
+ *
+ * \param[in] sela array
+ * \param[in] fileindex
+ * \param[in] filename [optional]; can be null
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 textlines from from hmttemplate1.txt */
+ if ((filestr = (char *)l_binaryRead(TEMPLATE1, &size)) == NULL)
+ return ERROR_INT("filestr not made", procName, 1);
+ sa2 = sarrayCreateLinesFromString(filestr, 1);
+ LEPT_FREE(filestr);
+ if (!sa2)
+ return ERROR_INT("sa2 not made", procName, 1);
+
+ /* Make array of sel names */
+ sa1 = selaGetSelnames(sela);
+
+ /* 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, " * \\brief pixHMTDwa_%d()", fileindex);
+ str_doc3 = stringNew(bigbuf);
+ sprintf(bigbuf, " * \\brief 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 */
+ sa3 = sarrayCreate(0);
+
+ /* 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);
+
+ filestr = sarrayToString(sa3, 1);
+ nbytes = strlen(filestr);
+ if (filename)
+ snprintf(bigbuf, L_BUF_SIZE, "%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;
+}
+
+
+/*!
+ * \brief fhmtautogen2()
+ *
+ * \param[in] sela array
+ * \param[in] fileindex
+ * \param[in] filename [optional]; can be null
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+ sa1 = sarrayCreateLinesFromString(filestr, 1);
+ LEPT_FREE(filestr);
+ if (!sa1)
+ return ERROR_INT("sa1 not made", procName, 1);
+
+ /* Make the array of static function names */
+ if ((sa2 = sarrayCreate(nsels)) == NULL) {
+ sarrayDestroy(&sa1);
+ 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 */
+ sa3 = sarrayCreate(2 * nsels); /* should be ok */
+ 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);
+
+ /* Output to this sa */
+ sa4 = sarrayCreate(0);
+
+ /* 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) {
+ sarrayDestroy(&sa1);
+ sarrayDestroy(&sa2);
+ sarrayDestroy(&sa3);
+ sarrayDestroy(&sa4);
+ return ERROR_INT("linestr not retrieved", procName, 1);
+ }
+ sarrayAddString(sa4, linestr, L_INSERT);
+ }
+
+ /* Make more strings containing function names */
+ sprintf(bigbuf, " * fhmtgen_low_%d()", fileindex);
+ str_doc3 = stringNew(bigbuf);
+ sprintf(bigbuf, "fhmtgen_low_%d(l_uint32 *datad,", fileindex);
+ str_def1 = stringNew(bigbuf);
+
+ /* 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) {
+ sarrayDestroy(&sa1);
+ sarrayDestroy(&sa2);
+ sarrayDestroy(&sa3);
+ sarrayDestroy(&sa4);
+ return ERROR_INT("sel not returned", procName, 1);
+ }
+ sa5 = sarrayMakeWplsCode(sel);
+ 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(" "
+ "lept_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) {
+ sarrayDestroy(&sa1);
+ sarrayDestroy(&sa2);
+ sarrayDestroy(&sa3);
+ sarrayDestroy(&sa4);
+ 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 */
+ filestr = sarrayToString(sa4, 1);
+ nbytes = strlen(filestr);
+ if (filename)
+ snprintf(bigbuf, L_BUF_SIZE, "%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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief 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;
+ }
+
+ sa = sarrayCreate(0);
+
+ /* 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;
+}
+
+
+/*!
+ * \brief 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);
+
+ sa = sarrayCreate(0);
+ 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;
+}
+
+
+/*!
+ * \brief 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/leptonica/src/fhmtgen.1.c b/leptonica/src/fhmtgen.1.c
new file mode 100644
index 00000000..8a1fcab8
--- /dev/null
+++ b/leptonica/src/fhmtgen.1.c
@@ -0,0 +1,177 @@
+/*====================================================================*
+ - 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"};
+
+/*!
+ * \brief pixHMTDwa_1()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * Notes:
+ * (1) This simply adds a 32 pixel border, calls the appropriate
+ * pixFHMTGen_*(), and removes the border.
+ * See notes below for that function.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFHMTGen_1()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/fhmtgenlow.1.c b/leptonica/src/fhmtgenlow.1.c
new file mode 100644
index 00000000..b1c863cf
--- /dev/null
+++ b/leptonica/src/fhmtgenlow.1.c
@@ -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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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/leptonica/src/finditalic.c b/leptonica/src/finditalic.c
new file mode 100644
index 00000000..adcd0e77
--- /dev/null
+++ b/leptonica/src/finditalic.c
@@ -0,0 +1,241 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*
+ * \file finditalic.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 ";
+
+/*!
+ * \brief pixItalicWords()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] boxaw [optional] word bounding boxes; can be NULL
+ * \param[in] pixw [optional] word box mask; can be NULL
+ * \param[out] pboxa boxa of italic words
+ * \param[in] debugflag 1 for debug output; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 (!pboxa)
+ return ERROR_INT("&boxa not defined", procName, 1);
+ *pboxa = NULL;
+ if (!pixs)
+ return ERROR_INT("pixs 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. */
+ size = 0; /* init */
+ if (boxaw) {
+ pixm = pixCreateTemplate(pixs);
+ pixMaskBoxa(pixm, pixm, boxaw, L_SET_PIXELS);
+ } else if (pixw) {
+ pixm = pixClone(pixw);
+ } else {
+ pixWordMaskByDilation(pixs, NULL, &size, NULL);
+ 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;
+ lept_mkdir("lept/ital");
+ BOXA *boxat;
+ GPLOT *gplot;
+ NUMA *na;
+ PIXA *pixa1;
+ PIX *pix1, *pix2, *pix3;
+ pixa1 = pixaCreate(0);
+ boxat = pixConnComp(pixm, NULL, 8);
+ boxaWriteDebug("/tmp/lept/ital/ital.ba", boxat);
+ pixaAddPix(pixa1, pixs, L_COPY); /* orig */
+ pixaAddPix(pixa1, pixsd, L_COPY); /* seed */
+ pix1 = pixConvertTo32(pixm);
+ pixRenderBoxaArb(pix1, boxat, 3, 255, 0, 0);
+ pixaAddPix(pixa1, pix1, L_INSERT); /* mask + outline */
+ pixaAddPix(pixa1, pixd, L_COPY); /* ital mask */
+ pix1 = pixConvertTo32(pixs);
+ pixRenderBoxaArb(pix1, boxa, 3, 255, 0, 0);
+ pixaAddPix(pixa1, pix1, L_INSERT); /* orig + outline */
+ pix1 = pixCreateTemplate(pixs);
+ pix2 = pixSetBlackOrWhiteBoxa(pix1, boxa, L_SET_BLACK);
+ pixCopy(pix1, pixs);
+ pix3 = pixDilateBrick(NULL, pixs, 3, 3);
+ pixCombineMasked(pix1, pix3, pix2);
+ pixaAddPix(pixa1, pix1, L_INSERT); /* ital bolded */
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pix2 = pixaDisplayTiledInColumns(pixa1, 1, 0.5, 20, 2);
+ pixWriteDebug("/tmp/lept/ital/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);
+ l_pdfSetDateAndVersion(0);
+ pixaConvertToPdf(pixa1, res, 1.0, L_FLATE_ENCODE, 75, "Italic Finder",
+ "/tmp/lept/ital/ital.pdf");
+ l_pdfSetDateAndVersion(1);
+ pixaDestroy(&pixa1);
+ 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/lept/ital/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/leptonica/src/flipdetect.c b/leptonica/src/flipdetect.c
new file mode 100644
index 00000000..e2131de8
--- /dev/null
+++ b/leptonica/src/flipdetect.c
@@ -0,0 +1,821 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file flipdetect.c
+ * <pre>
+ *
+ * High-level interface for detection and correction
+ * PIX *pixOrientCorrect()
+ *
+ * Page orientation detection (pure rotation by 90 degree increments):
+ * l_int32 pixOrientDetect()
+ * l_int32 makeOrientDecision()
+ * l_int32 pixUpDownDetect()
+ *
+ * Page mirror detection (flip 180 degrees about line in plane of image):
+ * l_int32 pixMirrorDetect()
+ *
+ * Static debug helper
+ * static 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().
+ *
+ * (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,
+ * use the slower pixUpDownDetect().
+ *
+ * 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.
+ *
+ * A high-level interface, pixOrientCorrect() combines the detection
+ * of the orientation with the rotation decision and the rotation itself.
+ *
+ * Finally, use can be made of programs such as exiftool and convert to
+ * read exif camera orientation data in jpeg files and conditionally rotate.
+ * Here is an example shell script, made by Dan9er:
+ * ==================================================================
+ * #!/bin/sh
+ * # orientByExif.sh
+ * # Dependencies: exiftool (exiflib) and convert (ImageMagick)
+ * # Note: if there is no exif orientation data in the jpeg file,
+ * # this simply copies the input file.
+ * #
+ * if [[ -z $(command -v exiftool) || -z $(command -v convert) ]]; then
+ * echo "You need to install dependencies; e.g.:"
+ * echo " sudo apt install libimage-exiftool-perl"
+ * echo " sudo apt install imagemagick"
+ * exit 1
+ * fi
+ * if [[ $# != 2 ]]; then
+ * echo "Syntax: orientByExif infile outfile"
+ * exit 2
+ * fi
+ * if [[ ${1: -4} != ".jpg" ]]; then
+ * echo "File is not a jpeg"
+ * exit 3
+ * fi
+ * if [[ $(exiftool -s3 -n -Orientation "$1") = 1 ]]; then
+ * echo "Image is already upright"
+ * exit 0
+ * fi
+ * convert "$1" -auto-orient "$2"
+ * echo "Done"
+ * exit 0
+ * ==================================================================
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 DefaultMinUpDownCount = 70;
+static const l_float32 DefaultMinUpDownConf = 8.0;
+static const l_float32 DefaultMinUpDownRatio = 2.5;
+
+ /* Parameters for determining mirror flip */
+static const l_int32 DefaultMinMirrorFlipCount = 100;
+static const l_float32 DefaultMinMirrorFlipConf = 5.0;
+
+ /* Static debug function */
+static void pixDebugFlipDetect(const char *filename, PIX *pixs,
+ PIX *pixhm, l_int32 enable);
+
+
+/*----------------------------------------------------------------*
+ * High-level interface for detection and correction *
+ *----------------------------------------------------------------*/
+/*!
+ * \brief pixOrientCorrect()
+ *
+ * \param[in] pixs 1 bpp, deskewed, English text, 150 - 300 ppi
+ * \param[in] minupconf minimum value for which a decision can be made
+ * \param[in] minratio minimum conf ratio required for a decision
+ * \param[out] pupconf [optional] ; use NULL to skip
+ * \param[out] pleftconf [optional] ; use NULL to skip
+ * \param[out] protation [optional] ; use NULL to skip
+ * \param[in] debug 1 for debug output; 0 otherwise
+ * \return pixd may be rotated by 90, 180 or 270; null on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Simple top-level function to detect if Roman text is in
+ * reading orientation, and to rotate the image accordingly if not.
+ * (2) Returns a copy if no rotation is needed.
+ * (3) See notes for pixOrientDetect() and pixOrientDecision().
+ * Use 0.0 for default values for %minupconf and %minratio
+ * (4) Optional output of intermediate confidence results and
+ * the rotation performed on pixs.
+ * </pre>
+ */
+PIX *
+pixOrientCorrect(PIX *pixs,
+ l_float32 minupconf,
+ l_float32 minratio,
+ l_float32 *pupconf,
+ l_float32 *pleftconf,
+ l_int32 *protation,
+ l_int32 debug)
+{
+l_int32 orient;
+l_float32 upconf, leftconf;
+PIX *pix1;
+
+ PROCNAME("pixOrientCorrect");
+
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+
+ /* Get confidences for orientation */
+ pixUpDownDetect(pixs, &upconf, 0, 0, debug);
+ pix1 = pixRotate90(pixs, 1);
+ pixUpDownDetect(pix1, &leftconf, 0, 0, debug);
+ pixDestroy(&pix1);
+ if (pupconf) *pupconf = upconf;
+ if (pleftconf) *pleftconf = leftconf;
+
+ /* Decide what to do */
+ makeOrientDecision(upconf,leftconf, minupconf, minratio, &orient, debug);
+
+ /* Do it */
+ switch (orient)
+ {
+ case L_TEXT_ORIENT_UNKNOWN:
+ L_INFO("text orientation not determined; no rotation\n", procName);
+ if (protation) *protation = 0;
+ return pixCopy(NULL, pixs);
+ break;
+ case L_TEXT_ORIENT_UP:
+ L_INFO("text is oriented up; no rotation\n", procName);
+ if (protation) *protation = 0;
+ return pixCopy(NULL, pixs);
+ break;
+ case L_TEXT_ORIENT_LEFT:
+ L_INFO("landscape; text oriented left; 90 cw rotation\n", procName);
+ if (protation) *protation = 90;
+ return pixRotateOrth(pixs, 1);
+ break;
+ case L_TEXT_ORIENT_DOWN:
+ L_INFO("text oriented down; 180 cw rotation\n", procName);
+ if (protation) *protation = 180;
+ return pixRotateOrth(pixs, 2);
+ break;
+ case L_TEXT_ORIENT_RIGHT:
+ L_INFO("landscape; text oriented right; 270 cw rotation\n", procName);
+ if (protation) *protation = 270;
+ return pixRotateOrth(pixs, 3);
+ break;
+ default:
+ L_ERROR("invalid orient flag!\n", procName);
+ return pixCopy(NULL, pixs);
+ }
+}
+
+
+/*----------------------------------------------------------------*
+ * Orientation detection (four 90 degree angles) *
+ *----------------------------------------------------------------*/
+/*!
+ * \brief pixOrientDetect()
+ *
+ * \param[in] pixs 1 bpp, deskewed, English text, 150 - 300 ppi
+ * \param[out] pupconf [optional] ; may be NULL
+ * \param[out] pleftconf [optional] ; may be NULL
+ * \param[in] mincount min number of up + down; use 0 for default
+ * \param[in] debug 1 for debug output; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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) This algorithm will fail on some images, such as tables,
+ * where most of the characters are numbers and appear as
+ * uppercase, but there are some repeated words that give a
+ * biased signal. It may be advisable to run a table detector
+ * first (e.g., pixDecideIfTable()), and not run the orientation
+ * detector if it is a table.
+ * (8) Uses rasterop implementation of HMT.
+ * </pre>
+ */
+l_ok
+pixOrientDetect(PIX *pixs,
+ l_float32 *pupconf,
+ l_float32 *pleftconf,
+ l_int32 mincount,
+ l_int32 debug)
+{
+PIX *pix1;
+
+ PROCNAME("pixOrientDetect");
+
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+ if (!pupconf && !pleftconf)
+ return ERROR_INT("nothing to do", procName, 1);
+ if (mincount == 0)
+ mincount = DefaultMinUpDownCount;
+
+ if (pupconf)
+ pixUpDownDetect(pixs, pupconf, mincount, 0, debug);
+ if (pleftconf) {
+ pix1 = pixRotate90(pixs, 1);
+ pixUpDownDetect(pix1, pleftconf, mincount, 0, debug);
+ pixDestroy(&pix1);
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief makeOrientDecision()
+ *
+ * \param[in] upconf nonzero
+ * \param[in] leftconf nonzero
+ * \param[in] minupconf minimum value for which a decision can be made
+ * \param[in] minratio minimum conf ratio required for a decision
+ * \param[out] porient text orientation enum {0,1,2,3,4}
+ * \param[in] debug 1 for debug output; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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) {
+ L_INFO("not enough confidence to get orientation\n", procName);
+ return 0;
+ }
+
+ if (minupconf == 0.0)
+ minupconf = DefaultMinUpDownConf;
+ if (minratio == 0.0)
+ minratio = DefaultMinUpDownRatio;
+ 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) {
+ lept_stderr("upconf = %7.3f, leftconf = %7.3f\n", upconf, leftconf);
+ if (*porient == L_TEXT_ORIENT_UNKNOWN)
+ lept_stderr("Confidence is low; no determination is made\n");
+ else if (*porient == L_TEXT_ORIENT_UP)
+ lept_stderr("Text is rightside-up\n");
+ else if (*porient == L_TEXT_ORIENT_LEFT)
+ lept_stderr("Text is rotated 90 deg ccw\n");
+ else if (*porient == L_TEXT_ORIENT_DOWN)
+ lept_stderr("Text is upside-down\n");
+ else /* *porient == L_TEXT_ORIENT_RIGHT */
+ lept_stderr("Text is rotated 90 deg cw\n");
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixUpDownDetect()
+ *
+ * \param[in] pixs 1 bpp, deskewed, English text, 150 - 300 ppi
+ * \param[out] pconf confidence that text is rightside-up
+ * \param[in] mincount min number of up + down; use 0 for default
+ * \param[in] npixels number of pixels removed from each side of word box
+ * \param[in] debug 1 for debug output; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixOrientDetect() for other details.
+ * (2) The detected confidence %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.
+ * </pre>
+ */
+l_ok
+pixUpDownDetect(PIX *pixs,
+ l_float32 *pconf,
+ l_int32 mincount,
+ l_int32 npixels,
+ l_int32 debug)
+{
+l_int32 countup, countdown, nmax;
+l_float32 nup, ndown;
+PIX *pix0, *pix1, *pix2, *pix3, *pixm;
+SEL *sel1, *sel2, *sel3, *sel4;
+
+ PROCNAME("pixUpDownDetect");
+
+ if (!pconf)
+ return ERROR_INT("&conf not defined", procName, 1);
+ *pconf = 0.0;
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+ if (mincount == 0)
+ mincount = DefaultMinUpDownCount;
+ if (npixels < 0)
+ npixels = 0;
+
+ if (debug) {
+ lept_mkdir("lept/orient");
+ }
+
+ 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. */
+ pix0 = 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;
+ pix1 = pixMorphSequence(pix0, "o10.1", 0);
+ boxa = pixConnComp(pix1, NULL, 8);
+ pixm = pixCreateTemplate(pix1);
+ pixDestroy(&pix1);
+ 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. */
+ pix1 = pixHMT(NULL, pix0, sel1);
+ pix2 = pixHMT(NULL, pix0, sel2);
+ pixOr(pix1, pix1, pix2);
+ if (pixm)
+ pixAnd(pix1, pix1, pixm);
+ pix3 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0);
+ pixCountPixels(pix3, &countup, NULL);
+ pixDebugFlipDetect("/tmp/lept/orient/up.png", pixs, pix1, debug);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Find the ascenders and optionally filter with pixm. */
+ pix1 = pixHMT(NULL, pix0, sel3);
+ pix2 = pixHMT(NULL, pix0, sel4);
+ pixOr(pix1, pix1, pix2);
+ if (pixm)
+ pixAnd(pix1, pix1, pixm);
+ pix3 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0);
+ pixCountPixels(pix3, &countdown, NULL);
+ pixDebugFlipDetect("/tmp/lept/orient/down.png", pixs, pix1, debug);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* 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) pixWriteDebug("/tmp/lept/orient/pixm1.png", pixm, IFF_PNG);
+ lept_stderr("nup = %7.3f, ndown = %7.3f, conf = %7.3f\n",
+ nup, ndown, *pconf);
+ if (*pconf > DefaultMinUpDownConf)
+ lept_stderr("Text is rightside-up\n");
+ if (*pconf < -DefaultMinUpDownConf)
+ lept_stderr("Text is upside-down\n");
+ }
+
+ pixDestroy(&pix0);
+ pixDestroy(&pixm);
+ selDestroy(&sel1);
+ selDestroy(&sel2);
+ selDestroy(&sel3);
+ selDestroy(&sel4);
+ return 0;
+}
+
+
+/*----------------------------------------------------------------*
+ * Left-right mirror detection *
+ *----------------------------------------------------------------*/
+/*!
+ * \brief pixMirrorDetect()
+ *
+ * \param[in] pixs 1 bpp, deskewed, English text
+ * \param[out] pconf confidence that text is not LR mirror reversed
+ * \param[in] mincount min number of left + right; use 0 for default
+ * \param[in] debug 1 for debug output; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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!
+ * </pre>
+ */
+l_ok
+pixMirrorDetect(PIX *pixs,
+ l_float32 *pconf,
+ l_int32 mincount,
+ l_int32 debug)
+{
+l_int32 count1, count2, nmax;
+l_float32 nleft, nright;
+PIX *pix0, *pix1, *pix2, *pix3;
+SEL *sel1, *sel2;
+
+ PROCNAME("pixMirrorDetect");
+
+ if (!pconf)
+ return ERROR_INT("&conf not defined", procName, 1);
+ *pconf = 0.0;
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+ if (mincount == 0)
+ mincount = DefaultMinMirrorFlipCount;
+
+ if (debug) {
+ lept_mkdir("lept/orient");
+ }
+
+ sel1 = selCreateFromString(textsel1, 5, 6, NULL);
+ sel2 = selCreateFromString(textsel2, 5, 6, NULL);
+
+ /* Fill x-height characters but not space between them, sort of. */
+ pix3 = pixMorphCompSequence(pixs, "d1.30", 0);
+ pixXor(pix3, pix3, pixs);
+ pix0 = pixMorphCompSequence(pixs, "c15.1", 0);
+ pixXor(pix0, pix0, pixs);
+ pixAnd(pix0, pix0, pix3);
+ pixOr(pix0, pix0, pixs);
+ pixDestroy(&pix3);
+
+ /* Filter the right-facing characters. */
+ pix1 = pixHMT(NULL, pix0, sel1);
+ pix3 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0);
+ pixCountPixels(pix3, &count1, NULL);
+ pixDebugFlipDetect("/tmp/lept/orient/right.png", pixs, pix1, debug);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+
+ /* Filter the left-facing characters. */
+ pix2 = pixHMT(NULL, pix0, sel2);
+ pix3 = pixReduceRankBinaryCascade(pix2, 1, 1, 0, 0);
+ pixCountPixels(pix3, &count2, NULL);
+ pixDebugFlipDetect("/tmp/lept/orient/left.png", pixs, pix2, debug);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ nright = (l_float32)count1;
+ nleft = (l_float32)count2;
+ nmax = L_MAX(count1, count2);
+ pixDestroy(&pix0);
+ selDestroy(&sel1);
+ selDestroy(&sel2);
+
+ if (nmax > mincount)
+ *pconf = 2. * ((nright - nleft) / sqrt(nright + nleft));
+
+ if (debug) {
+ lept_stderr("nright = %f, nleft = %f\n", nright, nleft);
+ if (*pconf > DefaultMinMirrorFlipConf)
+ lept_stderr("Text is not mirror reversed\n");
+ if (*pconf < -DefaultMinMirrorFlipConf)
+ lept_stderr("Text is mirror reversed\n");
+ }
+
+ return 0;
+}
+
+
+/*----------------------------------------------------------------*
+ * Static debug helper *
+ *----------------------------------------------------------------*/
+/*
+ * \brief pixDebugFlipDetect()
+ *
+ * \param[in] filename for output debug file
+ * \param[in] pixs input to pix*Detect
+ * \param[in] pixhm hit-miss result from ascenders or descenders
+ * \param[in] 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);
+
+ pixWriteDebug(filename, pixt, IFF_PNG);
+ pixDestroy(&pixthm);
+ pixDestroy(&pixt);
+ return;
+}
diff --git a/leptonica/src/flipdetectdwa.c.notused b/leptonica/src/flipdetectdwa.c.notused
new file mode 100644
index 00000000..c0727583
--- /dev/null
+++ b/leptonica/src/flipdetectdwa.c.notused
@@ -0,0 +1,668 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file flipdetectdwa.c
+ * <pre>
+ *
+ * NOTE
+ * ==============================================================
+ * This code has been retired from the library. It is faster than
+ * the rasterop implementation, but not by a large amount. On a
+ * standard 8 Mpixel page image, the timings are:
+ * Rasterop: 0.37 sec
+ * DWA: 0.21 sec.
+ * It is preserved here to show how the dwa code can be generated
+ * and then used in an application.
+ * ==============================================================
+ *
+ * DWA page orientation detection (pure rotation by 90 degree increments):
+ * l_int32 pixOrientDetectDwa()
+ * l_int32 pixUpDownDetectDwa()
+ *
+ * DWA mirror detection (flip 180 degrees about line in plane of image):
+ * l_int32 pixMirrorDetectDwa()
+ *
+ * Low-level auto-generated DWA implementation of hit-miss transforms.
+ * This code (rearranged) was generated by prog/flipselgen.c.
+ * static 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 four Sels used are defined in prog/flipselgen.c.
+ * They are the same as those in flipdetect.c.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include <string.h>
+#include "allheaders.h"
+
+ /* Parameters for determining orientation */
+static const l_int32 DefaultMinUpDownCount = 70;
+static const l_float32 DefaultMinUpDownConf = 8.0;
+
+ /* Parameters for determining mirror flip */
+static const l_int32 DefaultMinMirrorFlipCount = 100;
+static const l_float32 DefaultMinMirrorFlipConf = 5.0;
+
+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);
+
+/*----------------------------------------------------------------*
+ * High-level interface for orientation detection *
+ * (four 90 degree angles) *
+ *----------------------------------------------------------------*/
+/*!
+ * \brief pixOrientDetectDwa()
+ *
+ * \param[in] pixs 1 bpp, deskewed, English text
+ * \param[out] pupconf [optional] ; may be NULL
+ * \param[out] pleftconf [optional] ; may be NULL
+ * \param[in] mincount min number of up + down; use 0 for default
+ * \param[in] debug 1 for debug output; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 1.5 times faster than the pixOrientDetect().
+ * </pre>
+ */
+l_ok
+pixOrientDetectDwa(PIX *pixs,
+ l_float32 *pupconf,
+ l_float32 *pleftconf,
+ l_int32 mincount,
+ l_int32 debug)
+{
+PIX *pix1;
+
+ PROCNAME("pixOrientDetectDwa");
+
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+ if (!pupconf && !pleftconf)
+ return ERROR_INT("nothing to do", procName, 1);
+ if (mincount == 0)
+ mincount = DefaultMinUpDownCount;
+
+ if (pupconf)
+ pixUpDownDetectDwa(pixs, pupconf, mincount, 0, debug);
+ if (pleftconf) {
+ pix1 = pixRotate90(pixs, 1);
+ pixUpDownDetectDwa(pix1, pleftconf, mincount, 0, debug);
+ pixDestroy(&pix1);
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixUpDownDetectDwa()
+ *
+ * \param[in] pixs 1 bpp, deskewed, English text, 150 - 300 ppi
+ * \param[out] pconf confidence that text is rightside-up
+ * \param[in] mincount min number of up + down; use 0 for default
+ * \param[in] npixels number of pixels removed from each side of word box
+ * \param[in] debug 1 for debug output; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Faster (DWA) version of pixUpDownDetect().
+ * (2) If npixels == 0, the pixels identified through the HMT
+ * (hit-miss transform) are not clipped by a truncated word
+ * mask pixm. See pixUpDownDetect() 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.
+ * </pre>
+ */
+l_ok
+pixUpDownDetectDwa(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, *pix0, *pix1, *pix2, *pix3, *pixm;
+
+ PROCNAME("pixUpDownDetectDwa");
+
+ if (!pconf)
+ return ERROR_INT("&conf not defined", procName, 1);
+ *pconf = 0.0;
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+ if (mincount == 0)
+ mincount = DefaultMinUpDownCount;
+ 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! */
+ pix0 = 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;
+ pix1 = pixMorphSequenceDwa(pix0, "o10.1", 0);
+ boxa = pixConnComp(pix1, NULL, 8);
+ pixm = pixCreateTemplate(pix1);
+ pixDestroy(&pix1);
+ 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 pixUpDownDetect(). */
+ pix1 = pixFlipFHMTGen(NULL, pix0, flipsel1);
+ pix2 = pixFlipFHMTGen(NULL, pix0, flipsel2);
+ pixOr(pix1, pix1, pix2);
+ if (pixm)
+ pixAnd(pix1, pix1, pixm);
+ pix3 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0);
+ pixCountPixels(pix3, &countup, NULL);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* Find the ascenders and optionally filter with pixm. */
+ pix1 = pixFlipFHMTGen(NULL, pix0, flipsel3);
+ pix2 = pixFlipFHMTGen(NULL, pix0, flipsel4);
+ pixOr(pix1, pix1, pix2);
+ if (pixm)
+ pixAnd(pix1, pix1, pixm);
+ pix3 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0);
+ pixCountPixels(pix3, &countdown, NULL);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ /* 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) {
+ lept_mkdir("lept/orient");
+ pixWriteDebug("/tmp/lept/orient/pixm2.png", pixm, IFF_PNG);
+ }
+ lept_stderr("nup = %7.3f, ndown = %7.3f, conf = %7.3f\n",
+ nup, ndown, *pconf);
+ if (*pconf > DefaultMinUpDownConf)
+ lept_stderr("Text is rightside-up\n");
+ if (*pconf < -DefaultMinUpDownConf)
+ lept_stderr("Text is upside-down\n");
+ }
+
+ pixDestroy(&pix0);
+ pixDestroy(&pixm);
+ return 0;
+}
+
+
+/*----------------------------------------------------------------*
+ * Left-right mirror detection *
+ * DWA implementation *
+ *----------------------------------------------------------------*/
+/*!
+ * \brief pixMirrorDetectDwa()
+ *
+ * \param[in] pixs 1 bpp, deskewed, English text
+ * \param[out] pconf confidence that text is not LR mirror reversed
+ * \param[in] mincount min number of left + right; use 0 for default
+ * \param[in] debug 1 for debug output; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) We assume the text is horizontally oriented, with
+ * ascenders going up.
+ * (2) See notes in pixMirrorDetect().
+ * </pre>
+ */
+l_ok
+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 *pix0, *pix1, *pix2, *pix3;
+
+ PROCNAME("pixMirrorDetectDwa");
+
+ if (!pconf)
+ return ERROR_INT("&conf not defined", procName, 1);
+ *pconf = 0.0;
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+ if (mincount == 0)
+ mincount = DefaultMinMirrorFlipCount;
+
+ /* Fill x-height characters but not space between them, sort of. */
+ pix3 = pixMorphSequenceDwa(pixs, "d1.30", 0);
+ pixXor(pix3, pix3, pixs);
+ pix0 = pixMorphSequenceDwa(pixs, "c15.1", 0);
+ pixXor(pix0, pix0, pixs);
+ pixAnd(pix0, pix0, pix3);
+ pixOr(pix3, pix0, pixs);
+ pixDestroy(&pix0);
+ pix0 = pixAddBorderGeneral(pix3, ADDED_BORDER, ADDED_BORDER,
+ ADDED_BORDER, ADDED_BORDER, 0);
+ pixDestroy(&pix3);
+
+ /* Filter the right-facing characters. */
+ pix1 = pixFlipFHMTGen(NULL, pix0, flipsel1);
+ pix3 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0);
+ pixCountPixels(pix3, &count1, NULL);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+
+ /* Filter the left-facing characters. */
+ pix2 = pixFlipFHMTGen(NULL, pix0, flipsel2);
+ pix3 = pixReduceRankBinaryCascade(pix2, 1, 1, 0, 0);
+ pixCountPixels(pix3, &count2, NULL);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+
+ pixDestroy(&pix0);
+ 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) {
+ lept_stderr("nright = %f, nleft = %f\n", nright, nleft);
+ if (*pconf > DefaultMinMirrorFlipConf)
+ lept_stderr("Text is not mirror reversed\n");
+ if (*pconf < -DefaultMinMirrorFlipConf)
+ lept_stderr("Text is mirror reversed\n");
+ }
+
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Auto-generated hmt code *
+ *---------------------------------------------------------------------*/
+/*
+ * pixFlipFHMTGen()
+ *
+ * Input: pixd (usual 3 choices: null, == pixs, != pixs)
+ * pixs
+ * sel name (one of four defined in SEL_NAMES[])
+ * Return: pixd
+ *
+ * Notes:
+ * 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,
+ const 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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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/leptonica/src/fmorphauto.c b/leptonica/src/fmorphauto.c
new file mode 100644
index 00000000..fb883110
--- /dev/null
+++ b/leptonica/src/fmorphauto.c
@@ -0,0 +1,879 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file fmorphauto.c
+ * <pre>
+ *
+ * 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 fmorphautogen() on this SELA. This in turn calls
+ * fmorphautogen1() and fmorphautogen2(), which use the text
+ * files morphtemplate1.txt and morphtemplate2.txt, respectively,
+ * 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 allheaders.h prototype file,
+ * and recompile the library. Look at the Makefile to see how
+ * morphgen.1.c and fmorphgenlow.1.c are included. These files
+ * provide the single high-level interface for erosion, dilation,
+ * opening and closing, and the lower-level functions 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);
+ *
+ * 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.
+ *
+ * N.B. Although pixFMorphopGen_1() is global, you must NOT
+ * use it, because it assumes that 32 or 64 border pixels
+ * have been added to each side, and it will crash without those
+ * added pixels.
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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);"
+
+#define 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"};
+
+
+/*!
+ * \brief fmorphautogen()
+ *
+ * \param[in] sela
+ * \param[in] fileindex
+ * \param[in] filename [optional]; can be null
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fmorphautogen1()
+ *
+ * \param[in] sela
+ * \param[in] fileindex
+ * \param[in] filename [optional]; can be null
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 textlines from morphtemplate1.txt */
+ if ((filestr = (char *)l_binaryRead(TEMPLATE1, &size)) == NULL)
+ return ERROR_INT("filestr not made", procName, 1);
+ sa2 = sarrayCreateLinesFromString(filestr, 1);
+ LEPT_FREE(filestr);
+ if (!sa2)
+ return ERROR_INT("sa2 not made", procName, 1);
+
+ /* Make array of sel names */
+ sa1 = selaGetSelnames(sela);
+
+ /* 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, " * \\brief pixMorphDwa_%d()", fileindex);
+ str_doc3 = stringNew(bigbuf);
+ sprintf(bigbuf, " * \\brief 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 */
+ sa3 = sarrayCreate(0);
+
+ /* 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 */
+ filestr = sarrayToString(sa3, 1);
+ nbytes = strlen(filestr);
+ if (filename)
+ snprintf(bigbuf, L_BUF_SIZE, "%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);
+ sa1 = sarrayCreateLinesFromString(filestr, 1);
+ LEPT_FREE(filestr);
+ if (!sa1)
+ return ERROR_INT("sa1 not made", procName, 1);
+
+ /* Make the array of static function names */
+ if ((sa2 = sarrayCreate(2 * nsels)) == NULL) {
+ sarrayDestroy(&sa1);
+ 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 */
+ sa3 = sarrayCreate(2 * nsels); /* should be ok */
+ for (i = 0; i < 2 * 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 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);
+
+ /* Output to this sa */
+ sa4 = sarrayCreate(0);
+
+ /* 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) {
+ sarrayDestroy(&sa1);
+ sarrayDestroy(&sa2);
+ sarrayDestroy(&sa3);
+ sarrayDestroy(&sa4);
+ return ERROR_INT("linestr not retrieved", procName, 1);
+ }
+ sarrayAddString(sa4, linestr, L_INSERT);
+ }
+
+ /* More strings with function names */
+ sprintf(bigbuf, " * fmorphopgen_low_%d()", fileindex);
+ str_doc4 = stringNew(bigbuf);
+ sprintf(bigbuf, "fmorphopgen_low_%d(l_uint32 *datad,", fileindex);
+ str_def1 = stringNew(bigbuf);
+
+ /* 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) {
+ sarrayDestroy(&sa1);
+ sarrayDestroy(&sa2);
+ sarrayDestroy(&sa3);
+ sarrayDestroy(&sa4);
+ return ERROR_INT("sel not returned", procName, 1);
+ }
+ sa5 = sarrayMakeWplsCode(sel);
+ sarrayJoin(sa4, sa5);
+ sarrayDestroy(&sa5);
+
+ /* Add the function loop code */
+ sarrayAppendRange(sa4, sa1, loopstart, loopend);
+
+ /* Insert barrel-op code for *dptr */
+ sa6 = sarrayMakeInnerLoopDWACode(sel, i);
+ sarrayJoin(sa4, sa6);
+ sarrayDestroy(&sa6);
+
+ /* Finish the function code */
+ sarrayAppendRange(sa4, sa1, finalstart, finalend);
+ }
+
+ /* Output to file */
+ filestr = sarrayToString(sa4, 1);
+ nbytes = strlen(filestr);
+ if (filename)
+ snprintf(bigbuf, L_BUF_SIZE, "%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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief 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;
+ }
+ }
+
+ sa = sarrayCreate(0);
+
+ /* 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;
+}
+
+
+/*!
+ * \brief 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++;
+ }
+ }
+
+ sa = sarrayCreate(0);
+ 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 { /* 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;
+}
+
+
+/*!
+ * \brief 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/leptonica/src/fmorphgen.1.c b/leptonica/src/fmorphgen.1.c
new file mode 100644
index 00000000..f0bf9aec
--- /dev/null
+++ b/leptonica/src/fmorphgen.1.c
@@ -0,0 +1,277 @@
+/*====================================================================*
+ - 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"};
+
+/*!
+ * \brief pixMorphDwa_1()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] operation L_MORPH_DILATE, L_MORPH_ERODE,
+ * L_MORPH_OPEN, L_MORPH_CLOSE
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFMorphopGen_1()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] operation L_MORPH_DILATE, L_MORPH_ERODE,
+ * L_MORPH_OPEN, L_MORPH_CLOSE
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/fmorphgenlow.1.c b/leptonica/src/fmorphgenlow.1.c
new file mode 100644
index 00000000..dd43da2e
--- /dev/null
+++ b/leptonica/src/fmorphgenlow.1.c
@@ -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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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;
+l_int32 j, pwpls;
+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/leptonica/src/fpix1.c b/leptonica/src/fpix1.c
new file mode 100644
index 00000000..f0ecdcab
--- /dev/null
+++ b/leptonica/src/fpix1.c
@@ -0,0 +1,2190 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file fpix1.c
+ * <pre>
+ *
+ * ---------------------------------------------------
+ * This file has these FPix, FPixa and DPix utilities:
+ * - creation and destruction
+ * - accessors
+ * - serialization and deserialization
+ * ---------------------------------------------------
+ *
+ * FPix Create/copy/destroy
+ * FPIX *fpixCreate()
+ * FPIX *fpixCreateTemplate()
+ * FPIX *fpixClone()
+ * FPIX *fpixCopy()
+ * 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()
+ * 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()
+ * FPIX *fpixReadMem()
+ * l_int32 fpixWrite()
+ * l_int32 fpixWriteStream()
+ * l_int32 fpixWriteMem()
+ * FPIX *fpixEndianByteSwap()
+ *
+ * DPix serialized I/O
+ * DPIX *dpixRead()
+ * DPIX *dpixReadStream()
+ * DPIX *dpixReadMem()
+ * l_int32 dpixWrite()
+ * l_int32 dpixWriteStream()
+ * l_int32 dpixWriteMem()
+ * DPIX *dpixEndianByteSwap()
+ *
+ * Print FPix (subsampled, for debugging)
+ * l_int32 fpixPrintStream()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Bounds on array sizes */
+static const size_t MaxPtrArraySize = 100000;
+static const size_t InitialPtrArraySize = 20; /*!< n'importe quoi */
+
+ /* Static functions */
+static l_int32 fpixaExtendArray(FPIXA *fpixa);
+static l_int32 fpixaExtendArrayToSize(FPIXA *fpixa, l_int32 size);
+
+/*--------------------------------------------------------------------*
+ * FPix Create/copy/destroy *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixCreate()
+ *
+ * \param[in] width, height
+ * \return fpixd with data allocated and initialized to 0, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Makes a FPix of specified size, with the data array
+ * allocated and initialized to 0.
+ * (2) The number of pixels must be less than 2^29.
+ * </pre>
+ */
+FPIX *
+fpixCreate(l_int32 width,
+ l_int32 height)
+{
+l_float32 *data;
+l_uint64 npix64;
+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 */
+ npix64 = (l_uint64)width * (l_uint64)height; /* # of 4-byte pixels */
+ if (npix64 >= (1LL << 29)) {
+ L_ERROR("requested w = %d, h = %d\n", procName, width, height);
+ return (FPIX *)ERROR_PTR("requested bytes >= 2^31", procName, NULL);
+ }
+
+ fpixd = (FPIX *)LEPT_CALLOC(1, sizeof(FPIX));
+ fpixSetDimensions(fpixd, width, height);
+ fpixSetWpl(fpixd, width); /* 4-byte words */
+ fpixd->refcount = 1;
+
+ data = (l_float32 *)LEPT_CALLOC((size_t)width * height, sizeof(l_float32));
+ if (!data) {
+ fpixDestroy(&fpixd);
+ return (FPIX *)ERROR_PTR("calloc fail for data", procName, NULL);
+ }
+ fpixSetData(fpixd, data);
+ return fpixd;
+}
+
+
+/*!
+ * \brief fpixCreateTemplate()
+ *
+ * \param[in] fpixs
+ * \return fpixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if ((fpixd = fpixCreate(w, h)) == NULL)
+ return (FPIX *)ERROR_PTR("fpixd not made", procName, NULL);
+ fpixCopyResolution(fpixd, fpixs);
+ return fpixd;
+}
+
+
+/*!
+ * \brief fpixClone()
+ *
+ * \param[in] fpix
+ * \return same fpix ptr, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixClone() for definition and usage.
+ * </pre>
+ */
+FPIX *
+fpixClone(FPIX *fpix)
+{
+ PROCNAME("fpixClone");
+
+ if (!fpix)
+ return (FPIX *)ERROR_PTR("fpix not defined", procName, NULL);
+ fpixChangeRefcount(fpix, 1);
+
+ return fpix;
+}
+
+
+/*!
+ * \brief fpixCopy()
+ *
+ * \param[in] fpixs
+ * \return fpixd, or NULL on error
+ */
+FPIX *
+fpixCopy(FPIX *fpixs)
+{
+l_int32 w, h, bytes;
+l_float32 *datas, *datad;
+FPIX *fpixd;
+
+ PROCNAME("fpixCopy");
+
+ if (!fpixs)
+ return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+
+ /* Total bytes in image data */
+ fpixGetDimensions(fpixs, &w, &h);
+ bytes = 4 * w * h;
+
+ if ((fpixd = fpixCreateTemplate(fpixs)) == NULL)
+ return (FPIX *)ERROR_PTR("fpixd not made", procName, NULL);
+ datas = fpixGetData(fpixs);
+ datad = fpixGetData(fpixd);
+ memcpy(datad, datas, bytes);
+ return fpixd;
+}
+
+
+/*!
+ * \brief fpixDestroy()
+ *
+ * \param[in,out] pfpix will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Decrements the ref count and, if 0, destroys the fpix.
+ * (2) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*--------------------------------------------------------------------*
+ * FPix Accessors *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixGetDimensions()
+ *
+ * \param[in] fpix
+ * \param[out] pw, ph [optional] each can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fpixSetDimensions()
+ *
+ * \param[in] fpix
+ * \param[in] w, h
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fpixGetWpl()
+ *
+ * \param[in] fpix
+ * \return wpl, or 0 on error
+ */
+l_int32
+fpixGetWpl(FPIX *fpix)
+{
+ PROCNAME("fpixGetWpl");
+
+ if (!fpix)
+ return ERROR_INT("fpix not defined", procName, 0);
+ return fpix->wpl;
+}
+
+
+/*!
+ * \brief fpixSetWpl()
+ *
+ * \param[in] fpix
+ * \param[in] wpl
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+fpixSetWpl(FPIX *fpix,
+ l_int32 wpl)
+{
+ PROCNAME("fpixSetWpl");
+
+ if (!fpix)
+ return ERROR_INT("fpix not defined", procName, 1);
+
+ fpix->wpl = wpl;
+ return 0;
+}
+
+
+/*!
+ * \brief fpixGetRefcount()
+ *
+ * \param[in] fpix
+ * \return refcount, or UNDEF on error
+ */
+l_int32
+fpixGetRefcount(FPIX *fpix)
+{
+ PROCNAME("fpixGetRefcount");
+
+ if (!fpix)
+ return ERROR_INT("fpix not defined", procName, UNDEF);
+ return fpix->refcount;
+}
+
+
+/*!
+ * \brief fpixChangeRefcount()
+ *
+ * \param[in] fpix
+ * \param[in] delta
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+fpixChangeRefcount(FPIX *fpix,
+ l_int32 delta)
+{
+ PROCNAME("fpixChangeRefcount");
+
+ if (!fpix)
+ return ERROR_INT("fpix not defined", procName, 1);
+
+ fpix->refcount += delta;
+ return 0;
+}
+
+
+/*!
+ * \brief fpixGetResolution()
+ *
+ * \param[in] fpix
+ * \param[out] pxres, pyres [optional] x and y resolution
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fpixSetResolution()
+ *
+ * \param[in] fpix
+ * \param[in] xres, yres x and y resolution
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fpixCopyResolution()
+ *
+ * \param[in] fpixd, fpixs
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fpixGetData()
+ *
+ * \param[in] fpix
+ * \return ptr to fpix data, or NULL on error
+ */
+l_float32 *
+fpixGetData(FPIX *fpix)
+{
+ PROCNAME("fpixGetData");
+
+ if (!fpix)
+ return (l_float32 *)ERROR_PTR("fpix not defined", procName, NULL);
+ return fpix->data;
+}
+
+
+/*!
+ * \brief fpixSetData()
+ *
+ * \param[in] fpix
+ * \param[in] data
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+fpixSetData(FPIX *fpix,
+ l_float32 *data)
+{
+ PROCNAME("fpixSetData");
+
+ if (!fpix)
+ return ERROR_INT("fpix not defined", procName, 1);
+
+ fpix->data = data;
+ return 0;
+}
+
+
+/*!
+ * \brief fpixGetPixel()
+ *
+ * \param[in] fpix
+ * \param[in] x,y pixel coords
+ * \param[out] pval pixel value
+ * \return 0 if OK; 1 or 2 on error
+ *
+ * Notes:
+ * (1) If the point is outside the image, this returns an error (2),
+ * with 0.0 in %pval. To avoid spamming output, it fails silently.
+ */
+l_ok
+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 || y < 0 || y >= h)
+ return 2;
+
+ *pval = *(fpix->data + y * w + x);
+ return 0;
+}
+
+
+/*!
+ * \brief fpixSetPixel()
+ *
+ * \param[in] fpix
+ * \param[in] x,y pixel coords
+ * \param[in] val pixel value
+ * \return 0 if OK; 1 or 2 on error
+ *
+ * Notes:
+ * (1) If the point is outside the image, this returns an error (2),
+ * with 0.0 in %pval. To avoid spamming output, it fails silently.
+ */
+l_ok
+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 || y < 0 || y >= h)
+ return 2;
+
+ *(fpix->data + y * w + x) = val;
+ return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ * FPixa Create/copy/destroy *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixaCreate()
+ *
+ * \param[in] n initial number of ptrs
+ * \return fpixa, or NULL on error
+ */
+FPIXA *
+fpixaCreate(l_int32 n)
+{
+FPIXA *fpixa;
+
+ PROCNAME("fpixaCreate");
+
+ if (n <= 0 || n > MaxPtrArraySize)
+ n = InitialPtrArraySize;
+
+ fpixa = (FPIXA *)LEPT_CALLOC(1, sizeof(FPIXA));
+ fpixa->n = 0;
+ fpixa->nalloc = n;
+ fpixa->refcount = 1;
+ if ((fpixa->fpix = (FPIX **)LEPT_CALLOC(n, sizeof(FPIX *))) == NULL) {
+ fpixaDestroy(&fpixa);
+ return (FPIXA *)ERROR_PTR("fpixa ptrs not made", procName, NULL);
+ }
+
+ return fpixa;
+}
+
+
+/*!
+ * \brief fpixaCopy()
+ *
+ * \param[in] fpixa
+ * \param[in] copyflag L_COPY, L_CLODE or L_COPY_CLONE
+ * \return new fpixa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * copyflag may be one of
+ * ~ 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
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief fpixaDestroy()
+ *
+ * \param[in,out] pfpixa will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Decrements the ref count and, if 0, destroys the fpixa.
+ * (2) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*--------------------------------------------------------------------*
+ * FPixa addition *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixaAddFPix()
+ *
+ * \param[in] fpixa
+ * \param[in] fpix to be added
+ * \param[in] copyflag L_INSERT, L_COPY, L_CLONE
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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(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) {
+ if (fpixaExtendArray(fpixa)) {
+ if (copyflag != L_INSERT)
+ fpixDestroy(&fpixc);
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ }
+ fpixa->fpix[n] = fpixc;
+ fpixa->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief fpixaExtendArray()
+ *
+ * \param[in] fpixa
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Doubles the size of the fpixa ptr array.
+ * (2) The max number of fpix ptrs is 100000.
+ * </pre>
+ */
+static l_int32
+fpixaExtendArray(FPIXA *fpixa)
+{
+ PROCNAME("fpixaExtendArray");
+
+ if (!fpixa)
+ return ERROR_INT("fpixa not defined", procName, 1);
+
+ return fpixaExtendArrayToSize(fpixa, 2 * fpixa->nalloc);
+}
+
+
+/*!
+ * \brief fpixaExtendArrayToSize()
+ *
+ * \param[in] fpixa
+ * \param[in] size new ptr array size
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If necessary, reallocs new fpix ptr array to %size.
+ * (2) The max number of fpix ptrs is 100K.
+ * </pre>
+ */
+static l_int32
+fpixaExtendArrayToSize(FPIXA *fpixa,
+ l_int32 size)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("fpixaExtendArrayToSize");
+
+ if (!fpixa)
+ return ERROR_INT("fpixa not defined", procName, 1);
+ if (fpixa->nalloc > MaxPtrArraySize) /* belt & suspenders */
+ return ERROR_INT("fpixa has too many ptrs", procName, 1);
+ if (size > MaxPtrArraySize)
+ return ERROR_INT("size > 100K ptrs; too large", procName, 1);
+ if (size <= fpixa->nalloc) {
+ L_INFO("size too small; no extension\n", procName);
+ return 0;
+ }
+
+ oldsize = fpixa->nalloc * sizeof(FPIX *);
+ newsize = size * sizeof(FPIX *);
+ if ((fpixa->fpix = (FPIX **)reallocNew((void **)&fpixa->fpix,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+ fpixa->nalloc = size;
+ return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ * FPixa accessors *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixaGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief fpixaChangeRefcount()
+ *
+ * \param[in] fpixa
+ * \param[in] delta
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+fpixaChangeRefcount(FPIXA *fpixa,
+ l_int32 delta)
+{
+ PROCNAME("fpixaChangeRefcount");
+
+ if (!fpixa)
+ return ERROR_INT("fpixa not defined", procName, 1);
+
+ fpixa->refcount += delta;
+ return 0;
+}
+
+
+/*!
+ * \brief fpixaGetFPix()
+ *
+ * \param[in] fpixa
+ * \param[in] index to the index-th fpix
+ * \param[in] 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(fpixa->fpix[index]);
+ else if (accesstype == L_CLONE)
+ return fpixClone(fpixa->fpix[index]);
+ else
+ return (FPIX *)ERROR_PTR("invalid accesstype", procName, NULL);
+}
+
+
+/*!
+ * \brief fpixaGetFPixDimensions()
+ *
+ * \param[in] fpixa
+ * \param[in] index to the index-th box
+ * \param[out] pw, ph [optional] each can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fpixaGetData()
+ *
+ * \param[in] fpixa
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief fpixaGetPixel()
+ *
+ * \param[in] fpixa
+ * \param[in] index into fpixa array
+ * \param[in] x,y pixel coords
+ * \param[out] pval pixel value
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fpixaSetPixel()
+ *
+ * \param[in] fpixa
+ * \param[in] index into fpixa array
+ * \param[in] x,y pixel coords
+ * \param[in] val pixel value
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief dpixCreate()
+ *
+ * \param[in] width, height
+ * \return dpix with data allocated and initialized to 0, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Makes a DPix of specified size, with the data array
+ * allocated and initialized to 0.
+ * (2) The number of pixels must be less than 2^28.
+ * </pre>
+ */
+DPIX *
+dpixCreate(l_int32 width,
+ l_int32 height)
+{
+l_float64 *data;
+l_uint64 npix64;
+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 */
+ npix64 = (l_uint64)width * (l_uint64)height; /* # of 8 byte pixels */
+ if (npix64 >= (1LL << 28)) {
+ L_ERROR("requested w = %d, h = %d\n", procName, width, height);
+ return (DPIX *)ERROR_PTR("requested bytes >= 2^31", procName, NULL);
+ }
+
+ dpix = (DPIX *)LEPT_CALLOC(1, sizeof(DPIX));
+ dpixSetDimensions(dpix, width, height);
+ dpixSetWpl(dpix, width); /* 8 byte words */
+ dpix->refcount = 1;
+
+ data = (l_float64 *)LEPT_CALLOC((size_t)width * height, sizeof(l_float64));
+ if (!data) {
+ dpixDestroy(&dpix);
+ return (DPIX *)ERROR_PTR("calloc fail for data", procName, NULL);
+ }
+ dpixSetData(dpix, data);
+ return dpix;
+}
+
+
+/*!
+ * \brief dpixCreateTemplate()
+ *
+ * \param[in] dpixs
+ * \return dpixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief dpixClone()
+ *
+ * \param[in] dpix
+ * \return same dpix ptr, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixClone() for definition and usage.
+ * </pre>
+ */
+DPIX *
+dpixClone(DPIX *dpix)
+{
+ PROCNAME("dpixClone");
+
+ if (!dpix)
+ return (DPIX *)ERROR_PTR("dpix not defined", procName, NULL);
+ dpixChangeRefcount(dpix, 1);
+ return dpix;
+}
+
+
+/*!
+ * \brief dpixCopy()
+ *
+ * \param[in] dpixs
+ * \return dpixd, or NULL on error
+ */
+DPIX *
+dpixCopy(DPIX *dpixs)
+{
+l_int32 w, h, bytes;
+l_float64 *datas, *datad;
+DPIX *dpixd;
+
+ PROCNAME("dpixCopy");
+
+ if (!dpixs)
+ return (DPIX *)ERROR_PTR("dpixs not defined", procName, NULL);
+
+ /* Total bytes in image data */
+ dpixGetDimensions(dpixs, &w, &h);
+ bytes = 8 * w * h;
+
+ if ((dpixd = dpixCreateTemplate(dpixs)) == NULL)
+ return (DPIX *)ERROR_PTR("dpixd not made", procName, NULL);
+ datas = dpixGetData(dpixs);
+ datad = dpixGetData(dpixd);
+ memcpy(datad, datas, bytes);
+ return dpixd;
+}
+
+
+/*!
+ * \brief dpixDestroy()
+ *
+ * \param[in,out] pdpix will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Decrements the ref count and, if 0, destroys the dpix.
+ * (2) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*--------------------------------------------------------------------*
+ * DPix Accessors *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief dpixGetDimensions()
+ *
+ * \param[in] dpix
+ * \param[out] pw, ph [optional] each can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dpixSetDimensions()
+ *
+ * \param[in] dpix
+ * \param[in] w, h
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dpixGetWpl()
+ *
+ * \param[in] dpix
+ * \return wpl, or 0 on error
+ */
+l_int32
+dpixGetWpl(DPIX *dpix)
+{
+ PROCNAME("dpixGetWpl");
+
+ if (!dpix)
+ return ERROR_INT("dpix not defined", procName, 0);
+ return dpix->wpl;
+}
+
+
+/*!
+ * \brief dpixSetWpl()
+ *
+ * \param[in] dpix
+ * \param[in] wpl
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+dpixSetWpl(DPIX *dpix,
+ l_int32 wpl)
+{
+ PROCNAME("dpixSetWpl");
+
+ if (!dpix)
+ return ERROR_INT("dpix not defined", procName, 1);
+
+ dpix->wpl = wpl;
+ return 0;
+}
+
+
+/*!
+ * \brief dpixGetRefcount()
+ *
+ * \param[in] dpix
+ * \return refcount, or UNDEF on error
+ */
+l_int32
+dpixGetRefcount(DPIX *dpix)
+{
+ PROCNAME("dpixGetRefcount");
+
+ if (!dpix)
+ return ERROR_INT("dpix not defined", procName, UNDEF);
+ return dpix->refcount;
+}
+
+
+/*!
+ * \brief dpixChangeRefcount()
+ *
+ * \param[in] dpix
+ * \param[in] delta
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+dpixChangeRefcount(DPIX *dpix,
+ l_int32 delta)
+{
+ PROCNAME("dpixChangeRefcount");
+
+ if (!dpix)
+ return ERROR_INT("dpix not defined", procName, 1);
+
+ dpix->refcount += delta;
+ return 0;
+}
+
+
+/*!
+ * \brief dpixGetResolution()
+ *
+ * \param[in] dpix
+ * \param[out] pxres, pyres [optional] x and y resolution
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dpixSetResolution()
+ *
+ * \param[in] dpix
+ * \param[in] xres, yres x and y resolution
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dpixCopyResolution()
+ *
+ * \param[in] dpixd, dpixs
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dpixGetData()
+ *
+ * \param[in] dpix
+ * \return ptr to dpix data, or NULL on error
+ */
+l_float64 *
+dpixGetData(DPIX *dpix)
+{
+ PROCNAME("dpixGetData");
+
+ if (!dpix)
+ return (l_float64 *)ERROR_PTR("dpix not defined", procName, NULL);
+ return dpix->data;
+}
+
+
+/*!
+ * \brief dpixSetData()
+ *
+ * \param[in] dpix
+ * \param[in] data
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+dpixSetData(DPIX *dpix,
+ l_float64 *data)
+{
+ PROCNAME("dpixSetData");
+
+ if (!dpix)
+ return ERROR_INT("dpix not defined", procName, 1);
+
+ dpix->data = data;
+ return 0;
+}
+
+
+/*!
+ * \brief dpixGetPixel()
+ *
+ * \param[in] dpix
+ * \param[in] x,y pixel coords
+ * \param[out] pval pixel value
+ * \return 0 if OK; 1 or 2 on error
+ *
+ * Notes:
+ * (1) If the point is outside the image, this returns an error (2),
+ * with 0.0 in %pval. To avoid spamming output, it fails silently.
+ */
+l_ok
+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 || y < 0 || y >= h)
+ return 2;
+
+ *pval = *(dpix->data + y * w + x);
+ return 0;
+}
+
+
+/*!
+ * \brief dpixSetPixel()
+ *
+ * \param[in] dpix
+ * \param[in] x,y pixel coords
+ * \param[in] val pixel value
+ * \return 0 if OK; 1 or 2 on error
+ *
+ * Notes:
+ * (1) If the point is outside the image, this returns an error (2),
+ * with 0.0 in %pval. To avoid spamming output, it fails silently.
+ */
+l_ok
+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 || y < 0 || y >= h)
+ return 2;
+
+ *(dpix->data + y * w + x) = val;
+ return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ * FPix serialized I/O *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixRead()
+ *
+ * \param[in] 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);
+ fpix = fpixReadStream(fp);
+ fclose(fp);
+ if (!fpix)
+ return (FPIX *)ERROR_PTR("fpix not read", procName, NULL);
+ return fpix;
+}
+
+
+/*!
+ * \brief fpixReadStream()
+ *
+ * \param[in] fp file 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) {
+ fpixDestroy(&fpix);
+ 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;
+}
+
+
+/*!
+ * \brief fpixReadMem()
+ *
+ * \param[in] data of serialized fpix
+ * \param[in] size of data in bytes
+ * \return fpix, or NULL on error
+ */
+FPIX *
+fpixReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+FPIX *fpix;
+
+ PROCNAME("fpixReadMem");
+
+ if (!data)
+ return (FPIX *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (FPIX *)ERROR_PTR("stream not opened", procName, NULL);
+
+ fpix = fpixReadStream(fp);
+ fclose(fp);
+ if (!fpix) L_ERROR("fpix not read\n", procName);
+ return fpix;
+}
+
+
+/*!
+ * \brief fpixWrite()
+ *
+ * \param[in] filename
+ * \param[in] fpix
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+fpixWrite(const char *filename,
+ FPIX *fpix)
+{
+l_int32 ret;
+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);
+ ret = fpixWriteStream(fp, fpix);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("fpix not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief fpixWriteStream()
+ *
+ * \param[in] fp file stream opened for "wb"
+ * \param[in] fpix
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+fpixWriteStream(FILE *fp,
+ FPIX *fpix)
+{
+l_int32 w, h, xres, yres;
+l_uint32 nbytes;
+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 = sizeof(l_float32) * w * h;
+ fpixGetResolution(fpixt, &xres, &yres);
+ fprintf(fp, "\nFPix Version %d\n", FPIX_VERSION_NUMBER);
+ fprintf(fp, "w = %d, h = %d, nbytes = %u\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;
+}
+
+
+/*!
+ * \brief fpixWriteMem()
+ *
+ * \param[out] pdata data of serialized fpix
+ * \param[out] psize size of returned data
+ * \param[in] fpix
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a fpix in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+fpixWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ FPIX *fpix)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("fpixWriteMem");
+
+ 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 (!fpix)
+ return ERROR_INT("fpix not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = fpixWriteStream(fp, fpix);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = fpixWriteStream(fp, fpix);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*!
+ * \brief fpixEndianByteSwap()
+ *
+ * \param[in] fpixd [optional] can be either NULL, or equal to fpixs
+ * \param[in] fpixs
+ * \return fpixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!fpixd)
+ fpixd = fpixCopy(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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief dpixRead()
+ *
+ * \param[in] 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);
+ dpix = dpixReadStream(fp);
+ fclose(fp);
+ if (!dpix)
+ return (DPIX *)ERROR_PTR("dpix not read", procName, NULL);
+ return dpix;
+}
+
+
+/*!
+ * \brief dpixReadStream()
+ *
+ * \param[in] fp file 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) {
+ dpixDestroy(&dpix);
+ 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;
+}
+
+
+/*!
+ * \brief dpixReadMem()
+ *
+ * \param[in] data of serialized dpix
+ * \param[in] size of data in bytes
+ * \return dpix, or NULL on error
+ */
+DPIX *
+dpixReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+DPIX *dpix;
+
+ PROCNAME("dpixReadMem");
+
+ if (!data)
+ return (DPIX *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (DPIX *)ERROR_PTR("stream not opened", procName, NULL);
+
+ dpix = dpixReadStream(fp);
+ fclose(fp);
+ if (!dpix) L_ERROR("dpix not read\n", procName);
+ return dpix;
+}
+
+
+/*!
+ * \brief dpixWrite()
+ *
+ * \param[in] filename
+ * \param[in] dpix
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+dpixWrite(const char *filename,
+ DPIX *dpix)
+{
+l_int32 ret;
+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);
+ ret = dpixWriteStream(fp, dpix);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("dpix not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief dpixWriteStream()
+ *
+ * \param[in] fp file stream opened for "wb"
+ * \param[in] dpix
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+dpixWriteStream(FILE *fp,
+ DPIX *dpix)
+{
+l_int32 w, h, xres, yres;
+l_uint32 nbytes;
+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 = sizeof(l_float64) * w * h;
+ fprintf(fp, "\nDPix Version %d\n", DPIX_VERSION_NUMBER);
+ fprintf(fp, "w = %d, h = %d, nbytes = %u\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;
+}
+
+
+/*!
+ * \brief dpixWriteMem()
+ *
+ * \param[out] pdata data of serialized dpix
+ * \param[out] psize size of returned data
+ * \param[in] dpix
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a dpix in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+dpixWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ DPIX *dpix)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("dpixWriteMem");
+
+ 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 (!dpix)
+ return ERROR_INT("dpix not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = dpixWriteStream(fp, dpix);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = dpixWriteStream(fp, dpix);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*!
+ * \brief dpixEndianByteSwap()
+ *
+ * \param[in] dpixd [optional] can be either NULL, or equal to dpixs
+ * \param[in] dpixs
+ * \return dpixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!dpixd)
+ dpixd = dpixCopy(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) *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixPrintStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] fpix
+ * \param[in] factor for subsampling
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Subsampled printout of fpix for debugging.
+ * </pre>
+ */
+l_ok
+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/leptonica/src/fpix2.c b/leptonica/src/fpix2.c
new file mode 100644
index 00000000..1bb8bbed
--- /dev/null
+++ b/leptonica/src/fpix2.c
@@ -0,0 +1,2447 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file fpix2.c
+ * <pre>
+ *
+ * ------------------------------------------
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*--------------------------------------------------------------------*
+ * FPix <--> Pix conversions *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertToFPix()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16 or 32 bpp
+ * \param[in] ncomps number of components: 3 for RGB, 1 otherwise
+ * \return fpix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ pixDestroy(&pixt);
+ 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;
+}
+
+
+/*!
+ * \brief pixConvertToDPix()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16 or 32 bpp
+ * \param[in] ncomps number of components: 3 for RGB, 1 otherwise
+ * \return dpix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ pixDestroy(&pixt);
+ 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;
+}
+
+
+/*!
+ * \brief fpixConvertToPix()
+ *
+ * \param[in] fpixs
+ * \param[in] outdepth 0, 8, 16 or 32 bpp
+ * \param[in] negvals L_CLIP_TO_ZERO, L_TAKE_ABSVAL
+ * \param[in] errorflag 1 to output error stats; 0 otherwise
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+fpixConvertToPix(FPIX *fpixs,
+ l_int32 outdepth,
+ l_int32 negvals,
+ l_int32 errorflag)
+{
+l_int32 w, h, i, j, wpls, wpld;
+l_uint32 vald, maxval;
+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;
+ }
+ }
+ }
+ if (outdepth == 8)
+ maxval = 0xff;
+ else if (outdepth == 16)
+ maxval = 0xffff;
+ else /* outdepth == 32 */
+ maxval = 0xffffffff;
+
+ /* 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;
+}
+
+
+/*!
+ * \brief fpixDisplayMaxDynamicRange()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief fpixConvertToDPix()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief dpixConvertToPix()
+ *
+ * \param[in] dpixs
+ * \param[in] outdepth 0, 8, 16 or 32 bpp
+ * \param[in] negvals L_CLIP_TO_ZERO, L_TAKE_ABSVAL
+ * \param[in] errorflag 1 to output error stats; 0 otherwise
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 = 0xff;
+ if (outdepth == 16)
+ maxval = 0xffff;
+ else /* outdepth == 32 */
+ maxval = 0xffffffff;
+
+ /* 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;
+}
+
+
+/*!
+ * \brief dpixConvertToFPix()
+ *
+ * \param[in] 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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixGetMin()
+ *
+ * \param[in] fpix
+ * \param[out] pminval [optional] min value
+ * \param[out] pxminloc [optional] x location of min
+ * \param[out] pyminloc [optional] y location of min
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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.0e20f;
+ 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;
+}
+
+
+/*!
+ * \brief fpixGetMax()
+ *
+ * \param[in] fpix
+ * \param[out] pmaxval [optional] max value
+ * \param[out] pxmaxloc [optional] x location of max
+ * \param[out] pymaxloc [optional] y location of max
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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.0e20f;
+ 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;
+}
+
+
+/*!
+ * \brief dpixGetMin()
+ *
+ * \param[in] dpix
+ * \param[out] pminval [optional] min value
+ * \param[out] pxminloc [optional] x location of min
+ * \param[out] pyminloc [optional] y location of min
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dpixGetMax()
+ *
+ * \param[in] dpix
+ * \param[out] pmaxval [optional] max value
+ * \param[out] pxmaxloc [optional] x location of max
+ * \param[out] pymaxloc [optional] y location of max
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixScaleByInteger()
+ *
+ * \param[in] fpixs typically low resolution
+ * \param[in] factor integer scaling factor
+ * \return fpixd interpolated result, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief dpixScaleByInteger()
+ *
+ * \param[in] dpixs typically low resolution
+ * \param[in] factor integer scaling factor
+ * \return dpixd interpolated result, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixLinearCombination()
+ *
+ * \param[in] fpixd [optional] this can be null, or equal to fpixs1
+ * \param[in] fpixs1 can be equal to fpixd
+ * \param[in] fpixs2
+ * \param[in] a, b multiplication factors on fpixs1 and fpixs2, rsp.
+ * \return fpixd always
+ *
+ * <pre>
+ * Notes:
+ * (1) Computes pixelwise linear combination: a * src1 + b * src2
+ * (2) Alignment is to UL corner; src1 and src2 do not have to be
+ * the same size.
+ * (3) There are 2 cases. The result can go to a new dest, or
+ * in-place to fpixs1:
+ * * fpixd == null: (src1 + src2) --> new fpixd
+ * * fpixd == fpixs1: (src1 + src2) --> src1 (in-place)
+ * </pre>
+ */
+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 (fpixd && (fpixd != fpixs1))
+ return (FPIX *)ERROR_PTR("invalid inplace operation", procName, fpixd);
+
+ if (!fpixd)
+ fpixd = fpixCopy(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;
+}
+
+
+/*!
+ * \brief fpixAddMultConstant()
+ *
+ * \param[in] fpix
+ * \param[in] addc use 0.0 to skip the operation
+ * \param[in] multc use 1.0 to skip the operation
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dpixLinearCombination()
+ *
+ * \param[in] dpixd [optional] this can be null, or equal to dpixs1
+ * \param[in] dpixs1 can be equal to dpixd
+ * \param[in] dpixs2
+ * \param[in] a, b multiplication factors on dpixs1 and dpixs2, rsp.
+ * \return dpixd always
+ *
+ * <pre>
+ * Notes:
+ * (1) Computes pixelwise linear combination: a * src1 + b * src2
+ * (2) Alignment is to UL corner; src1 and src2 do not have to be
+ * the same size.
+ * (3) There are 2 cases. The result can go to a new dest, or
+ * in-place to dpixs1:
+ * * dpixd == null: (src1 + src2) --> new dpixd
+ * * dpixd == dpixs1: (src1 + src2) --> src1 (in-place)
+ * </pre>
+ */
+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 (dpixd && (dpixd != dpixs1))
+ return (DPIX *)ERROR_PTR("invalid inplace operation", procName, dpixd);
+
+ if (!dpixd)
+ dpixd = dpixCopy(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;
+}
+
+
+/*!
+ * \brief dpixAddMultConstant()
+ *
+ * \param[in] dpix
+ * \param[in] addc use 0.0 to skip the operation
+ * \param[in] multc use 1.0 to skip the operation
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixSetAllArbitrary()
+ *
+ * \param[in] fpix
+ * \param[in] inval to set at each pixel
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief dpixSetAllArbitrary()
+ *
+ * \param[in] dpix
+ * \param[in] inval to set at each pixel
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixAddBorder()
+ *
+ * \param[in] fpixs
+ * \param[in] left, right, top, bot pixels on each side to be added
+ * \return fpixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Adds border of '0' 32-bit pixels
+ * </pre>
+ */
+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(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;
+}
+
+
+/*!
+ * \brief fpixRemoveBorder()
+ *
+ * \param[in] fpixs
+ * \param[in] 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(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;
+}
+
+
+
+/*!
+ * \brief fpixAddMirroredBorder()
+ *
+ * \param[in] fpixs
+ * \param[in] left, right, top, bot pixels on each side to be added
+ * \return fpixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixAddMirroredBorder() for situations of usage.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief fpixAddContinuedBorder()
+ *
+ * \param[in] fpixs
+ * \param[in] left, right, top, bot pixels on each side to be added
+ * \return fpixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This adds pixels on each side whose values are equal to
+ * the value on the closest boundary pixel.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief fpixAddSlopeBorder()
+ *
+ * \param[in] fpixs
+ * \param[in] left, right, top, bot pixels on each side to be added
+ * \return fpixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This adds pixels on each side whose values have a normal
+ * derivative equal to the normal derivative at the boundary
+ * of fpixs.
+ * </pre>
+ */
+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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixRasterop()
+ *
+ * \param[in] fpixd dest fpix
+ * \param[in] dx x val of UL corner of dest rectangle
+ * \param[in] dy y val of UL corner of dest rectangle
+ * \param[in] dw width of dest rectangle
+ * \param[in] dh height of dest rectangle
+ * \param[in] fpixs src fpix
+ * \param[in] sx x val of UL corner of src rectangle
+ * \param[in] sy y val of UL corner of src rectangle
+ * \return 0 if OK; 1 on error.
+ *
+ * <pre>
+ * 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!
+ * </pre>
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixRotateOrth()
+ *
+ * \param[in] fpixs
+ * \param[in] 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(fpixs);
+ else if (quads == 1)
+ return fpixRotate90(fpixs, 1);
+ else if (quads == 2)
+ return fpixRotate180(NULL, fpixs);
+ else /* quads == 3 */
+ return fpixRotate90(fpixs, -1);
+}
+
+
+/*!
+ * \brief fpixRotate180()
+ *
+ * \param[in] fpixd [optional] can be null, or equal to fpixs
+ * \param[in] fpixs
+ * \return fpixd, or NULL on error
+ *
+ * <pre>
+ * 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 2 cases for input:
+ * (a) fpixd == null (creates a new fpixd)
+ * (b) fpixd == fpixs (in-place operation)
+ * (3) For clarity, use these two patterns:
+ * (a) fpixd = fpixRotate180(NULL, fpixs);
+ * (b) fpixRotate180(fpixs, fpixs);
+ * </pre>
+ */
+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)
+ fpixd = fpixCopy(fpixs);
+
+ fpixFlipLR(fpixd, fpixd);
+ fpixFlipTB(fpixd, fpixd);
+ return fpixd;
+}
+
+
+/*!
+ * \brief fpixRotate90()
+ *
+ * \param[in] fpixs
+ * \param[in] direction 1 = clockwise; -1 = counter-clockwise
+ * \return fpixd, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFlipLR()
+ *
+ * \param[in] fpixd [optional] can be null, or equal to fpixs
+ * \param[in] fpixs
+ * \return fpixd, or NULL on error
+ *
+ * <pre>
+ * 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 2 cases for input:
+ * (a) fpixd == null (creates a new fpixd)
+ * (b) fpixd == fpixs (in-place operation)
+ * (3) For clarity, use these two patterns:
+ * (a) fpixd = fpixFlipLR(NULL, fpixs);
+ * (b) fpixFlipLR(fpixs, fpixs);
+ * </pre>
+ */
+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);
+
+ /* Prepare fpixd for in-place operation */
+ if (!fpixd)
+ fpixd = fpixCopy(fpixs);
+
+ fpixGetDimensions(fpixd, &w, &h);
+ data = fpixGetData(fpixd);
+ wpl = fpixGetWpl(fpixd); /* 4-byte words */
+ bpl = 4 * wpl;
+ if ((buffer = (l_float32 *)LEPT_CALLOC(wpl, sizeof(l_float32))) == NULL) {
+ fpixDestroy(&fpixd);
+ 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;
+}
+
+
+/*!
+ * \brief fpixFlipTB()
+ *
+ * \param[in] fpixd [optional] can be null, or equal to fpixs
+ * \param[in] fpixs
+ * \return fpixd, or NULL on error
+ *
+ * <pre>
+ * 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 2 cases for input:
+ * (a) fpixd == null (creates a new fpixd)
+ * (b) fpixd == fpixs (in-place operation)
+ * (3) For clarity, use these two patterns:
+ * (a) fpixd = fpixFlipTB(NULL, fpixs);
+ * (b) fpixFlipTB(fpixs, fpixs);
+ * </pre>
+ */
+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)
+ fpixd = fpixCopy(fpixs);
+
+ data = fpixGetData(fpixd);
+ wpl = fpixGetWpl(fpixd);
+ fpixGetDimensions(fpixd, NULL, &h);
+ if ((buffer = (l_float32 *)LEPT_CALLOC(wpl, sizeof(l_float32))) == NULL) {
+ fpixDestroy(&fpixd);
+ 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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixAffinePta()
+ *
+ * \param[in] fpixs 8 bpp
+ * \param[in] ptad 4 pts of final coordinate space
+ * \param[in] ptas 4 pts of initial coordinate space
+ * \param[in] border size of extension with constant normal derivative
+ * \param[in] inval value brought in; typ. 0
+ * \return fpixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief fpixAffine()
+ *
+ * \param[in] fpixs 8 bpp
+ * \param[in] vc vector of 8 coefficients for projective transformation
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief fpixProjectivePta()
+ *
+ * \param[in] fpixs 8 bpp
+ * \param[in] ptad 4 pts of final coordinate space
+ * \param[in] ptas 4 pts of initial coordinate space
+ * \param[in] border size of extension with constant normal derivative
+ * \param[in] inval value brought in; typ. 0
+ * \return fpixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief fpixProjective()
+ *
+ * \param[in] fpixs 8 bpp
+ * \param[in] vc vector of 8 coefficients for projective transform
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief linearInterpolatePixelFloat()
+ *
+ * \param[in] datas ptr to beginning of float image data
+ * \param[in] w, h dimensions of image
+ * \param[in] x, y floating pt location for evaluation
+ * \param[in] inval float value brought in from the outside when the
+ * input x,y location is outside the image
+ * \param[out] pval interpolated float value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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)
+ lept_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 = (l_float32)(xf) * yf * (*(lines + w + xp + 1));
+ *pval = (v00 + v01 + v10 + v11) / 256.0;
+ return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Thresholding to 1 bpp Pix *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fpixThresholdToPix()
+ *
+ * \param[in] fpix
+ * \param[in] thresh
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For all values of fpix that are <= thresh, sets the pixel
+ * in pixd to 1.
+ * </pre>
+ */
+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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixComponentFunction()
+ *
+ * \param[in] pix 32 bpp rgb
+ * \param[in] rnum, gnum, bnum coefficients for numerator
+ * \param[in] rdenom, gdenom, bdenom coefficients for denominator
+ * \return fpixd, or NULL on error
+ *
+ * <pre>
+ * 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".
+ *
+ * </pre>
+ */
+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/leptonica/src/gifio.c b/leptonica/src/gifio.c
new file mode 100644
index 00000000..21554835
--- /dev/null
+++ b/leptonica/src/gifio.c
@@ -0,0 +1,687 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file gifio.c
+ * <pre>
+ *
+ * Reading gif
+ * PIX *pixReadStreamGif()
+ * PIX *pixReadMemGif()
+ * static l_int32 gifReadFunc()
+ * static PIX *gifToPix()
+ *
+ * Writing gif
+ * l_int32 pixWriteStreamGif()
+ * l_int32 pixWriteMemGif()
+ * static l_int32 gifWriteFunc()
+ * static l_int32 pixToGif()
+ *
+ * The initial version of this module was generously contribued by
+ * Antony Dovgal.
+ *
+ * The functions that read and write from pix to gif-compressed memory,
+ * using gif internal functions DGifOpen() and EGifOpen() that are
+ * available in 5.1 and later, were contributed by Tobias Peirick.
+ *
+ * Version information:
+ *
+ * (1) This supports the gif library, version 5.1 or later, for which
+ * gif read-from-mem and write-to-mem allow these operations
+ * without writing temporary files.
+ * (2) There has never been a gif stream interface. For versions
+ * before 5.1, it was necessary to use a file descriptor, and to
+ * generate a file stream from the low-level descriptor. With the
+ * memory interface in 5.1 that can be used on all platforms, it
+ * is no longer necessary to use any API code with file descriptors.
+ * (3) The public interface changed with 5.0 and with 5.1, and we
+ * no longer support 4.6.1 and 5.0.
+ * (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. The problem was fixed in 5.1.3.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/* --------------------------------------------------------------------*/
+#if HAVE_LIBGIF || HAVE_LIBUNGIF /* defined in environ.h */
+/* --------------------------------------------------------------------*/
+
+#include "gif_lib.h"
+
+ /* Interface that enables low-level GIF support for reading from memory */
+static PIX * gifToPix(GifFileType *gif);
+ /* Interface that enables low-level GIF support for writing to memory */
+static l_int32 pixToGif(PIX *pix, GifFileType *gif);
+
+ /*! For in-memory decoding of GIF; 5.1+ */
+typedef struct GifReadBuffer
+{
+ size_t size; /*!< size of buffer */
+ size_t pos; /*!< position relative to beginning of buffer */
+ const l_uint8 *cdata; /*!< data in the buffer */
+} GifReadBuffer;
+
+ /*! Low-level callback for in-memory decoding */
+static l_int32 gifReadFunc(GifFileType *gif, GifByteType *dest,
+ l_int32 bytesToRead);
+ /*! Low-level callback for in-memory encoding */
+static l_int32 gifWriteFunc(GifFileType *gif, const GifByteType *src,
+ l_int32 bytesToWrite);
+
+
+/*---------------------------------------------------------------------*
+ * Reading gif *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixReadStreamGif()
+ *
+ * \param[in] fp file stream opened for reading
+ * \return pix, or NULL on error
+ */
+PIX *
+pixReadStreamGif(FILE *fp)
+{
+l_uint8 *filedata;
+size_t filesize;
+PIX *pix;
+
+ PROCNAME("pixReadStreamGif");
+
+ if (!fp)
+ return (PIX *)ERROR_PTR("fp not defined", procName, NULL);
+
+ /* Read data into memory from file */
+ rewind(fp);
+ if ((filedata = l_binaryReadStream(fp, &filesize)) == NULL)
+ return (PIX *)ERROR_PTR("filedata not read", procName, NULL);
+
+ /* Uncompress from memory */
+ pix = pixReadMemGif(filedata, filesize);
+ LEPT_FREE(filedata);
+ if (!pix)
+ L_ERROR("failed to read gif from file data\n", procName);
+ return pix;
+}
+
+
+/*!
+ * \brief pixReadMemGif()
+ *
+ * \param[in] cdata const; gif-encoded
+ * \param[in] size bytes data
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For libgif version >= 5.1, this uses the DGifOpen() buffer
+ * interface. No temp files are required.
+ * (2) For libgif version < 5.1, it was necessary to write the compressed
+ * data to file and read it back, and we couldn't use the GNU
+ * runtime extension fmemopen() because libgif doesn't have a file
+ * stream interface.
+ * </pre>
+ */
+PIX *
+pixReadMemGif(const l_uint8 *cdata,
+ size_t size)
+{
+GifFileType *gif;
+GifReadBuffer buffer;
+
+ PROCNAME("pixReadMemGif");
+
+ /* 5.1+ and not 5.1.2 */
+#if (GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0))
+ L_ERROR("Require giflib-5.1 or later\n", procName);
+ return NULL;
+#endif /* < 5.1 */
+#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.3 or later\n", procName);
+ return NULL;
+#endif /* 5.1.2 */
+
+ if (!cdata)
+ return (PIX *)ERROR_PTR("cdata not defined", procName, NULL);
+
+ buffer.cdata = cdata;
+ buffer.size = size;
+ buffer.pos = 0;
+ if ((gif = DGifOpen((void*)&buffer, gifReadFunc, NULL)) == NULL)
+ return (PIX *)ERROR_PTR("could not open gif stream from memory",
+ procName, NULL);
+
+ return gifToPix(gif);
+}
+
+
+static l_int32
+gifReadFunc(GifFileType *gif,
+ GifByteType *dest,
+ l_int32 bytesToRead)
+{
+GifReadBuffer *buffer;
+l_int32 bytesRead;
+
+ PROCNAME("gifReadFunc");
+
+ if ((buffer = (GifReadBuffer*)gif->UserData) == NULL)
+ return ERROR_INT("UserData not set", procName, -1);
+
+ if(buffer->pos >= buffer->size || bytesToRead > buffer->size)
+ return -1;
+
+ bytesRead = (buffer->pos < buffer->size - bytesToRead)
+ ? bytesToRead : buffer->size - buffer->pos;
+ memcpy(dest, buffer->cdata + buffer->pos, bytesRead);
+ buffer->pos += bytesRead;
+ return bytesRead;
+}
+
+
+/*!
+ * \brief gifToPix()
+ *
+ * \param[in] gif opened gif stream
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This decodes the pix from the compressed gif stream and
+ * closes the stream.
+ * (2) It is static so that the stream is not exposed to clients.
+ * </pre>
+ */
+static PIX *
+gifToPix(GifFileType *gif)
+{
+l_int32 wpl, i, j, w, h, d, cindex, ncolors, valid;
+l_int32 rval, gval, bval;
+l_uint32 *data, *line;
+PIX *pixd;
+PIXCMAP *cmap;
+ColorMapObject *gif_cmap;
+SavedImage si;
+int giferr;
+
+ PROCNAME("gifToPix");
+
+ /* 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 <= 0 || ncolors > 256) {
+ DGifCloseFile(gif, &giferr);
+ return (PIX *)ERROR_PTR("ncolors is invalid", procName, NULL);
+ }
+ if (ncolors <= 2)
+ d = 1;
+ else if (ncolors <= 4)
+ d = 2;
+ else if (ncolors <= 16)
+ d = 4;
+ else /* [17 ... 256] */
+ d = 8;
+ cmap = pixcmapCreate(d);
+ 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);
+ pixcmapIsValid(cmap, pixd, &valid);
+ if (!valid) {
+ DGifCloseFile(gif, &giferr);
+ pixDestroy(&pixd);
+ pixcmapDestroy(&cmap);
+ return (PIX *)ERROR_PTR("colormap is invalid", procName, NULL);
+ }
+
+ 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]);
+ }
+ }
+
+ /* Versions before 5.0 required un-interlacing to restore
+ * the raster lines to normal order if the image
+ * had been interlaced (for viewing in a browser):
+ if (gif->Image.Interlace) {
+ PIX *pixdi = pixUninterlaceGIF(pixd);
+ pixTransferAllData(pixd, &pixdi, 0, 0);
+ }
+ * This is no longer required. */
+
+ DGifCloseFile(gif, &giferr);
+ return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Writing gif *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixWriteStreamGif()
+ *
+ * \param[in] fp file stream opened for writing
+ * \param[in] pix 1, 2, 4, 8, 16 or 32 bpp
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+pixWriteStreamGif(FILE *fp,
+ PIX *pix)
+{
+l_uint8 *filedata;
+size_t filebytes, nbytes;
+
+ PROCNAME("pixWriteStreamGif");
+
+ if (!fp)
+ return ERROR_INT("stream not open", procName, 1);
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+
+ pixSetPadBits(pix, 0);
+ if (pixWriteMemGif(&filedata, &filebytes, pix) != 0) {
+ LEPT_FREE(filedata);
+ return ERROR_INT("failure to gif encode pix", procName, 1);
+ }
+
+ rewind(fp);
+ nbytes = fwrite(filedata, 1, filebytes, fp);
+ LEPT_FREE(filedata);
+ if (nbytes != filebytes)
+ return ERROR_INT("write error", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixWriteMemGif()
+ *
+ * \param[out] pdata data of gif compressed image
+ * \param[out] psize size of returned data
+ * \param[in] pix
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See comments in pixReadMemGif()
+ * </pre>
+ */
+l_ok
+pixWriteMemGif(l_uint8 **pdata,
+ size_t *psize,
+ PIX *pix)
+{
+int giferr;
+l_int32 result;
+GifFileType *gif;
+L_BBUFFER *buffer;
+
+ PROCNAME("pixWriteMemGif");
+
+ /* 5.1+ and not 5.1.2 */
+#if (GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0))
+ L_ERROR("Require giflib-5.1 or later\n", procName);
+ return 1;
+#endif /* < 5.1 */
+#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.3 or later\n", procName);
+ return 1;
+#endif /* 5.1.2 */
+
+ 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 );
+
+ if ((buffer = bbufferCreate(NULL, 0)) == NULL)
+ return ERROR_INT("failed to create buffer", procName, 1);
+
+ if ((gif = EGifOpen((void*)buffer, gifWriteFunc, NULL)) == NULL) {
+ bbufferDestroy(&buffer);
+ return ERROR_INT("failed to create GIF image handle", procName, 1);
+ }
+
+ result = pixToGif(pix, gif);
+ EGifCloseFile(gif, &giferr);
+
+ if (result == 0) {
+ *pdata = bbufferDestroyAndSaveData(&buffer, psize);
+ } else {
+ bbufferDestroy(&buffer);
+ }
+ return result;
+}
+
+
+static l_int32
+gifWriteFunc(GifFileType *gif,
+ const GifByteType *src,
+ l_int32 bytesToWrite)
+{
+L_BBUFFER *buffer;
+
+ PROCNAME("gifWriteFunc");
+
+ if ((buffer = (L_BBUFFER*)gif->UserData) == NULL)
+ return ERROR_INT("UserData not set", procName, -1);
+
+ if(bbufferRead(buffer, (l_uint8*)src, bytesToWrite) == 0)
+ return bytesToWrite;
+ return 0;
+}
+
+
+/*!
+ * \brief pixToGif()
+ *
+ * \param[in] pix 1, 2, 4, 8, 16 or 32 bpp
+ * \param[in] gif opened gif stream
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This encodes the pix to the gif stream. The stream is not
+ * closed by this function.
+ * (2) It is static to make this function private.
+ * </pre>
+ */
+static l_int32
+pixToGif(PIX *pix,
+ GifFileType *gif)
+{
+char *text;
+l_int32 wpl, i, j, w, h, d, ncolor, rval, gval, bval, valid;
+l_int32 gif_ncolor = 0;
+l_uint32 *data, *line;
+PIX *pixd;
+PIXCMAP *cmap;
+ColorMapObject *gif_cmap;
+GifByteType *gif_line;
+
+ PROCNAME("pixToGif");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+ if (!gif)
+ return ERROR_INT("gif not defined", procName, 1);
+
+ 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 to colormapped pix", procName, 1);
+ d = pixGetDepth(pixd);
+ cmap = pixGetColormap(pixd);
+ if (!cmap) {
+ pixDestroy(&pixd);
+ return ERROR_INT("cmap is missing", procName, 1);
+ }
+ pixcmapIsValid(cmap, pixd, &valid);
+ if (!valid) {
+ pixDestroy(&pixd);
+ return ERROR_INT("colormap is not valid", 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;
+ }
+
+ pixGetDimensions(pixd, &w, &h, NULL);
+ if (EGifPutScreenDesc(gif, w, h, gif_cmap->BitsPerPixel, 0, gif_cmap)
+ != GIF_OK) {
+ pixDestroy(&pixd);
+ GifFreeMapObject(gif_cmap);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ return 0;
+}
+
+
+#if 0
+/*---------------------------------------------------------------------*
+ * Removing interlacing (reference only; not used) *
+ *---------------------------------------------------------------------*/
+ /* GIF supports 4-way interlacing by raster lines.
+ * Before 5.0, it was necessary for leptonica to restore interlaced
+ * data to normal raster order when reading to a pix. With 5.0,
+ * the de-interlacing is done by the library read function.
+ * It is here only as a reference. */
+static const l_int32 InterlacedOffset[] = {0, 4, 2, 1};
+static const l_int32 InterlacedJumps[] = {8, 8, 4, 2};
+
+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;
+}
+#endif
+
+
+/* -----------------------------------------------------------------*/
+#endif /* HAVE_LIBGIF || HAVE_LIBUNGIF */
diff --git a/leptonica/src/gifiostub.c b/leptonica/src/gifiostub.c
new file mode 100644
index 00000000..20129b9c
--- /dev/null
+++ b/leptonica/src/gifiostub.c
@@ -0,0 +1,72 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file gifiostub.c
+ * <pre>
+ *
+ * Stubs for gifio.c functions
+ * </pre>
+ */
+
+#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);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadMemGif(const l_uint8 *cdata, size_t size)
+{
+ return (PIX *)ERROR_PTR("function not present", "pixReadMemGif", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWriteStreamGif(FILE *fp, PIX *pix)
+{
+ return ERROR_INT("function not present", "pixWriteStreamGif", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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/leptonica/src/gplot.c b/leptonica/src/gplot.c
new file mode 100644
index 00000000..3743207b
--- /dev/null
+++ b/leptonica/src/gplot.c
@@ -0,0 +1,1364 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file gplot.c
+ * <pre>
+ *
+ * Basic plotting functions
+ * GPLOT *gplotCreate()
+ * void gplotDestroy()
+ * l_int32 gplotAddPlot()
+ * l_int32 gplotSetScaling()
+ * PIX *gplotMakeOutputPix()
+ * l_int32 gplotMakeOutput()
+ * l_int32 gplotGenCommandFile()
+ * l_int32 gplotGenDataFiles()
+ *
+ * Quick, one-line plots
+ * l_int32 gplotSimple1()
+ * l_int32 gplotSimple2()
+ * l_int32 gplotSimpleN()
+ * PIX *gplotSimplePix1()
+ * PIX *gplotSimplePix2()
+ * PIX *gplotSimplePixN()
+ * GPLOT *gplotSimpleXY1()
+ * GPLOT *gplotSimpleXY2()
+ * GPLOT *gplotSimpleXYN()
+ * PIX *gplotGeneralPix1()
+ * PIX *gplotGeneralPix2()
+ * PIX *gplotGeneralPixN()
+ *
+ * Serialize for I/O
+ * GPLOT *gplotRead()
+ * l_int32 gplotWrite()
+ *
+ *
+ * Utility for programmatic plotting using gnuplot 4.6 or later
+ * Enabled:
+ * ~ output to png (color), ps and eps (mono), latex (mono)
+ * ~ optional title for plot
+ * ~ optional x and y axis labels
+ * ~ multiple plots on one frame
+ * ~ optional label for each plot on the frame
+ * ~ optional log scaling on either or both axes
+ * ~ choice of 5 plot styles for each array of input data
+ * ~ choice of 2 plot modes, either using one input array
+ * (Y vs index) or two input arrays (Y vs X). For functions
+ * that take two arrays, the first mode (Y vs index) is
+ * employed if the first array is NULL.
+ *
+ * General 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);
+ *
+ * Example usage of one-line plot generators:
+ *
+ * -- Simple plots --
+ * Specify the root of output files, the output format,
+ * and the title (optional), but not the x and y coordinate labels
+ * or the plot labels. The plotstyle defaults to GPLOT_LINES.
+ * gplotSimple2(na1, na2, GPLOT_PNG, "/tmp/lept/histo/gray",
+ * "gray histogram");
+ * Multiple plots can be generated using gplotSimpleN().
+ *
+ * -- Simple plots with more options --
+ * Specify the root of output files, the plotstyle, the output format,
+ * and optionally the title, but not the x and y coordinate labels
+ * or the plot labels.
+ * gplotSimpleXY1(na1, na2, GPLOT_LINES, GPLOT_PNG,
+ * "/tmp/lept/histo/gray", "gray histogram");
+ * Multiple plots can be generated using gplotSimpleXYN().
+ *
+ * -- Simple plots returning a pix --
+ * Specify only the title (optional). The plotstyle defaults
+ * GPLOT_LINES and the output format is GPLOT_PNG..
+ * You can't specify the x and y coordinate lables or the plot label.
+ * The rootname of the generated files is determined internally.
+ * Pix *pix = gplotSimplePix2(na1, na2, "gray histogram");
+ * Multiple plots can be generated using gplotSimplePixN().
+ *
+ * -- General plots returning a pix --
+ * Specify the root of the output files, the plotstyle, and optionally
+ * the title and axis labels. This does not allow the individual
+ * plots to have plot labels, or to use different plotstyles
+ * for each plot.
+ * Pix *pix = gplotGeneralPix2(na1, na2, "/tmp/lept/histo/gray",
+ * GPLOT_LINES, "gray histogram",
+ * "pix value", "num pixels");
+ * Multiple plots can be generated using gplotGeneralPixN().
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+#define Bufsize 512 /* hardcoded below in fscanf */
+
+const char *gplotstylenames[] = {"with lines",
+ "with points",
+ "with impulses",
+ "with linespoints",
+ "with dots"};
+const char *gplotfileoutputs[] = {"",
+ "PNG",
+ "PS",
+ "EPS",
+ "LATEX",
+ "PNM"};
+
+
+/*-----------------------------------------------------------------*
+ * Basic Plotting Functions *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief gplotCreate()
+ *
+ * \param[in] rootname root for all output files
+ * \param[in] outformat GPLOT_PNG, GPLOT_PS, GPLOT_EPS,
+ * GPLOT_LATEX, GPLOT_PNM
+ * \param[in] title [optional] overall title
+ * \param[in] xlabel [optional] x axis label
+ * \param[in] ylabel [optional] y axis label
+ * \return gplot, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This initializes the plot.
+ * (2) The 'title', 'xlabel' and 'ylabel' strings can have spaces,
+ * double quotes and backquotes, but not single quotes.
+ * </pre>
+ */
+GPLOT *
+gplotCreate(const char *rootname,
+ l_int32 outformat,
+ const char *title,
+ const char *xlabel,
+ const char *ylabel)
+{
+char *newroot;
+char buf[Bufsize];
+l_int32 badchar;
+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_LATEX &&
+ outformat != GPLOT_PNM)
+ return (GPLOT *)ERROR_PTR("outformat invalid", procName, NULL);
+ stringCheckForChars(rootname, "`;&|><\"?*$()", &badchar);
+ if (badchar) /* danger of command injection */
+ return (GPLOT *)ERROR_PTR("invalid rootname", procName, NULL);
+
+#if !defined(HAVE_LIBPNG)
+ if (outformat == GPLOT_PNG) {
+ L_WARNING("png library missing; output pnm format\n", procName);
+ outformat = GPLOT_PNM;
+ }
+#endif
+
+ gplot = (GPLOT *)LEPT_CALLOC(1, sizeof(GPLOT));
+ gplot->cmddata = sarrayCreate(0);
+ gplot->datanames = sarrayCreate(0);
+ gplot->plotdata = sarrayCreate(0);
+ gplot->plotlabels = 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, Bufsize, "%s.cmd", rootname);
+ gplot->cmdname = stringNew(buf);
+ if (outformat == GPLOT_PNG)
+ snprintf(buf, Bufsize, "%s.png", newroot);
+ else if (outformat == GPLOT_PS)
+ snprintf(buf, Bufsize, "%s.ps", newroot);
+ else if (outformat == GPLOT_EPS)
+ snprintf(buf, Bufsize, "%s.eps", newroot);
+ else if (outformat == GPLOT_LATEX)
+ snprintf(buf, Bufsize, "%s.tex", newroot);
+ else if (outformat == GPLOT_PNM)
+ snprintf(buf, Bufsize, "%s.pnm", newroot);
+ gplot->outname = stringNew(buf);
+ if (title) gplot->title = stringNew(title);
+ if (xlabel) gplot->xlabel = stringNew(xlabel);
+ if (ylabel) gplot->ylabel = stringNew(ylabel);
+
+ return gplot;
+}
+
+
+/*!
+ * \brief gplotDestroy()
+ *
+ * \param[in,out] pgplot will be set to null before returning
+ */
+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->plotlabels);
+ 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;
+}
+
+
+/*!
+ * \brief gplotAddPlot()
+ *
+ * \param[in] gplot
+ * \param[in] nax [optional] numa: set to null for Y_VS_I;
+ * required for Y_VS_X
+ * \param[in] nay numa; required for both Y_VS_I and Y_VS_X
+ * \param[in] plotstyle GPLOT_LINES, GPLOT_POINTS, GPLOT_IMPULSES,
+ * GPLOT_LINESPOINTS, GPLOT_DOTS
+ * \param[in] plotlabel [optional] label for individual plot
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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, and
+ * must have at least one number.
+ * (4) The 'plotlabel' string can have spaces, double
+ * quotes and backquotes, but not single quotes.
+ * </pre>
+ */
+l_ok
+gplotAddPlot(GPLOT *gplot,
+ NUMA *nax,
+ NUMA *nay,
+ l_int32 plotstyle,
+ const char *plotlabel)
+{
+char buf[Bufsize];
+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 < 0 || plotstyle >= NUM_GPLOT_STYLES)
+ return ERROR_INT("invalid plotstyle", procName, 1);
+
+ if ((n = numaGetCount(nay)) == 0)
+ return ERROR_INT("no points to plot", procName, 1);
+ if (nax && (n != numaGetCount(nax)))
+ return ERROR_INT("nax and nay sizes differ", procName, 1);
+ if (n == 1 && plotstyle == GPLOT_LINES) {
+ L_INFO("only 1 pt; changing style to points\n", procName);
+ plotstyle = GPLOT_POINTS;
+ }
+
+ /* Save plotstyle and plotlabel */
+ numaGetParameters(nay, &startx, &delx);
+ numaAddNumber(gplot->plotstyles, plotstyle);
+ if (plotlabel) {
+ title = stringNew(plotlabel);
+ sarrayAddString(gplot->plotlabels, title, L_INSERT);
+ } else {
+ sarrayAddString(gplot->plotlabels, emptystring, L_COPY);
+ }
+
+ /* Generate and save data filename */
+ gplot->nplots++;
+ snprintf(buf, Bufsize, "%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, Bufsize, "%f %f\n", valx, valy);
+ sarrayAddString(sa, buf, L_COPY);
+ }
+ datastr = sarrayToString(sa, 0);
+ sarrayAddString(gplot->plotdata, datastr, L_INSERT);
+ sarrayDestroy(&sa);
+
+ return 0;
+}
+
+
+/*!
+ * \brief gplotSetScaling()
+ *
+ * \param[in] gplot
+ * \param[in] scaling GPLOT_LINEAR_SCALE, GPLOT_LOG_SCALE_X,
+ * GPLOT_LOG_SCALE_Y, GPLOT_LOG_SCALE_X_Y
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) By default, the x and y axis scaling is linear.
+ * (2) Call this function to set semi-log or log-log scaling.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief gplotMakeOutputPix()
+ *
+ * \param[in] gplot
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This wraps gplotMakeOutput(), and returns a pix.
+ * See gplotMakeOutput() for details.
+ * (2) The gplot output format must be an image (png or pnm).
+ * </pre>
+ */
+PIX *
+gplotMakeOutputPix(GPLOT *gplot)
+{
+ PROCNAME("gplotMakeOutputPix");
+
+ if (!gplot)
+ return (PIX *)ERROR_PTR("gplot not defined", procName, NULL);
+ if (gplot->outformat != GPLOT_PNG && gplot->outformat != GPLOT_PNM)
+ return (PIX *)ERROR_PTR("output format not an image", procName, NULL);
+
+ if (gplotMakeOutput(gplot))
+ return (PIX *)ERROR_PTR("plot output not made", procName, NULL);
+ return pixRead(gplot->outname);
+}
+
+
+/*!
+ * \brief gplotMakeOutput()
+ *
+ * \param[in] gplot
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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) Along with gplotMakeOutputPix(), these are the only functions
+ * in this file that requires the gnuplot executable to
+ * actually generate the plot.
+ * (3) The command file name for unix is canonical (i.e., directory /tmp)
+ * but the temp filename paths in the command file must be correct.
+ * (4) The gnuplot program for windows is wgnuplot.exe.
+ * </pre>
+ */
+l_ok
+gplotMakeOutput(GPLOT *gplot)
+{
+char buf[Bufsize];
+char *cmdname;
+
+ PROCNAME("gplotMakeOutput");
+
+ if (!gplot)
+ return ERROR_INT("gplot not defined", procName, 1);
+
+ if (!LeptDebugOK) {
+ L_INFO("running gnuplot is disabled; "
+ "use setLeptDebugOK(1) to enable\n", procName);
+ return 0;
+ }
+
+#ifdef OS_IOS /* iOS 11 does not support system() */
+ return ERROR_INT("iOS 11 does not support system()", procName, 0);
+#endif /* OS_IOS */
+
+ gplotGenCommandFile(gplot);
+ gplotGenDataFiles(gplot);
+ cmdname = genPathname(gplot->cmdname, NULL);
+
+#ifndef _WIN32
+ snprintf(buf, Bufsize, "gnuplot %s", cmdname);
+#else
+ snprintf(buf, Bufsize, "wgnuplot %s", cmdname);
+#endif /* _WIN32 */
+
+ callSystemDebug(buf); /* gnuplot || wgnuplot */
+ LEPT_FREE(cmdname);
+ return 0;
+}
+
+
+/*!
+ * \brief gplotGenCommandFile()
+ *
+ * \param[in] gplot
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+gplotGenCommandFile(GPLOT *gplot)
+{
+char buf[Bufsize];
+char *cmdstr, *plotlabel, *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, Bufsize, "set title '%s'", gplot->title);
+ sarrayAddString(gplot->cmddata, buf, L_COPY);
+ }
+ if (gplot->xlabel) { /* set xlabel */
+ snprintf(buf, Bufsize, "set xlabel '%s'", gplot->xlabel);
+ sarrayAddString(gplot->cmddata, buf, L_COPY);
+ }
+ if (gplot->ylabel) { /* set ylabel */
+ snprintf(buf, Bufsize, "set ylabel '%s'", gplot->ylabel);
+ sarrayAddString(gplot->cmddata, buf, L_COPY);
+ }
+
+ /* Set terminal type and output */
+ if (gplot->outformat == GPLOT_PNG) {
+ snprintf(buf, Bufsize, "set terminal png; set output '%s'",
+ gplot->outname);
+ } else if (gplot->outformat == GPLOT_PS) {
+ snprintf(buf, Bufsize, "set terminal postscript; set output '%s'",
+ gplot->outname);
+ } else if (gplot->outformat == GPLOT_EPS) {
+ snprintf(buf, Bufsize, "set terminal postscript eps; set output '%s'",
+ gplot->outname);
+ } else if (gplot->outformat == GPLOT_LATEX) {
+ snprintf(buf, Bufsize, "set terminal latex; set output '%s'",
+ gplot->outname);
+ } else if (gplot->outformat == GPLOT_PNM) {
+ snprintf(buf, Bufsize, "set terminal pbm color; set output '%s'",
+ gplot->outname);
+ }
+ sarrayAddString(gplot->cmddata, buf, L_COPY);
+
+ if (gplot->scaling == GPLOT_LOG_SCALE_X ||
+ gplot->scaling == GPLOT_LOG_SCALE_X_Y) {
+ snprintf(buf, Bufsize, "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, Bufsize, "set logscale y");
+ sarrayAddString(gplot->cmddata, buf, L_COPY);
+ }
+
+ nplots = sarrayGetCount(gplot->datanames);
+ for (i = 0; i < nplots; i++) {
+ plotlabel = sarrayGetString(gplot->plotlabels, i, L_NOCOPY);
+ dataname = sarrayGetString(gplot->datanames, i, L_NOCOPY);
+ numaGetIValue(gplot->plotstyles, i, &plotstyle);
+ if (nplots == 1) {
+ snprintf(buf, Bufsize, "plot '%s' title '%s' %s",
+ dataname, plotlabel, gplotstylenames[plotstyle]);
+ } else {
+ if (i == 0)
+ snprintf(buf, Bufsize, "plot '%s' title '%s' %s, \\",
+ dataname, plotlabel, gplotstylenames[plotstyle]);
+ else if (i < nplots - 1)
+ snprintf(buf, Bufsize, " '%s' title '%s' %s, \\",
+ dataname, plotlabel, gplotstylenames[plotstyle]);
+ else
+ snprintf(buf, Bufsize, " '%s' title '%s' %s",
+ dataname, plotlabel, 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) {
+ LEPT_FREE(cmdstr);
+ return ERROR_INT("cmd stream not opened", procName, 1);
+ }
+ fwrite(cmdstr, 1, strlen(cmdstr), fp);
+ fclose(fp);
+ LEPT_FREE(cmdstr);
+ return 0;
+}
+
+
+/*!
+ * \brief gplotGenDataFiles()
+ *
+ * \param[in] gplot
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The pathnames in the gplot command file are actual pathnames,
+ * which can be in temp directories. Consequently, they must not be
+ * rewritten by calling fopenWriteStream(), and we use fopen().
+ * </pre>
+ */
+l_ok
+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 = fopen(dataname, "w")) == NULL)
+ return ERROR_INT("datafile stream not opened", procName, 1);
+ fwrite(plotdata, 1, strlen(plotdata), fp);
+ fclose(fp);
+ }
+
+ return 0;
+}
+
+
+/*-----------------------------------------------------------------*
+ * Quick one-line plots *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief gplotSimple1()
+ *
+ * \param[in] na numa; plot Y_VS_I
+ * \param[in] outformat GPLOT_PNG, GPLOT_PS, GPLOT_EPS,
+ * GPLOT_LATEX, GPLOT_PNM
+ * \param[in] outroot root of output files
+ * \param[in] title [optional], can be NULL
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates 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.
+ * </pre>
+ */
+l_ok
+gplotSimple1(NUMA *na,
+ l_int32 outformat,
+ const char *outroot,
+ const char *title)
+{
+GPLOT *gplot;
+
+ PROCNAME("gplotSimple1");
+
+ gplot = gplotSimpleXY1(NULL, na, GPLOT_LINES, outformat, outroot, title);
+ if (!gplot)
+ return ERROR_INT("failed to generate plot", procName, 1);
+ gplotDestroy(&gplot);
+ return 0;
+}
+
+
+/*!
+ * \brief gplotSimple2()
+ *
+ * \param[in] na1 numa; plot with Y_VS_I
+ * \param[in] na2 ditto
+ * \param[in] outformat GPLOT_PNG, GPLOT_PS, GPLOT_EPS,
+ * GPLOT_LATEX, GPLOT_PNM
+ * \param[in] outroot root of output files
+ * \param[in] title [optional]
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates 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.
+ * </pre>
+ */
+l_ok
+gplotSimple2(NUMA *na1,
+ NUMA *na2,
+ l_int32 outformat,
+ const char *outroot,
+ const char *title)
+{
+GPLOT *gplot;
+
+ PROCNAME("gplotSimple2");
+
+ gplot = gplotSimpleXY2(NULL, na1, na2, GPLOT_LINES,
+ outformat, outroot, title);
+ if (!gplot)
+ return ERROR_INT("failed to generate plot", procName, 1);
+ gplotDestroy(&gplot);
+ return 0;
+}
+
+
+/*!
+ * \brief gplotSimpleN()
+ *
+ * \param[in] naa numaa; plot Y_VS_I for each numa
+ * \param[in] outformat GPLOT_PNG, GPLOT_PS, GPLOT_EPS,
+ * GPLOT_LATEX, GPLOT_PNM
+ * \param[in] outroot root of output files
+ * \param[in] title [optional]
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates 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.
+ * </pre>
+ */
+l_ok
+gplotSimpleN(NUMAA *naa,
+ l_int32 outformat,
+ const char *outroot,
+ const char *title)
+{
+GPLOT *gplot;
+
+ PROCNAME("gplotSimpleN");
+
+ gplot = gplotSimpleXYN(NULL, naa, GPLOT_LINES, outformat, outroot, title);
+ if (!gplot)
+ return ERROR_INT("failed to generate plot", procName, 1);
+ gplotDestroy(&gplot);
+ return 0;
+}
+
+
+/*!
+ * \brief gplotSimplePix1()
+ *
+ * \param[in] na numa; plot Y_VS_I
+ * \param[in] title [optional], can be NULL
+ * \return pix of plot, or null on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates a line plot of a numa as a pix, where the array
+ * value is plotted vs the array index. The title is optional.
+ * (2) The temporary plot file is a png; its name is generated internally
+ * and stored in gplot.
+ * </pre>
+ */
+PIX *
+gplotSimplePix1(NUMA *na,
+ const char *title)
+{
+char buf[64];
+static l_int32 index;
+GPLOT *gplot;
+PIX *pix;
+
+ PROCNAME("gplotSimplePix1");
+
+ if (!na)
+ return (PIX *)ERROR_PTR("na not defined", procName, NULL);
+
+ lept_mkdir("lept/gplot/pix");
+ snprintf(buf, sizeof(buf), "/tmp/lept/gplot/pix1.%d", index++);
+ gplot = gplotSimpleXY1(NULL, na, GPLOT_LINES, GPLOT_PNG, buf, title);
+ if (!gplot)
+ return (PIX *)ERROR_PTR("failed to generate plot", procName, NULL);
+ pix = pixRead(gplot->outname);
+ gplotDestroy(&gplot);
+ if (!pix)
+ return (PIX *)ERROR_PTR("failed to generate plot", procName, NULL);
+ return pix;
+}
+
+
+/*!
+ * \brief gplotSimplePix2()
+ *
+ * \param[in] na1 numa; plot with Y_VS_I
+ * \param[in] na2 ditto
+ * \param[in] title [optional], can be NULL
+ * \return pix of plot, or null on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates a pix with line plots of two numa, where each of
+ * two arrays is plotted vs the array index. the title is optional.
+ * (2) The temporary plot file is a png; its name is generated internally
+ * and stored in gplot.
+ * </pre>
+ */
+PIX *
+gplotSimplePix2(NUMA *na1,
+ NUMA *na2,
+ const char *title)
+{
+char buf[64];
+static l_int32 index;
+GPLOT *gplot;
+PIX *pix;
+
+ PROCNAME("gplotSimplePix2");
+
+ if (!na1 || !na2)
+ return (PIX *)ERROR_PTR("both na1, na2 not defined", procName, NULL);
+
+ lept_mkdir("lept/gplot/pix");
+ snprintf(buf, sizeof(buf), "/tmp/lept/gplot/pix2.%d", index++);
+ gplot = gplotSimpleXY2(NULL, na1, na2, GPLOT_LINES, GPLOT_PNG, buf, title);
+ if (!gplot)
+ return (PIX *)ERROR_PTR("failed to generate plot", procName, NULL);
+ pix = pixRead(gplot->outname);
+ gplotDestroy(&gplot);
+ if (!pix)
+ return (PIX *)ERROR_PTR("failed to generate plot", procName, NULL);
+ return pix;
+}
+
+
+/*!
+ * \brief gplotSimplePixN()
+ *
+ * \param[in] naa numaa; plot Y_VS_I for each numa
+ * \param[in] title [optional], can be NULL
+ * \return pix of plot, or null on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates a pix with an arbitrary number of line plots,
+ * each coming from a numa in %naa. Each array value is plotted
+ * vs the array index. The title is optional.
+ * (2) The temporary plot file is a png; its name is generated internally
+ * and stored in gplot.
+ * </pre>
+ */
+PIX *
+gplotSimplePixN(NUMAA *naa,
+ const char *title)
+{
+char buf[64];
+static l_int32 index;
+GPLOT *gplot;
+PIX *pix;
+
+ PROCNAME("gplotSimplePixN");
+
+ if (!naa)
+ return (PIX *)ERROR_PTR("naa not defined", procName, NULL);
+
+ lept_mkdir("lept/gplot/pix");
+ snprintf(buf, sizeof(buf), "/tmp/lept/gplot/pixN.%d", index++);
+ gplot = gplotSimpleXYN(NULL, naa, GPLOT_LINES, GPLOT_PNG, buf, title);
+ if (!gplot)
+ return (PIX *)ERROR_PTR("failed to generate plot", procName, NULL);
+ pix = pixRead(gplot->outname);
+ gplotDestroy(&gplot);
+ if (!pix)
+ return (PIX *)ERROR_PTR("failed to generate plot", procName, NULL);
+ return pix;
+}
+
+
+/*!
+ * \brief gplotSimpleXY1()
+ *
+ * \param[in] nax [optional]
+ * \param[in] nay [required]
+ * \param[in] plotstyle GPLOT_LINES, GPLOT_POINTS, GPLOT_IMPULSES,
+ * GPLOT_LINESPOINTS, GPLOT_DOTS
+ * \param[in] outformat GPLOT_PNG, GPLOT_PS, GPLOT_EPS,
+ * GPLOT_LATEX, GPLOT_PNM
+ * \param[in] outroot root of output files
+ * \param[in] title [optional], can be NULL
+ * \return gplot or null on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates 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.
+ * (5) The returned gplot must be destroyed by the caller.
+ * </pre>
+ */
+GPLOT *
+gplotSimpleXY1(NUMA *nax,
+ NUMA *nay,
+ l_int32 plotstyle,
+ l_int32 outformat,
+ const char *outroot,
+ const char *title)
+{
+GPLOT *gplot;
+
+ PROCNAME("gplotSimpleXY1");
+
+ if (!nay)
+ return (GPLOT *)ERROR_PTR("nay not defined", procName, NULL);
+ if (plotstyle < 0 || plotstyle >= NUM_GPLOT_STYLES)
+ return (GPLOT *)ERROR_PTR("invalid plotstyle", procName, NULL);
+ if (outformat != GPLOT_PNG && outformat != GPLOT_PS &&
+ outformat != GPLOT_EPS && outformat != GPLOT_LATEX &&
+ outformat != GPLOT_PNM)
+ return (GPLOT *)ERROR_PTR("invalid outformat", procName, NULL);
+ if (!outroot)
+ return (GPLOT *)ERROR_PTR("outroot not specified", procName, NULL);
+
+ if ((gplot = gplotCreate(outroot, outformat, title, NULL, NULL)) == 0)
+ return (GPLOT *)ERROR_PTR("gplot not made", procName, NULL);
+ gplotAddPlot(gplot, nax, nay, plotstyle, NULL);
+ gplotMakeOutput(gplot);
+ return gplot;
+}
+
+
+/*!
+ * \brief gplotSimpleXY2()
+ *
+ * \param[in] nax [optional], can be NULL
+ * \param[in] nay1
+ * \param[in] nay2
+ * \param[in] plotstyle GPLOT_LINES, GPLOT_POINTS, GPLOT_IMPULSES,
+ * GPLOT_LINESPOINTS, GPLOT_DOTS
+ * \param[in] outformat GPLOT_PNG, GPLOT_PS, GPLOT_EPS,
+ * GPLOT_LATEX, GPLOT_PNM
+ * \param[in] outroot root of output files
+ * \param[in] title [optional]
+ * \return gplot or null on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates 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.
+ * (5) The returned gplot must be destroyed by the caller.
+ * </pre>
+ */
+GPLOT *
+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 (GPLOT *)ERROR_PTR("nay1 and nay2 not both defined",
+ procName, NULL);
+ if (plotstyle < 0 || plotstyle >= NUM_GPLOT_STYLES)
+ return (GPLOT *)ERROR_PTR("invalid plotstyle", procName, NULL);
+ if (outformat != GPLOT_PNG && outformat != GPLOT_PS &&
+ outformat != GPLOT_EPS && outformat != GPLOT_LATEX &&
+ outformat != GPLOT_PNM)
+ return (GPLOT *)ERROR_PTR("invalid outformat", procName, NULL);
+ if (!outroot)
+ return (GPLOT *)ERROR_PTR("outroot not specified", procName, NULL);
+
+ if ((gplot = gplotCreate(outroot, outformat, title, NULL, NULL)) == 0)
+ return (GPLOT *)ERROR_PTR("gplot not made", procName, NULL);
+ gplotAddPlot(gplot, nax, nay1, plotstyle, NULL);
+ gplotAddPlot(gplot, nax, nay2, plotstyle, NULL);
+ gplotMakeOutput(gplot);
+ return gplot;
+}
+
+
+/*!
+ * \brief gplotSimpleXYN()
+ *
+ * \param[in] nax [optional]; can be NULL
+ * \param[in] naay numaa of arrays to plot against %nax
+ * \param[in] plotstyle GPLOT_LINES, GPLOT_POINTS, GPLOT_IMPULSES,
+ * GPLOT_LINESPOINTS, GPLOT_DOTS
+ * \param[in] outformat GPLOT_PNG, GPLOT_PS, GPLOT_EPS,
+ * GPLOT_LATEX, GPLOT_PNM
+ * \param[in] outroot root of output files
+ * \param[in] title [optional]
+ * \return gplot or null on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates 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.
+ * (5) The returned gplot must be destroyed by the caller.
+ * </pre>
+ */
+GPLOT *
+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 (GPLOT *)ERROR_PTR("naay not defined", procName, NULL);
+ if ((n = numaaGetCount(naay)) == 0)
+ return (GPLOT *)ERROR_PTR("no numa in array", procName, NULL);
+ if (plotstyle < 0 || plotstyle >= NUM_GPLOT_STYLES)
+ return (GPLOT *)ERROR_PTR("invalid plotstyle", procName, NULL);
+ if (outformat != GPLOT_PNG && outformat != GPLOT_PS &&
+ outformat != GPLOT_EPS && outformat != GPLOT_LATEX &&
+ outformat != GPLOT_PNM)
+ return (GPLOT *)ERROR_PTR("invalid outformat", procName, NULL);
+ if (!outroot)
+ return (GPLOT *)ERROR_PTR("outroot not specified", procName, NULL);
+
+ if ((gplot = gplotCreate(outroot, outformat, title, NULL, NULL)) == 0)
+ return (GPLOT *)ERROR_PTR("gplot not made", procName, NULL);
+ for (i = 0; i < n; i++) {
+ nay = numaaGetNuma(naay, i, L_CLONE);
+ gplotAddPlot(gplot, nax, nay, plotstyle, NULL);
+ numaDestroy(&nay);
+ }
+ gplotMakeOutput(gplot);
+ return gplot;
+}
+
+
+/*!
+ * \brief gplotGeneralPix1()
+ *
+ * \param[in] na data array
+ * \param[in] plotstyle GPLOT_LINES, GPLOT_POINTS, GPLOT_IMPULSES,
+ * GPLOT_LINESPOINTS, GPLOT_DOTS
+ * \param[in] rootname root for all output files
+ * \param[in] title [optional] overall title
+ * \param[in] xlabel [optional] x axis label
+ * \param[in] ylabel [optional] y axis label
+ * \return pix of plot, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The 'title', 'xlabel' and 'ylabel' strings can have spaces,
+ * double quotes and backquotes, but not single quotes.
+ * </pre>
+ */
+PIX *
+gplotGeneralPix1(NUMA *na,
+ l_int32 plotstyle,
+ const char *rootname,
+ const char *title,
+ const char *xlabel,
+ const char *ylabel)
+{
+GPLOT *gplot;
+PIX *pix;
+
+ PROCNAME("gplotGeneralPix1");
+
+ if (!na)
+ return (PIX *)ERROR_PTR("na not defined", procName, NULL);
+ if (plotstyle < 0 || plotstyle >= NUM_GPLOT_STYLES)
+ return (PIX *)ERROR_PTR("invalid plotstyle", procName, NULL);
+ if (!rootname)
+ return (PIX *)ERROR_PTR("rootname not defined", procName, NULL);
+
+ gplot = gplotCreate(rootname, GPLOT_PNG, title, xlabel, ylabel);
+ if (!gplot)
+ return (PIX *)ERROR_PTR("gplot not made", procName, NULL);
+ gplotAddPlot(gplot, NULL, na, plotstyle, NULL);
+ pix = gplotMakeOutputPix(gplot);
+ gplotDestroy(&gplot);
+ return pix;
+}
+
+
+/*!
+ * \brief gplotGeneralPix2()
+ *
+ * \param[in] na1 x-axis data array
+ * \param[in] na2 y-axis data array
+ * \param[in] plotstyle GPLOT_LINES, GPLOT_POINTS, GPLOT_IMPULSES,
+ * GPLOT_LINESPOINTS, GPLOT_DOTS
+ * \param[in] rootname root for all output files
+ * \param[in] title [optional] overall title
+ * \param[in] xlabel [optional] x axis label
+ * \param[in] ylabel [optional] y axis label
+ * \return pix of plot, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The 'title', 'xlabel' and 'ylabel' strings can have spaces,
+ * double quotes and backquotes, but not single quotes.
+ * </pre>
+ */
+PIX *
+gplotGeneralPix2(NUMA *na1,
+ NUMA *na2,
+ l_int32 plotstyle,
+ const char *rootname,
+ const char *title,
+ const char *xlabel,
+ const char *ylabel)
+{
+GPLOT *gplot;
+PIX *pix;
+
+ PROCNAME("gplotGeneralPix2");
+
+ if (!na1)
+ return (PIX *)ERROR_PTR("na1 not defined", procName, NULL);
+ if (!na2)
+ return (PIX *)ERROR_PTR("na2 not defined", procName, NULL);
+ if (plotstyle < 0 || plotstyle >= NUM_GPLOT_STYLES)
+ return (PIX *)ERROR_PTR("invalid plotstyle", procName, NULL);
+ if (!rootname)
+ return (PIX *)ERROR_PTR("rootname not defined", procName, NULL);
+
+ gplot = gplotCreate(rootname, GPLOT_PNG, title, xlabel, ylabel);
+ if (!gplot)
+ return (PIX *)ERROR_PTR("gplot not made", procName, NULL);
+ gplotAddPlot(gplot, na1, na2, plotstyle, NULL);
+ pix = gplotMakeOutputPix(gplot);
+ gplotDestroy(&gplot);
+ return pix;
+}
+
+
+/*!
+ * \brief gplotGeneralPixN()
+ *
+ * \param[in] nax x-axis data array
+ * \param[in] naay array of y-axis data arrays
+ * \param[in] plotstyle GPLOT_LINES, GPLOT_POINTS, GPLOT_IMPULSES,
+ * GPLOT_LINESPOINTS, GPLOT_DOTS
+ * \param[in] rootname root for all output files
+ * \param[in] title [optional] overall title
+ * \param[in] xlabel [optional] x axis label
+ * \param[in] ylabel [optional] y axis label
+ * \return pix of plot, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The 'title', 'xlabel' and 'ylabel' strings can have spaces,
+ * double quotes and backquotes, but not single quotes.
+ * </pre>
+ */
+PIX *
+gplotGeneralPixN(NUMA *nax,
+ NUMAA *naay,
+ l_int32 plotstyle,
+ const char *rootname,
+ const char *title,
+ const char *xlabel,
+ const char *ylabel)
+{
+l_int32 i, n;
+GPLOT *gplot;
+NUMA *nay;
+PIX *pix;
+
+ PROCNAME("gplotGeneralPixN");
+
+ if (!nax)
+ return (PIX *)ERROR_PTR("nax not defined", procName, NULL);
+ if (!naay)
+ return (PIX *)ERROR_PTR("naay not defined", procName, NULL);
+ if ((n = numaaGetCount(naay)) == 0)
+ return (PIX *)ERROR_PTR("no numa in array", procName, NULL);
+ if (plotstyle < 0 || plotstyle >= NUM_GPLOT_STYLES)
+ return (PIX *)ERROR_PTR("invalid plotstyle", procName, NULL);
+ if (!rootname)
+ return (PIX *)ERROR_PTR("rootname not defined", procName, NULL);
+
+ gplot = gplotCreate(rootname, GPLOT_PNG, title, xlabel, ylabel);
+ if (!gplot)
+ return (PIX *)ERROR_PTR("gplot not made", procName, NULL);
+ for (i = 0; i < n; i++) {
+ nay = numaaGetNuma(naay, i, L_CLONE);
+ gplotAddPlot(gplot, nax, nay, plotstyle, NULL);
+ numaDestroy(&nay);
+ }
+ pix = gplotMakeOutputPix(gplot);
+ gplotDestroy(&gplot);
+ return pix;
+}
+
+
+/*-----------------------------------------------------------------*
+ * Serialize for I/O *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief gplotRead()
+ *
+ * \param[in] filename
+ * \return gplot, or NULL on error
+ */
+GPLOT *
+gplotRead(const char *filename)
+{
+char buf[Bufsize];
+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: %511s\n", buf); /* Bufsize - 1 */
+ rootname = stringNew(buf);
+ ignore = fscanf(fp, "Output format: %d\n", &outformat);
+ ignores = fgets(buf, Bufsize, fp); /* Title: ... */
+ title = stringNew(buf + 7);
+ title[strlen(title) - 1] = '\0';
+ ignores = fgets(buf, Bufsize, fp); /* X axis label: ... */
+ xlabel = stringNew(buf + 14);
+ xlabel[strlen(xlabel) - 1] = '\0';
+ ignores = fgets(buf, Bufsize, fp); /* Y axis label: ... */
+ ylabel = stringNew(buf + 14);
+ ylabel[strlen(ylabel) - 1] = '\0';
+
+ gplot = gplotCreate(rootname, outformat, title, xlabel, ylabel);
+ LEPT_FREE(rootname);
+ LEPT_FREE(title);
+ LEPT_FREE(xlabel);
+ LEPT_FREE(ylabel);
+ if (!gplot) {
+ fclose(fp);
+ return (GPLOT *)ERROR_PTR("gplot not made", procName, NULL);
+ }
+ sarrayDestroy(&gplot->cmddata);
+ sarrayDestroy(&gplot->datanames);
+ sarrayDestroy(&gplot->plotdata);
+ sarrayDestroy(&gplot->plotlabels);
+ numaDestroy(&gplot->plotstyles);
+
+ ignore = fscanf(fp, "Commandfile name: %511s\n", buf); /* Bufsize - 1 */
+ 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->plotlabels = 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: %511s\n", buf);
+ stringReplace(&gplot->outname, buf);
+ ignore = fscanf(fp, "Axis scaling: %d\n", &gplot->scaling);
+
+ fclose(fp);
+ return gplot;
+}
+
+
+/*!
+ * \brief gplotWrite()
+ *
+ * \param[in] filename
+ * \param[in] gplot
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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->plotlabels);
+ fprintf(fp, "\nPlot styles:");
+ numaWriteStderr(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/leptonica/src/gplot.h b/leptonica/src/gplot.h
new file mode 100644
index 00000000..d2e4f7e5
--- /dev/null
+++ b/leptonica/src/gplot.h
@@ -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.
+ *====================================================================*/
+
+#ifndef LEPTONICA_GPLOT_H
+#define LEPTONICA_GPLOT_H
+
+/*!
+ * \file gplot.h
+ *
+ * <pre>
+ * Data structures and parameters for generating gnuplot files
+ *
+ * We used to support X11 output, but recent versions of gnuplot do not
+ * support the X11 terminal. To get display to your screen, use
+ * GPLOT_PNG output; e.g.,
+ * gplotSimple1(na, GPLOT_PNG, "/tmp/someroot", ...);
+ * l_fileDisplay("/tmp/someroot.png", ...);
+ * </pre>
+ */
+
+#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_LATEX = 4,
+ GPLOT_PNM = 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 *gplotfileoutputs[]; /*!< used in simple file input */
+
+/*! Data structure for generating gnuplot files */
+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 *plotlabels; /*!< label 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/leptonica/src/graphics.c b/leptonica/src/graphics.c
new file mode 100644
index 00000000..44e879be
--- /dev/null
+++ b/leptonica/src/graphics.c
@@ -0,0 +1,2910 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file graphics.c
+ * <pre>
+ *
+ * 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 pixRenderHashMaskArb()
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+/*------------------------------------------------------------------*
+ * Pta generation for arbitrary shapes built with lines *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief generatePtaLine()
+ *
+ * \param[in] x1, y1 end point 1
+ * \param[in] x2, y2 end point 2
+ * \return pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Uses Bresenham line drawing, which results in an 8-connected line.
+ * </pre>
+ */
+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 */
+ getyofx = TRUE;
+ 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;
+}
+
+
+/*!
+ * \brief generatePtaWideLine()
+ *
+ * \param[in] x1, y1 end point 1
+ * \param[in] x2, y2 end point 2
+ * \param[in] 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) {
+ 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) {
+ ptaJoin(ptaj, pta, 0, -1);
+ ptaDestroy(&pta);
+ }
+ }
+ }
+
+ return ptaj;
+}
+
+
+/*!
+ * \brief generatePtaBox()
+ *
+ * \param[in] box
+ * \param[in] width of line
+ * \return ptad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Because the box is constructed so that we don't have any
+ * overlapping lines, there is no need to remove duplicates.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief generatePtaBoxa()
+ *
+ * \param[in] boxa
+ * \param[in] width
+ * \param[in] removedups 1 to remove, 0 to leave
+ * \return ptad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief generatePtaHashBox()
+ *
+ * \param[in] box
+ * \param[in] spacing spacing between lines; must be > 1
+ * \param[in] width of line
+ * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
+ * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ * L_NEG_SLOPE_LINE
+ * \param[in] outline 0 to skip drawing box outline
+ * \return ptad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief generatePtaHashBoxa()
+ *
+ * \param[in] boxa
+ * \param[in] spacing spacing between lines; must be > 1
+ * \param[in] width of line
+ * \param[in] orient orientation of lines: L_HORIZONTAL_LINE, ...
+ * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
+ * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ * L_NEG_SLOPE_LINE
+ * \param[in] outline 0 to skip drawing box outline
+ * \param[in] removedups 1 to remove, 0 to leave
+ * \return ptad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief generatePtaaBoxa()
+ *
+ * \param[in] boxa
+ * \return ptaa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief generatePtaaHashBoxa()
+ *
+ * \param[in] boxa
+ * \param[in] spacing spacing between hash lines; must be > 1
+ * \param[in] width hash line width
+ * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
+ * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ * L_NEG_SLOPE_LINE
+ * \param[in] outline 0 to skip drawing box outline
+ * \return ptaa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ *
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief generatePtaPolyline()
+ *
+ * \param[in] ptas vertices of polyline
+ * \param[in] width
+ * \param[in] closeflag 1 to close the contour; 0 otherwise
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief generatePtaGrid()
+ *
+ * \param[in] w, h of region where grid will be displayed
+ * \param[in] nx, ny number of rectangles in each direction in grid
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief convertPtaLineTo4cc()
+ *
+ * \param[in] ptas 8-connected line of points
+ * \return ptad 4-connected line, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief generatePtaFilledCircle()
+ *
+ * \param[in] radius
+ * \return pta, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief generatePtaFilledSquare()
+ *
+ * \param[in] side
+ * \return pta, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief generatePtaLineFromPt()
+ *
+ * \param[in] x, y point of origination
+ * \param[in] length of line, including starting point
+ * \param[in] radang angle in radians, CW from horizontal
+ * \return pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) %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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief locatePtRadially()
+ *
+ * \param[in] xr, yr reference point
+ * \param[in] radang angle in radians, CW from horizontal
+ * \param[in] dist distance of point from reference point along
+ * line given by the specified angle
+ * \param[out] px, py location of point
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRenderPlotFromNuma()
+ *
+ * \param[in,out] ppix any type; replaced if not 32 bpp rgb
+ * \param[in] na to be plotted
+ * \param[in] plotloc location of plot: L_PLOT_AT_TOP, etc
+ * \param[in] linewidth width of "line" that is drawn; between 1 and 7
+ * \param[in] max maximum excursion in pixels from baseline
+ * \param[in] color plot color: 0xrrggbb00
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief makePlotPtaFromNuma()
+ *
+ * \param[in] na
+ * \param[in] size pix height for horizontal plot; pix width
+ * for vertical plot
+ * \param[in] plotloc location of plot: L_PLOT_AT_TOP, etc
+ * \param[in] linewidth width of "line" that is drawn; between 1 and 7
+ * \param[in] max maximum excursion in pixels from baseline
+ * \return ptad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixRenderPlotFromNumaGen()
+ *
+ * \param[in,out] ppix any type; replaced if not 32 bpp rgb
+ * \param[in] na to be plotted
+ * \param[in] orient L_HORIZONTAL_LINE, L_VERTICAL_LINE
+ * \param[in] linewidth width of "line" that is drawn; between 1 and 7
+ * \param[in] refpos reference position: y for horizontal;
+ * x for vertical
+ * \param[in] max maximum excursion in pixels from baseline
+ * \param[in] drawref 1 to draw the reference line and its normal
+ * \param[in] color plot color: 0xrrggbb00
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief makePlotPtaFromNumaGen()
+ *
+ * \param[in] na
+ * \param[in] orient L_HORIZONTAL_LINE, L_VERTICAL_LINE
+ * \param[in] linewidth width of "line" that is drawn; between 1 and 7
+ * \param[in] refpos reference position: y for horizontal;
+ * x for vertical
+ * \param[in] max maximum excursion in pixels from baseline
+ * \param[in] drawref 1 to draw the reference line and its normal
+ * \return ptad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRenderPta()
+ *
+ * \param[in] pix any depth, not cmapped
+ * \param[in] pta arbitrary set of points
+ * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+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 (pixGetColormap(pix))
+ return ERROR_INT("pix is colormapped", 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;
+}
+
+
+/*!
+ * \brief pixRenderPtaArb()
+ *
+ * \param[in] pix any depth, cmapped ok
+ * \param[in] pta arbitrary set of points
+ * \param[in] rval, gval, bval
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %pix is colormapped, render this color (or the nearest
+ * color if the cmap is full) on each pixel.
+ * (2) The rgb components have the standard dynamic range [0 ... 255]
+ * (3) 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
+ * (4) This function clips the rendering to %pix.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderPtaBlend()
+ *
+ * \param[in] pix 32 bpp rgb
+ * \param[in] pta arbitrary set of points
+ * \param[in] rval, gval, bval
+ * \param[in] fract
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function clips the rendering to %pix.
+ * </pre>
+ */
+l_ok
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRenderLine()
+ *
+ * \param[in] pix any depth, not cmapped
+ * \param[in] x1, y1
+ * \param[in] x2, y2
+ * \param[in] width thickness of line
+ * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderLineArb()
+ *
+ * \param[in] pix any depth, cmapped ok
+ * \param[in] x1, y1
+ * \param[in] x2, y2
+ * \param[in] width thickness of line
+ * \param[in] rval, gval, bval
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderLineBlend()
+ *
+ * \param[in] pix 32 bpp rgb
+ * \param[in] x1, y1
+ * \param[in] x2, y2
+ * \param[in] width thickness of line
+ * \param[in] rval, gval, bval
+ * \param[in] fract
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderBox()
+ *
+ * \param[in] pix any depth, not cmapped
+ * \param[in] box
+ * \param[in] width thickness of box lines
+ * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderBoxArb()
+ *
+ * \param[in] pix any depth, cmapped ok
+ * \param[in] box
+ * \param[in] width thickness of box lines
+ * \param[in] rval, gval, bval
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderBoxBlend()
+ *
+ * \param[in] pix 32 bpp rgb
+ * \param[in] box
+ * \param[in] width thickness of box lines
+ * \param[in] rval, gval, bval
+ * \param[in] fract in [0.0 - 1.0]: 1.0 is no transparency;
+ * 0.0 is complete transparency (no effect)
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderBoxa()
+ *
+ * \param[in] pix any depth, not cmapped
+ * \param[in] boxa
+ * \param[in] width thickness of line
+ * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderBoxaArb()
+ *
+ * \param[in] pix any depth; colormapped is ok
+ * \param[in] boxa
+ * \param[in] width thickness of line
+ * \param[in] rval, gval, bval
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderBoxaBlend()
+ *
+ * \param[in] pix 32 bpp rgb
+ * \param[in] boxa
+ * \param[in] width thickness of line
+ * \param[in] rval, gval, bval
+ * \param[in] fract in [0.0 - 1.0]: 1.0 is no transparency;
+ * 0.0 is complete transparency (no effect)
+ * \param[in] removedups 1 to remove; 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderHashBox()
+ *
+ * \param[in] pix any depth, not cmapped
+ * \param[in] box
+ * \param[in] spacing spacing between lines; must be > 1
+ * \param[in] width thickness of box and hash lines
+ * \param[in] orient orientation of lines: L_HORIZONTAL_LINE, ...
+ * \param[in] outline 0 to skip drawing box outline
+ * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderHashBoxArb()
+ *
+ * \param[in] pix any depth; cmapped ok
+ * \param[in] box
+ * \param[in] spacing spacing between lines; must be > 1
+ * \param[in] width thickness of box and hash lines
+ * \param[in] orient orientation of lines: L_HORIZONTAL_LINE, ...
+ * \param[in] outline 0 to skip drawing box outline
+ * \param[in] rval, gval, bval
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderHashBoxBlend()
+ *
+ * \param[in] pix 32 bpp
+ * \param[in] box
+ * \param[in] spacing spacing between lines; must be > 1
+ * \param[in] width thickness of box and hash lines
+ * \param[in] orient orientation of lines: L_HORIZONTAL_LINE, ...
+ * \param[in] outline 0 to skip drawing box outline
+ * \param[in] rval, gval, bval
+ * \param[in] fract in [0.0 - 1.0]: 1.0 is no transparency;
+ * 0.0 is complete transparency (no effect)
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderHashMaskArb()
+ *
+ * \param[in] pix any depth; cmapped ok
+ * \param[in] pixm 1 bpp clipping mask for hash marks
+ * \param[in] x,y UL corner of %pixm with respect to %pix
+ * \param[in] spacing spacing between lines; must be > 1
+ * \param[in] width thickness of box and hash lines
+ * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
+ * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ * L_NEG_SLOPE_LINE
+ * \param[in] outline 0 to skip drawing box outline
+ * \param[in] rval, gval, bval
+ * \return 0 if OK, 1 on error
+ * <pre>
+ * Notes:
+ * (1) This is an in-place operation that renders hash lines
+ * through a mask %pixm onto %pix. The mask origin is
+ * translated by (%x,%y) relative to the origin of %pix.
+ * </pre>
+ */
+l_ok
+pixRenderHashMaskArb(PIX *pix,
+ PIX *pixm,
+ l_int32 x,
+ l_int32 y,
+ l_int32 spacing,
+ l_int32 width,
+ l_int32 orient,
+ l_int32 outline,
+ l_int32 rval,
+ l_int32 gval,
+ l_int32 bval)
+{
+l_int32 w, h;
+BOX *box1, *box2;
+PIX *pix1;
+PTA *pta1, *pta2;
+
+ PROCNAME("pixRenderHashMaskArb");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+ if (!pixm || pixGetDepth(pixm) != 1)
+ return ERROR_INT("pixm not defined or not 1 bpp", 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);
+
+ /* Get the points for masked hash lines */
+ pixGetDimensions(pixm, &w, &h, NULL);
+ box1 = boxCreate(0, 0, w, h);
+ pta1 = generatePtaHashBox(box1, spacing, width, orient, outline);
+ pta2 = ptaCropToMask(pta1, pixm);
+ boxDestroy(&box1);
+ ptaDestroy(&pta1);
+
+ /* Clip out the region and apply the hash lines */
+ box2 = boxCreate(x, y, w, h);
+ pix1 = pixClipRectangle(pix, box2, NULL);
+ pixRenderPtaArb(pix1, pta2, rval, gval, bval);
+ ptaDestroy(&pta2);
+ boxDestroy(&box2);
+
+ /* Rasterop the altered rectangle back in place */
+ pixRasterop(pix, x, y, w, h, PIX_SRC, pix1, 0, 0);
+ pixDestroy(&pix1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixRenderHashBoxa()
+ *
+ * \param[in] pix any depth, not cmapped
+ * \param[in] boxa
+ * \param[in] spacing spacing between lines; must be > 1
+ * \param[in] width thickness of box and hash lines
+ * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
+ * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ * L_NEG_SLOPE_LINE
+ * \param[in] outline 0 to skip drawing box outline
+ * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderHashBoxaArb()
+ *
+ * \param[in] pix any depth; cmapped ok
+ * \param[in] box
+ * \param[in] spacing spacing between lines; must be > 1
+ * \param[in] width thickness of box and hash lines
+ * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
+ * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ * L_NEG_SLOPE_LINE
+ * \param[in] outline 0 to skip drawing box outline
+ * \param[in] rval, gval, bval
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderHashBoxaBlend()
+ *
+ * \param[in] pix 32 bpp rgb
+ * \param[in] boxa
+ * \param[in] spacing spacing between lines; must be > 1
+ * \param[in] width thickness of box and hash lines
+ * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
+ * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ * L_NEG_SLOPE_LINE
+ * \param[in] outline 0 to skip drawing box outline
+ * \param[in] rval, gval, bval
+ * \param[in] fract in [0.0 - 1.0]: 1.0 is no transparency;
+ * 0.0 is complete transparency (no effect)
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderPolyline()
+ *
+ * \param[in] pix any depth, not cmapped
+ * \param[in] ptas
+ * \param[in] width thickness of line
+ * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \param[in] closeflag 1 to close the contour; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * This renders a closed contour.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderPolylineArb()
+ *
+ * \param[in] pix any depth; cmapped ok
+ * \param[in] ptas
+ * \param[in] width thickness of line
+ * \param[in] rval, gval, bval
+ * \param[in] closeflag 1 to close the contour; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * This renders a closed contour.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderPolylineBlend()
+ *
+ * \param[in] pix 32 bpp rgb
+ * \param[in] ptas
+ * \param[in] width thickness of line
+ * \param[in] rval, gval, bval
+ * \param[in] fract in [0.0 - 1.0]: 1.0 is no transparency;
+ * 0.0 is complete transparency (no effect)
+ * \param[in] closeflag 1 to close the contour; 0 otherwise
+ * \param[in] removedups 1 to remove; 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderGridArb()
+ *
+ * \param[in] pix any depth, cmapped ok
+ * \param[in] nx, ny number of rectangles in each direction
+ * \param[in] width thickness of grid lines
+ * \param[in] rval, gval, bval
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRenderRandomCmapPtaa()
+ *
+ * \param[in] pix 1, 2, 4, 8, 16, 32 bpp
+ * \param[in] ptaa
+ * \param[in] polyflag 1 to interpret each Pta as a polyline;
+ * 0 to simply render the Pta as a set of pixels
+ * \param[in] width thickness of line; use only for polyline
+ * \param[in] closeflag 1 to close the contour; 0 otherwise;
+ * use only for polyline mode
+ * \return pixd cmapped, 8 bpp or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRenderPolygon()
+ *
+ * \param[in] ptas of vertices, none repeated
+ * \param[in] width of polygon outline
+ * \param[out] pxmin [optional] min x value of input pts
+ * \param[out] pymin [optional] min y value of input pts
+ * \return pix 1 bpp, with outline generated, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFillPolygon()
+ *
+ * \param[in] pixs 1 bpp, with 4-connected polygon outline
+ * \param[in] pta vertices of the polygon
+ * \param[in] xmin, ymin min values of vertices of polygon
+ * \return pixd with outline filled, or NULL on error
+ *
+ * <pre>
+ * 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 %pta:
+ * PIX *pixt = pixRenderPolygon(pta, 1, &xmin, &ymin);
+ * PIX *pixd = pixFillPolygon(pixt, pta, xmin, ymin);
+ * pixDestroy(&pixt);
+ * </pre>
+ */
+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);
+ if (ptaGetCount(pta) < 2)
+ return (PIX *)ERROR_PTR("pta has < 2 pts", procName, NULL);
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ xstart = (l_int32 *)LEPT_CALLOC(L_MAX(1, w / 2), sizeof(l_int32));
+ xend = (l_int32 *)LEPT_CALLOC(L_MAX(1, w / 2), sizeof(l_int32));
+ if (!xstart || !xend) {
+ LEPT_FREE(xstart);
+ LEPT_FREE(xend);
+ return (PIX *)ERROR_PTR("xstart and xend not made", procName, NULL);
+ }
+
+ /* 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRenderContours()
+ *
+ * \param[in] pixs 8 or 16 bpp; no colormap
+ * \param[in] startval value of lowest contour; must be in [0 ... maxval]
+ * \param[in] incr increment to next contour; must be > 0
+ * \param[in] outdepth either 1 or depth of pixs
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief fpixAutoRenderContours()
+ *
+ * \param[in] fpix
+ * \param[in] ncontours in [2 ... 500]; typically about 50
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.15f);
+}
+
+
+/*!
+ * \brief fpixRenderContours()
+ *
+ * \param[in] fpixs
+ * \param[in] incr increment between contours; must be > 0.0
+ * \param[in] proxim required proximity to target value; default 0.15
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.15f; /* 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixGeneratePtaBoundary()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] width of boundary line
+ * \return pta, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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/leptonica/src/graymorph.c b/leptonica/src/graymorph.c
new file mode 100644
index 00000000..1d7440ce
--- /dev/null
+++ b/leptonica/src/graymorph.c
@@ -0,0 +1,1376 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file graymorph.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixErodeGray()
+ *
+ * \param[in] pixs
+ * \param[in] hsize of Sel; must be odd; origin implicitly in center
+ * \param[in] vsize ditto
+ * \return pixd
+ *
+ * <pre>
+ * Notes:
+ * (1) Sel is a brick with all elements being hits
+ * (2) If hsize = vsize = 1, just returns a copy.
+ * </pre>
+ */
+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++;
+ }
+
+ pixb = pixt = pixd = NULL;
+ buffer = minarray = NULL;
+
+ 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;
+ }
+
+ pixb = pixAddBorderGeneral(pixs, leftpix, rightpix, toppix, bottompix, 255);
+ pixt = pixCreateTemplate(pixb);
+ if (!pixb || !pixt) {
+ L_ERROR("pixb and pixt not made\n", procName);
+ goto cleanup;
+ }
+
+ pixGetDimensions(pixt, &w, &h, NULL);
+ datab = pixGetData(pixb);
+ datat = pixGetData(pixt);
+ wplb = pixGetWpl(pixb);
+ wplt = pixGetWpl(pixt);
+
+ buffer = (l_uint8 *)LEPT_CALLOC(L_MAX(w, h), sizeof(l_uint8));
+ maxsize = L_MAX(hsize, vsize);
+ minarray = (l_uint8 *)LEPT_CALLOC(2 * maxsize, sizeof(l_uint8));
+ if (!buffer || !minarray) {
+ L_ERROR("buffer and minarray not made\n", procName);
+ goto cleanup;
+ }
+
+ 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);
+ }
+
+ pixd = pixRemoveBorderGeneral(pixt, leftpix, rightpix, toppix, bottompix);
+ if (!pixd)
+ L_ERROR("pixd not made\n", procName);
+
+cleanup:
+ LEPT_FREE(buffer);
+ LEPT_FREE(minarray);
+ pixDestroy(&pixb);
+ pixDestroy(&pixt);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixDilateGray()
+ *
+ * \param[in] pixs
+ * \param[in] hsize of Sel; must be odd; origin implicitly in center
+ * \param[in] vsize ditto
+ * \return pixd
+ *
+ * <pre>
+ * Notes:
+ * (1) Sel is a brick with all elements being hits
+ * (2) If hsize = vsize = 1, just returns a copy.
+ * </pre>
+ */
+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++;
+ }
+
+ pixb = pixt = pixd = NULL;
+ buffer = maxarray = NULL;
+
+ 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;
+ }
+
+ pixb = pixAddBorderGeneral(pixs, leftpix, rightpix, toppix, bottompix, 0);
+ pixt = pixCreateTemplate(pixb);
+ if (!pixb || !pixt) {
+ L_ERROR("pixb and pixt not made\n", procName);
+ goto cleanup;
+ }
+
+ pixGetDimensions(pixt, &w, &h, NULL);
+ datab = pixGetData(pixb);
+ datat = pixGetData(pixt);
+ wplb = pixGetWpl(pixb);
+ wplt = pixGetWpl(pixt);
+
+ buffer = (l_uint8 *)LEPT_CALLOC(L_MAX(w, h), sizeof(l_uint8));
+ maxsize = L_MAX(hsize, vsize);
+ maxarray = (l_uint8 *)LEPT_CALLOC(2 * maxsize, sizeof(l_uint8));
+ if (!buffer || !maxarray) {
+ L_ERROR("buffer and maxarray not made\n", procName);
+ goto cleanup;
+ }
+
+ 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);
+ }
+
+ pixd = pixRemoveBorderGeneral(pixt, leftpix, rightpix, toppix, bottompix);
+ if (!pixd)
+ L_ERROR("pixd not made\n", procName);
+
+cleanup:
+ LEPT_FREE(buffer);
+ LEPT_FREE(maxarray);
+ pixDestroy(&pixb);
+ pixDestroy(&pixt);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixOpenGray()
+ *
+ * \param[in] pixs
+ * \param[in] hsize of Sel; must be odd; origin implicitly in center
+ * \param[in] vsize ditto
+ * \return pixd
+ *
+ * <pre>
+ * Notes:
+ * (1) Sel is a brick with all elements being hits
+ * (2) If hsize = vsize = 1, just returns a copy.
+ * </pre>
+ */
+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++;
+ }
+
+ pixb = pixt = pixd = NULL;
+ buffer = array = NULL;
+
+ 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;
+ }
+
+ pixb = pixAddBorderGeneral(pixs, leftpix, rightpix, toppix, bottompix, 255);
+ pixt = pixCreateTemplate(pixb);
+ if (!pixb || !pixt) {
+ L_ERROR("pixb and pixt not made\n", procName);
+ goto cleanup;
+ }
+
+ pixGetDimensions(pixt, &w, &h, NULL);
+ datab = pixGetData(pixb);
+ datat = pixGetData(pixt);
+ wplb = pixGetWpl(pixb);
+ wplt = pixGetWpl(pixt);
+
+ buffer = (l_uint8 *)LEPT_CALLOC(L_MAX(w, h), sizeof(l_uint8));
+ maxsize = L_MAX(hsize, vsize);
+ array = (l_uint8 *)LEPT_CALLOC(2 * maxsize, sizeof(l_uint8));
+ if (!buffer || !array) {
+ L_ERROR("buffer and array not made\n", procName);
+ goto cleanup;
+ }
+
+ 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);
+ }
+
+ pixd = pixRemoveBorderGeneral(pixb, leftpix, rightpix, toppix, bottompix);
+ if (!pixd)
+ L_ERROR("pixd not made\n", procName);
+
+cleanup:
+ LEPT_FREE(buffer);
+ LEPT_FREE(array);
+ pixDestroy(&pixb);
+ pixDestroy(&pixt);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixCloseGray()
+ *
+ * \param[in] pixs
+ * \param[in] hsize of Sel; must be odd; origin implicitly in center
+ * \param[in] vsize ditto
+ * \return pixd
+ *
+ * <pre>
+ * Notes:
+ * (1) Sel is a brick with all elements being hits
+ * (2) If hsize = vsize = 1, just returns a copy.
+ * </pre>
+ */
+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++;
+ }
+
+ pixb = pixt = pixd = NULL;
+ buffer = array = NULL;
+
+ 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;
+ }
+
+ pixb = pixAddBorderGeneral(pixs, leftpix, rightpix, toppix, bottompix, 0);
+ pixt = pixCreateTemplate(pixb);
+ if (!pixb || !pixt) {
+ L_ERROR("pixb and pixt not made\n", procName);
+ goto cleanup;
+ }
+
+ pixGetDimensions(pixt, &w, &h, NULL);
+ datab = pixGetData(pixb);
+ datat = pixGetData(pixt);
+ wplb = pixGetWpl(pixb);
+ wplt = pixGetWpl(pixt);
+
+ buffer = (l_uint8 *)LEPT_CALLOC(L_MAX(w, h), sizeof(l_uint8));
+ maxsize = L_MAX(hsize, vsize);
+ array = (l_uint8 *)LEPT_CALLOC(2 * maxsize, sizeof(l_uint8));
+ if (!buffer || !array) {
+ L_ERROR("buffer and array not made\n", procName);
+ goto cleanup;
+ }
+
+ 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);
+ }
+
+ pixd = pixRemoveBorderGeneral(pixb, leftpix, rightpix, toppix, bottompix);
+ if (!pixd)
+ L_ERROR("pixd not made\n", procName);
+
+cleanup:
+ LEPT_FREE(buffer);
+ LEPT_FREE(array);
+ pixDestroy(&pixb);
+ pixDestroy(&pixt);
+ return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ * Special operations for 1x3, 3x1 and 3x3 Sels *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixErodeGray3()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \param[in] hsize 1 or 3
+ * \param[in] vsize 1 or 3
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixErodeGray3h()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Special case for horizontal 3x1 brick Sel;
+ * also used as the first step for the 3x3 brick Sel.
+ * </pre>
+ */
+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 = pixCreateTemplate(pixs);
+ 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;
+}
+
+
+/*!
+ * \brief pixErodeGray3v()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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 = pixCreateTemplate(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;
+}
+
+
+/*!
+ * \brief pixDilateGray3()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \param[in] hsize 1 or 3
+ * \param[in] vsize 1 or 3
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
+ * (2) If hsize = vsize = 1, just returns a copy.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixDilateGray3h()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Special case for horizontal 3x1 brick Sel;
+ * also used as the first step for the 3x3 brick Sel.
+ * </pre>
+ */
+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 = pixCreateTemplate(pixs);
+ 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;
+}
+
+
+/*!
+ * \brief pixDilateGray3v()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Special case for vertical 1x3 brick Sel;
+ * also used as the second step for the 3x3 brick Sel.
+ * </pre>
+ */
+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 = pixCreateTemplate(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;
+}
+
+
+/*!
+ * \brief pixOpenGray3()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \param[in] hsize 1 or 3
+ * \param[in] vsize 1 or 3
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCloseGray3()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \param[in] hsize 1 or 3
+ * \param[in] vsize 1 or 3
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
+ * (2) If hsize = vsize = 1, just returns a copy.
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief dilateGrayLow()
+ *
+ * \param[in] datad 8 bpp dsst image
+ * \param[in] w, h dimensions of src and dest
+ * \param[in] wpld words/line of dest
+ * \param[in] datas 8 bpp src image
+ * \param[in] wpls words/line of src
+ * \param[in] size full length of SEL; restricted to odd numbers
+ * \param[in] direction L_HORIZ or L_VERT
+ * \param[in] buffer holds full line or column of src image pixels
+ * \param[in] maxarray array of dimension 2*size+1
+ * \return void
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief erodeGrayLow()
+ *
+ * \param[in] datad 8 bpp dsst image
+ * \param[in] w, h dimensions of src and dest
+ * \param[in] wpld words/line of dest
+ * \param[in] datas 8 bpp src image
+ * \param[in] wpls words/line of src
+ * \param[in] size full length of SEL; restricted to odd numbers
+ * \param[in] direction L_HORIZ or L_VERT
+ * \param[in] buffer holds full line or column of src image pixels
+ * \param[in] minarray array of dimension 2*size+1
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes in dilateGrayLow()
+ * </pre>
+ */
+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/leptonica/src/grayquant.c b/leptonica/src/grayquant.c
new file mode 100644
index 00000000..a943e561
--- /dev/null
+++ b/leptonica/src/grayquant.c
@@ -0,0 +1,2913 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file grayquant.c
+ * <pre>
+ *
+ * Thresholding from 8 bpp to 1 bpp
+ *
+ * Floyd-Steinberg dithering to binary
+ * PIX *pixDitherToBinary()
+ * PIX *pixDitherToBinarySpec()
+ * static void ditherToBinaryLow()
+ * void ditherToBinaryLineLow()
+ *
+ * Simple (pixelwise) binarization with fixed threshold
+ * PIX *pixThresholdToBinary()
+ * static void thresholdToBinaryLow()
+ * void thresholdToBinaryLineLow()
+ *
+ * Binarization with variable threshold
+ * PIX *pixVarThresholdToBinary()
+ *
+ * Binarization by adaptive mapping
+ * PIX *pixAdaptThresholdToBinary()
+ * PIX *pixAdaptThresholdToBinaryGen()
+ *
+ * Generate a binary mask from pixels of particular values
+ * PIX *pixGenerateMaskByValue()
+ * PIX *pixGenerateMaskByBand()
+ *
+ * Thresholding from 8 bpp to 2 bpp
+ *
+ * Floyd-Steinberg-like dithering to 2 bpp
+ * PIX *pixDitherTo2bpp()
+ * PIX *pixDitherTo2bppSpec()
+ * static void ditherTo2bppLow()
+ * static void ditherTo2bppLineLow()
+ * static l_int32 make8To2DitherTables()
+ *
+ * Simple (pixelwise) thresholding to 2 bpp with optional cmap
+ * PIX *pixThresholdTo2bpp()
+ * static void thresholdTo2bppLow()
+ *
+ * Simple (pixelwise) thresholding from 8 bpp to 4 bpp
+ * PIX *pixThresholdTo4bpp()
+ * static void thresholdTo4bppLow()
+ *
+ * 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()
+ * static l_int32 *makeGrayQuantTargetTable()
+ *
+ * Quantization table for arbitrary thresholding of grayscale images
+ * l_int32 makeGrayQuantTableArb()
+ * static 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+static 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);
+static 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);
+static 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);
+static 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);
+static l_int32 make8To2DitherTables(l_int32 **ptabval, l_int32 **ptab38,
+ l_int32 **ptab14, l_int32 cliptoblack,
+ l_int32 cliptowhite);
+static void thresholdTo2bppLow(l_uint32 *datad, l_int32 h, l_int32 wpld,
+ l_uint32 *datas, l_int32 wpls, l_int32 *tab);
+static void thresholdTo4bppLow(l_uint32 *datad, l_int32 h, l_int32 wpld,
+ l_uint32 *datas, l_int32 wpls, l_int32 *tab);
+static l_int32 *makeGrayQuantTargetTable(l_int32 nlevels, l_int32 depth);
+static l_int32 makeGrayQuantColormapArb(PIX *pixs, l_int32 *tab,
+ l_int32 outdepth, PIXCMAP **pcmap);
+static l_int32 numaFillCmapFromHisto(NUMA *na, PIXCMAP *cmap,
+ l_float32 minfract, l_int32 maxsize,
+ l_int32 **plut);
+
+#ifndef NO_CONSOLE_IO
+#define DEBUG_UNROLLING 0
+#endif /* ~NO_CONSOLE_IO */
+
+/*------------------------------------------------------------------*
+ * Binarization by Floyd-Steinberg dithering *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixDitherToBinary()
+ *
+ * \param[in] 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 the same speed
+ * as pixDitherToBinaryLUT(), which uses three 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);
+}
+
+
+/*!
+ * \brief pixDitherToBinarySpec()
+ *
+ * \param[in] pixs
+ * \param[in] lowerclip lower clip distance to black; use 0 for default
+ * \param[in] upperclip upper clip distance to white; use 0 for default
+ * \return pixd dithered binary, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+ if ((pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE)) == NULL) {
+ pixDestroy(&pixd);
+ return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+ }
+ datat = pixGetData(pixt);
+ wplt = pixGetWpl(pixt);
+
+ /* Two line buffers, 1 for current line and 2 for next line */
+ bufs1 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32));
+ bufs2 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32));
+ if (!bufs1 || !bufs2) {
+ LEPT_FREE(bufs1);
+ LEPT_FREE(bufs2);
+ pixDestroy(&pixd);
+ pixDestroy(&pixt);
+ return (PIX *)ERROR_PTR("bufs1, bufs2 not both made", procName, NULL);
+ }
+
+ ditherToBinaryLow(datad, w, h, wpld, datat, wplt, bufs1, bufs2,
+ lowerclip, upperclip);
+
+ LEPT_FREE(bufs1);
+ LEPT_FREE(bufs2);
+ pixDestroy(&pixt);
+ return pixd;
+}
+
+
+/*!
+ * \brief ditherToBinaryLow()
+ *
+ * See comments in pixDitherToBinary() in binarize.c
+ */
+static 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);
+}
+
+
+/*!
+ * \brief ditherToBinaryLineLow()
+ *
+ * \param[in] lined ptr to beginning of dest line
+ * \param[in] w width of image in pixels
+ * \param[in] bufs1 buffer of current source line
+ * \param[in] bufs2 buffer of next source line
+ * \param[in] lowerclip lower clip distance to black
+ * \param[in] upperclip upper clip distance to white
+ * \param[in] 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 */
+ }
+}
+
+
+/*------------------------------------------------------------------*
+ * Simple (pixelwise) binarization with fixed threshold *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixThresholdToBinary()
+ *
+ * \param[in] pixs 4 or 8 bpp
+ * \param[in] thresh threshold value
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If the source pixel is less than the threshold value,
+ * the dest will be 1; otherwise, it will be 0.
+ * (2) For example, for 8 bpp src pix, if %thresh == 256, the dest
+ * 1 bpp pix is all ones (fg), and if %thresh == 0, the dest
+ * pix is all zeros (bg).
+ *
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief thresholdToBinaryLow()
+ *
+ * If the source pixel is less than thresh,
+ * the dest will be 1; otherwise, it will be 0
+ */
+static 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);
+ }
+}
+
+
+/*
+ * 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) { \
+ lept_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 |= (l_uint64)(((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;
+ }
+}
+
+
+/*------------------------------------------------------------------*
+ * Binarization with variable threshold *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixVarThresholdToBinary()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] pixg 8 bpp; contains threshold values for each pixel
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixAdaptThresholdToBinary()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] pixm [optional] 1 bpp image mask; can be null
+ * \param[in] gamma gamma correction; must be > 0.0; typically ~1.0
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixAdaptThresholdToBinaryGen()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] pixm [optional] 1 bpp image mask; can be null
+ * \param[in] gamma gamma correction; must be > 0.0; typically ~1.0
+ * \param[in] blackval dark value to set to black (0)
+ * \param[in] whiteval light value to set to white (255)
+ * \param[in] thresh final threshold for binarization
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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);
+
+ if ((pix1 = pixBackgroundNormSimple(pixs, pixm, NULL)) == NULL)
+ return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+ pixGammaTRC(pix1, pix1, gamma, blackval, whiteval);
+ pixd = pixThresholdToBinary(pix1, thresh);
+ pixDestroy(&pix1);
+ return pixd;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Generate a binary mask from pixels of particular value(s) *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixGenerateMaskByValue()
+ *
+ * \param[in] pixs 2, 4 or 8 bpp, or colormapped
+ * \param[in] val of pixels for which we set 1 in dest
+ * \param[in] usecmap 1 to retain cmap values; 0 to convert to gray
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGenerateMaskByBand()
+ *
+ * \param[in] pixs 2, 4 or 8 bpp, or colormapped
+ * \param[in] 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
+ * \param[in] inband 1 for finding pixels in [lower, upper];
+ * 0 for finding pixels in
+ * [0, lower) union (upper, 255]
+ * \param[in] usecmap 1 to retain cmap values; 0 to convert to gray
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixDitherTo2bpp()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief pixDitherTo2bppSpec()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] lowerclip lower clip distance to black; use 0 for default
+ * \param[in] upperclip upper clip distance to white; use 0 for default
+ * \param[in] cmapflag 1 to generate a colormap
+ * \return pixd dithered 2 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+ bufs1 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32));
+ bufs2 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32));
+ if (!bufs1 || !bufs2) {
+ LEPT_FREE(bufs1);
+ LEPT_FREE(bufs2);
+ pixDestroy(&pixd);
+ pixDestroy(&pixt);
+ return (PIX *)ERROR_PTR("bufs1, bufs2 not both 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;
+}
+
+
+/*!
+ * \brief 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.
+ */
+static 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);
+}
+
+
+/*!
+ * \brief ditherTo2bppLineLow()
+ *
+ * \param[in] lined ptr to beginning of dest line
+ * \param[in] w width of image in pixels
+ * \param[in] bufs1 buffer of current source line
+ * \param[in] bufs2 buffer of next source line
+ * \param[in] tabval value to assign for current pixel
+ * \param[in] tab38 excess value to give to neighboring 3/8 pixels
+ * \param[in] tab14 excess value to give to neighboring 1/4 pixel
+ * \param[in] 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.
+ */
+static 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]);
+ }
+}
+
+
+/*!
+ * \brief make8To2DitherTables()
+ *
+ * \param[out] ptabval value assigned to output pixel; 0, 1, 2 or 3
+ * \param[out] ptab38 amount propagated to pixels left and below
+ * \param[out] ptab14 amount propagated to pixel to left and down
+ * \param[in] cliptoblack values near 0 where the excess is not propagated
+ * \param[in] cliptowhite values near 255 where the deficit is not propagated
+ *
+ * \return 0 if OK, 1 on error
+ */
+static 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;
+
+ /* 3 lookup tables: 2-bit value, (3/8)excess, and (1/4)excess */
+ tabval = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ tab38 = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ tab14 = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ *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 (pixelwise) thresholding to 2 bpp with optional colormap *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixThresholdTo2bpp()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] nlevels equally spaced; must be between 2 and 4
+ * \param[in] cmapflag 1 to build colormap; 0 otherwise
+ * \return pixd 2 bpp, optionally with colormap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ 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);
+
+ /* Make the appropriate table */
+ if (cmapflag)
+ qtab = makeGrayQuantIndexTable(nlevels);
+ else
+ qtab = makeGrayQuantTargetTable(4, 2);
+
+ thresholdTo2bppLow(datad, h, wpld, datat, wplt, qtab);
+
+ LEPT_FREE(qtab);
+ pixDestroy(&pixt);
+ return pixd;
+}
+
+
+/*!
+ * \brief 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.
+ */
+static 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);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------*
+ * Simple (pixelwise) thresholding to 4 bpp *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixThresholdTo4bpp()
+ *
+ * \param[in] pixs 8 bpp, can have colormap
+ * \param[in] nlevels equally spaced; must be between 2 and 16
+ * \param[in] cmapflag 1 to build colormap; 0 otherwise
+ * \return pixd 4 bpp, optionally with colormap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ 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);
+
+ /* Make the appropriate table */
+ if (cmapflag)
+ qtab = makeGrayQuantIndexTable(nlevels);
+ else
+ qtab = makeGrayQuantTargetTable(16, 4);
+
+ thresholdTo4bppLow(datad, h, wpld, datat, wplt, qtab);
+
+ LEPT_FREE(qtab);
+ pixDestroy(&pixt);
+ return pixd;
+}
+
+
+/*!
+ * \brief 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.
+ */
+static 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);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------*
+ * Simple (pixelwise) thresholding on 8 bpp with optional colormap *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixThresholdOn8bpp()
+ *
+ * \param[in] pixs 8 bpp, can have colormap
+ * \param[in] nlevels equally spaced; must be between 2 and 256
+ * \param[in] cmapflag 1 to build colormap; 0 otherwise
+ * \return pixd 8 bpp, optionally with colormap, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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);
+
+ /* 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);
+ }
+
+ if (cmapflag)
+ qtab = makeGrayQuantIndexTable(nlevels);
+ else
+ qtab = makeGrayQuantTargetTable(nlevels, 8);
+
+ 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);
+ }
+ }
+
+ LEPT_FREE(qtab);
+ return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Arbitrary (pixelwise) thresholding from 8 bpp to 2, 4 or 8 bpp *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixThresholdGrayArb()
+ *
+ * \param[in] pixs 8 bpp grayscale; can have colormap
+ * \param[in] edgevals string giving edge value of each bin
+ * \param[in] outdepth 0, 2, 4 or 8 bpp; 0 is default for min depth
+ * \param[in] use_average 1 if use the average pixel value in colormap
+ * \param[in] setblack 1 if darkest color is set to black
+ * \param[in] setwhite 1 if lightest color is set to white
+ * \return pixd 2, 4 or 8 bpp quantized image with colormap,
+ * or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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) {
+ numaDestroy(&na);
+ 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) {
+ LEPT_FREE(qtab);
+ pixcmapDestroy(&cmap);
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief makeGrayQuantIndexTable()
+ *
+ * \param[in] nlevels number of output levels
+ * \return table maps input gray level to colormap index,
+ * or NULL on error
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_int32 *
+makeGrayQuantIndexTable(l_int32 nlevels)
+{
+l_int32 *tab;
+l_int32 i, j, thresh;
+
+ tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ 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;
+/* lept_stderr("tab[%d] = %d\n", i, j); */
+ break;
+ }
+ }
+ }
+ return tab;
+}
+
+
+/*!
+ * \brief makeGrayQuantTargetTable()
+ *
+ * \param[in] nlevels number of output levels
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static l_int32 *
+makeGrayQuantTargetTable(l_int32 nlevels,
+ l_int32 depth)
+{
+l_int32 *tab;
+l_int32 i, j, thresh, maxval, quantval;
+
+ tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ 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;
+/* lept_stderr("tab[%d] = %d\n", i, tab[i]); */
+ break;
+ }
+ }
+ }
+ return tab;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Quantization table for arbitrary thresholding of grayscale images *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief makeGrayQuantTableArb()
+ *
+ * \param[in] na numa of bin boundaries
+ * \param[in] outdepth of colormap: 1, 2, 4 or 8
+ * \param[out] ptab table mapping input gray level to cmap index
+ * \param[out] pcmap colormap
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 ((cmap = pixcmapCreate(outdepth)) == NULL)
+ return ERROR_INT("cmap not made", procName, 1);
+ tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ *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;
+}
+
+
+/*!
+ * \brief makeGrayQuantColormapArb()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] tab table mapping input gray level to cmap index
+ * \param[in] outdepth of colormap: 1, 2, 4 or 8
+ * \param[out] pcmap colormap
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static 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) {
+ LEPT_FREE(bincount);
+ 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 */
+ binstart = (l_int32 *)LEPT_CALLOC(nbins, sizeof(l_int32));
+ 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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixGenerateMaskByBand32()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] refval reference rgb value
+ * \param[in] delm max amount below the ref value for any component
+ * \param[in] delp max amount above the ref value for any component
+ * \param[in] fractm fractional amount below ref value for all components
+ * \param[in] fractp fractional amount above ref value for all components
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGenerateMaskByDiscr32()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] refval1 reference rgb value
+ * \param[in] refval2 reference rgb value
+ * \param[in] distflag L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixGrayQuantFromHisto()
+ *
+ * \param[in] pixd [optional] quantized pix with cmap; can be null
+ * \param[in] pixs 8 bpp gray input pix; not cmapped
+ * \param[in] pixm [optional] mask over pixels in pixs to quantize
+ * \param[in] 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
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.05f;
+ }
+ 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);
+ lept_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;
+}
+
+
+/*!
+ * \brief numaFillCmapFromHisto()
+ *
+ * \param[in] na histogram of gray values
+ * \param[in] cmap 8 bpp cmap, possibly initialized with color value
+ * \param[in] 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
+ * \param[in] 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
+ * \param[out] plut lookup table from gray value to colormap index
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This static function must be called from pixGrayQuantFromHisto()
+ * </pre>
+ */
+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);
+ lut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ *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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixGrayQuantFromCmap()
+ *
+ * \param[in] pixs 8 bpp grayscale without cmap
+ * \param[in] cmap to quantize to; of dest pix
+ * \param[in] mindepth minimum depth of pixd: can be 2, 4 or 8 bpp
+ * \return pixd 2, 4 or 8 bpp, colormapped, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.3f, 0.5f, 0.2f);
+ } else {
+ cmapd = pixcmapCopy(cmap);
+ }
+
+ /* Make LUT into colormap */
+ tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ 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;
+}
+
+
+#if 0 /* Documentation */
+/*--------------------------------------------------------------------*
+ * Implementation of binarization by dithering using LUTs *
+ * It is archived here. *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixDitherToBinaryLUT()
+ *
+ * \param[in] pixs
+ * \param[in] lowerclip lower clip distance to black; use -1 for default
+ * \param[in] upperclip upper clip distance to white; use -1 for default
+ * \return pixd dithered binary, or NULL on error
+ *
+ * We don't need two implementations of Floyd-Steinberg dithering,
+ * and this one with LUTs is a little more complicated than
+ * pixDitherToBinary(). It uses three lookup tables to generate the
+ * output pixel value and the excess or deficit carried over to the
+ * neighboring pixels. It's here for pedagogical reasons only.
+ */
+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 */
+ bufs1 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32));
+ bufs2 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32));
+ if (!bufs1 || !bufs2) {
+ LEPT_FREE(bufs1);
+ LEPT_FREE(bufs2);
+ pixDestroy(&pixd);
+ pixDestroy(&pixt);
+ return (PIX *)ERROR_PTR("bufs1, bufs2 not both 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;
+}
+
+/*!
+ * \brief 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;
+}
+
+/*!
+ * \brief ditherToBinaryLineLUTLow()
+ *
+ * \param[in] lined ptr to beginning of dest line
+ * \param[in] w width of image in pixels
+ * \param[in] bufs1 buffer of current source line
+ * \param[in] bufs2 buffer of next source line
+ * \param[in] tabval value to assign for current pixel
+ * \param[in] tab38 excess value to give to neighboring 3/8 pixels
+ * \param[in] tab14 excess value to give to neighboring 1/4 pixel
+ * \param[in] 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;
+}
+
+/*!
+ * \brief make8To1DitherTables()
+ *
+ * \param[out] ptabval value assigned to output pixel; 0 or 1
+ * \param[out] ptab38 amount propagated to pixels left and below
+ * \param[out] ptab14 amount propagated to pixel to left and down
+ * \param[in] lowerclip values near 0 where the excess is not propagated
+ * \param[in] upperclip values near 255 where the deficit is not propagated
+ *
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) *ptabval = NULL;
+ if (ptab38) *ptab38 = NULL;
+ if (ptab14) *ptab14 = NULL;
+ 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 */
+ tabval = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ tab38 = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ tab14 = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ if (!tabval || !tab38 || !tab14)
+ return ERROR_INT("calloc failure to make small table", 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;
+}
+#endif /* Documentation */
diff --git a/leptonica/src/heap.c b/leptonica/src/heap.c
new file mode 100644
index 00000000..99f80c4d
--- /dev/null
+++ b/leptonica/src/heap.c
@@ -0,0 +1,589 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file heap.c
+ * <pre>
+ *
+ * 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()
+ *
+ * Other accessors
+ * l_int32 lheapGetCount()
+ * void *lheapGetElement()
+ *
+ * Heap sort
+ * l_int32 lheapSort()
+ * l_int32 lheapSortStrictOrder()
+ *
+ * Low-level heap operations
+ * static l_int32 lheapSwapUp()
+ * static l_int32 lheapSwapDown()
+ *
+ * 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. ------------------------------
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Bounds on initial array size */
+static const l_uint32 MaxPtrArraySize = 100000;
+static const l_int32 InitialPtrArraySize = 20; /*!< n'importe quoi */
+
+#define SWAP_ITEMS(i, j) { void *tempitem = lh->array[(i)]; \
+ lh->array[(i)] = lh->array[(j)]; \
+ lh->array[(j)] = tempitem; }
+
+ /* Static functions */
+static l_int32 lheapExtendArray(L_HEAP *lh);
+static l_ok lheapSwapUp(L_HEAP *lh, l_int32 index);
+static l_ok lheapSwapDown(L_HEAP *lh);
+
+
+/*--------------------------------------------------------------------------*
+ * L_Heap create/destroy *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief lheapCreate()
+ *
+ * \param[in] n size of ptr array to be alloc'd; use 0 for default
+ * \param[in] direction L_SORT_INCREASING, L_SORT_DECREASING
+ * \return lheap, or NULL on error
+ */
+L_HEAP *
+lheapCreate(l_int32 n,
+ l_int32 direction)
+{
+L_HEAP *lh;
+
+ PROCNAME("lheapCreate");
+
+ if (n < InitialPtrArraySize || n > MaxPtrArraySize)
+ n = InitialPtrArraySize;
+
+ /* Allocate ptr array and initialize counters. */
+ lh = (L_HEAP *)LEPT_CALLOC(1, sizeof(L_HEAP));
+ if ((lh->array = (void **)LEPT_CALLOC(n, sizeof(void *))) == NULL) {
+ lheapDestroy(&lh, FALSE);
+ return (L_HEAP *)ERROR_PTR("ptr array not made", procName, NULL);
+ }
+ lh->nalloc = n;
+ lh->n = 0;
+ lh->direction = direction;
+ return lh;
+}
+
+
+/*!
+ * \brief lheapDestroy()
+ *
+ * \param[in,out] plh will be set to null before returning
+ * \param[in] freeflag TRUE to free each remaining struct in the array
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+/*--------------------------------------------------------------------------*
+ * Operations to add/remove to/from the heap *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief lheapAdd()
+ *
+ * \param[in] lh heap
+ * \param[in] item to be added to the tail of the heap
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ if (lheapExtendArray(lh))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+
+ /* Add the item */
+ lh->array[lh->n] = item;
+ lh->n++;
+
+ /* Restore the heap */
+ lheapSwapUp(lh, lh->n - 1);
+ return 0;
+}
+
+
+/*!
+ * \brief lheapExtendArray()
+ *
+ * \param[in] lh heap
+ * \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;
+}
+
+
+/*!
+ * \brief lheapRemove()
+ *
+ * \param[in] lh heap
+ * \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;
+}
+
+
+/*--------------------------------------------------------------------------*
+ * Other accessors *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief lheapGetCount()
+ *
+ * \param[in] lh heap
+ * \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;
+}
+
+
+/*!
+ * \brief lheapGetElement()
+ *
+ * \param[in] lh heap
+ * \param[in] index into the internal heap array
+ * \return ptr to the element at array[index], or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is useful for retrieving an arbitrary element in the
+ * heap array without disturbing the heap. It allows all the
+ * elements on the heap to be queried in linear time; for
+ * example, to find the min or max of some value.
+ * (2) Tbe retrieved element is owned by the heap. Do not destroy it.
+ * </pre>
+ */
+void *
+lheapGetElement(L_HEAP *lh,
+ l_int32 index)
+{
+ PROCNAME("lheapGetElement");
+
+ if (!lh)
+ return ERROR_PTR("lh not defined", procName, NULL);
+ if (index < 0 || index >= lh->n)
+ return ERROR_PTR("invalid index", procName, NULL);
+
+ return (void *)lh->array[index];
+}
+
+
+/*--------------------------------------------------------------------------*
+ * Heap sort *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief lheapSort()
+ *
+ * \param[in] lh heap, with internal array
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief lheapSortStrictOrder()
+ *
+ * \param[in] lh heap, with internal array
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+lheapSortStrictOrder(L_HEAP *lh)
+{
+l_int32 i, index, size;
+
+ PROCNAME("lheapSortStrictOrder");
+
+ if (!lh)
+ return ERROR_INT("lh not defined", procName, 1);
+
+ /* Start from a sorted heap */
+ lheapSort(lh);
+
+ 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;
+}
+
+
+/*--------------------------------------------------------------------------*
+ * Low-level heap operations *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief lheapSwapUp()
+ *
+ * \param[in] lh heap
+ * \param[in] index of array corresponding to node to be swapped up
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static l_ok
+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;
+}
+
+
+/*!
+ * \brief lheapSwapDown()
+ *
+ * \param[in] lh heap
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+static l_ok
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Debug output *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief lheapPrint()
+ *
+ * \param[in] fp file stream
+ * \param[in] lh heap
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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/leptonica/src/heap.h b/leptonica/src/heap.h
new file mode 100644
index 00000000..d39b06b9
--- /dev/null
+++ b/leptonica/src/heap.h
@@ -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_HEAP_H
+#define LEPTONICA_HEAP_H
+
+/*!
+ * \file heap.h
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+
+/*! Heap of arbitrary void* data */
+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/leptonica/src/hmttemplate1.txt b/leptonica/src/hmttemplate1.txt
new file mode 100644
index 00000000..cf1e0f84
--- /dev/null
+++ b/leptonica/src/hmttemplate1.txt
@@ -0,0 +1,170 @@
+/*====================================================================*
+ - 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>", ...};
+
+/*!
+--- * \brief pixHMTDwa_*()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * Notes:
+ * (1) This simply adds a 32 pixel border, calls the appropriate
+ * pixFHMTGen_*(), and removes the border.
+ * See notes below for that function.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+--- * \brief pixFHMTGen_*()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/hmttemplate2.txt b/leptonica/src/hmttemplate2.txt
new file mode 100644
index 00000000..cd4da0bf
--- /dev/null
+++ b/leptonica/src/hmttemplate2.txt
@@ -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;
+l_int32 j, pwpls;
+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/leptonica/src/imageio.h b/leptonica/src/imageio.h
new file mode 100644
index 00000000..4ffce40e
--- /dev/null
+++ b/leptonica/src/imageio.h
@@ -0,0 +1,245 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file imageio.h
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+
+#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.
+ *
+ * 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.
+ */
+
+/*! Image Formats */
+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_TIFF_JPEG = 17,
+ IFF_DEFAULT = 18,
+ IFF_SPIX = 19
+};
+
+/* Convenient macro for checking requested tiff output */
+#define L_FORMAT_IS_TIFF(f) ((f) == IFF_TIFF || (f) == IFF_TIFF_PACKBITS || \
+ (f) == IFF_TIFF_RLE || (f) == IFF_TIFF_G3 || \
+ (f) == IFF_TIFF_G4 || (f) == IFF_TIFF_LZW || \
+ (f) == IFF_TIFF_ZIP || (f) == IFF_TIFF_JPEG)
+
+
+/* --------------------------------------------------------------- *
+ * Format header ids *
+ * --------------------------------------------------------------- */
+
+/*! Header Ids */
+enum {
+ BMP_ID = 0x4d42, /*!< BM - for bitmaps */
+ TIFF_BIGEND_ID = 0x4d4d, /*!< MM - for 'motorola' */
+ TIFF_LITTLEEND_ID = 0x4949 /*!< II - for 'intel' */
+};
+
+
+/* --------------------------------------------------------------- *
+ * Hinting bit flags in jpeg reader *
+ * --------------------------------------------------------------- */
+
+/*! Jpeg Hints */
+/* The default behavior is now to fail on data corruption. */
+enum {
+ L_JPEG_READ_LUMINANCE = 1, /*!< only want luminance data; no chroma */
+ L_JPEG_CONTINUE_WITH_BAD_DATA = 2 /*!< return possibly damaged pix */
+};
+
+
+/* --------------------------------------------------------------- *
+ * Pdf formatted encoding types *
+ * --------------------------------------------------------------- */
+
+/*! Pdf Encoding */
+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.
+ */
+
+/*! Compressed image data */
+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 *
+ * ------------------------------------------------------------------------- */
+
+/*! Pdf MultiImage */
+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.
+ */
+
+/*! Intermediate pdf generation data */
+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/leptonica/src/jbclass.c b/leptonica/src/jbclass.c
new file mode 100644
index 00000000..cc3c5a52
--- /dev/null
+++ b/leptonica/src/jbclass.c
@@ -0,0 +1,2569 @@
+/*====================================================================*
+ - 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+#define 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 */
+
+ /* 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_CORRELATION_SCORE 0
+#endif /* ~NO_CONSOLE_IO */
+
+/*----------------------------------------------------------------------*
+ * Initialization *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief jbRankHausInit()
+ *
+ * \param[in] components JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS
+ * \param[in] maxwidth of component; use 0 for default
+ * \param[in] maxheight of component; use 0 for default
+ * \param[in] 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
+ * \param[in] 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 */
+ classer->keep_pixaa = 1; /* keep all components in pixaa */
+ return classer;
+}
+
+
+/*!
+ * \brief jbCorrelationInit()
+ *
+ * \param[in] components JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS
+ * \param[in] maxwidth of component; use 0 for default
+ * \param[in] maxheight of component; use 0 for default
+ * \param[in] thresh value for correlation score: in [0.4 - 0.98]
+ * \param[in] weightfactor corrects thresh for thick characters [0.0 - 1.0]
+ * \return jbclasser if OK; NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+/*!
+ * \brief jbCorrelationInitWithoutComponents()
+ *
+ * \param[in] components JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS
+ * \param[in] maxwidth of component; use 0 for default
+ * \param[in] maxheight of component; use 0 for default
+ * \param[in] thresh value for correlation score: in [0.4 - 0.98]
+ * \param[in] weightfactor corrects thresh for thick characters [0.0 - 1.0]
+ * \return jbclasser if OK; NULL on error
+ *
+ * <pre>
+ * Notes:
+ * Acts the same as jbCorrelationInit(), but the resulting
+ * object doesn't keep a list of all the components.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief jbAddPages()
+ *
+ * \param[in] jbclasser
+ * \param[in] safiles of page image file names
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) jbclasser makes a copy of the array of file names.
+ * (2) The caller is still responsible for destroying the input array.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief jbAddPage()
+ *
+ * \param[in] jbclasser
+ * \param[in] pixs input page
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief jbAddPageComponents()
+ *
+ * \param[in] jbclasser
+ * \param[in] pixs input page
+ * \param[in] boxas b.b. of components for this page
+ * \param[in] pixas components for this page
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, (l_float32)n);
+ classer->npages++;
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Classification using windowed rank hausdorff metric *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief jbClassifyRankHaus()
+ *
+ * \param[in] jbclasser
+ * \param[in] boxa new components for classification
+ * \param[in] pixas new components for classification
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+jbClassifyRankHaus(JBCLASSER *classer,
+ BOXA *boxa,
+ PIXA *pixas)
+{
+l_int32 n, nt, i, wt, ht, iclass, size, found, testval;
+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 defined", procName, 1);
+ if (!boxa)
+ return ERROR_INT("boxa not defined", procName, 1);
+ if (!pixas)
+ return ERROR_INT("pixas not defined", procName, 1);
+ if ((n = pixaGetCount(pixas)) == 0)
+ return ERROR_INT("pixas is empty", procName, 1);
+ if ((nafg = pixaCountPixels(pixas)) == NULL) /* areas for this page */
+ return ERROR_INT("fg counting failed", 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. */
+ 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;
+
+ /* 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, (l_float32)iclass);
+ numaAddNumber(napage, (l_float32)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, (l_float32)nt);
+ numaAddNumber(napage, (l_float32)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, (l_uint64)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 */
+ 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, (l_float32)iclass);
+ numaAddNumber(napage, (l_float32)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, (l_float32)nt);
+ numaAddNumber(napage, (l_float32)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, (l_uint64)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, (l_float32)area1);
+ } else { /* don't save them */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ }
+ LEPT_FREE(tab8);
+ }
+ classer->nclass = pixaGetCount(pixat);
+
+ numaDestroy(&nafg);
+ ptaDestroy(&pta);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ selDestroy(&sel);
+ return 0;
+}
+
+
+/*!
+ * \brief pixHaustest()
+ *
+ * \param[in] pix1 new pix, not dilated
+ * \param[in] pix2 new pix, dilated
+ * \param[in] pix3 exemplar pix, not dilated
+ * \param[in] pix4 exemplar pix, dilated
+ * \param[in] delx x comp of centroid difference
+ * \param[in] dely y comp of centroid difference
+ * \param[in] maxdiffw max width difference of pix1 and pix2
+ * \param[in] 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.
+ *
+ * <pre>
+ * Notes:
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixRankHaustest()
+ *
+ * \param[in] pix1 new pix, not dilated
+ * \param[in] pix2 new pix, dilated
+ * \param[in] pix3 exemplar pix, not dilated
+ * \param[in] pix4 exemplar pix, dilated
+ * \param[in] delx x comp of centroid difference
+ * \param[in] dely y comp of centroid difference
+ * \param[in] maxdiffw max width difference of pix1 and pix2
+ * \param[in] maxdiffh max height difference of pix1 and pix2
+ * \param[in] area1 fg pixels in pix1
+ * \param[in] area3 fg pixels in pix3
+ * \param[in] rank rank value of test, each way
+ * \param[in] 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.
+ *
+ * <pre>
+ * Notes:
+ * 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.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief jbClassifyCorrelation()
+ *
+ * \param[in] jbclasser
+ * \param[in] boxa new components for classification
+ * \param[in] pixas new components for classification
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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. */
+ if ((n = pixaGetCount(pixas)) == 0) {
+ L_WARNING("pixas is empty\n", procName);
+ return 0;
+ }
+ 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();
+
+ /* 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;
+ if (downcount > 0) {
+ ptaAddPt(pta,
+ xsum / (l_float32)downcount, ysum / (l_float32)downcount);
+ } else { /* no pixels; shouldn't happen */
+ L_ERROR("downcount == 0 !\n", procName);
+ ptaAddPt(pta, (l_float32)(pixGetWidth(pix) / 2), (l_float32)(pixGetHeight(pix) / 2));
+ }
+ 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.f - 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)) {
+ lept_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) {
+ lept_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, (l_float32)iclass);
+ numaAddNumber(napage, (l_float32)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, (l_float32)nt);
+ numaAddNumber(napage, (l_float32)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, (l_uint64)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, (l_float32)area1);
+ pixaAddPix(pixat, pix1, L_INSERT); /* bordered template */
+ area = (pixGetWidth(pix1) - 2 * JB_ADDED_PIXELS) *
+ (pixGetHeight(pix1) - 2 * JB_ADDED_PIXELS);
+ numaAddNumber(naarea, (l_float32)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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief jbGetComponents()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] components JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS
+ * \param[in] maxwidth of saved components; larger are discarded
+ * \param[in] maxheight of saved components; larger are discarded
+ * \param[out] ppboxa b.b. of component items
+ * \param[out] pppixa component items
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+jbGetComponents(PIX *pixs,
+ l_int32 components,
+ l_int32 maxwidth,
+ l_int32 maxheight,
+ BOXA **pboxad,
+ PIXA **ppixad)
+{
+l_int32 empty, res, redfactor;
+BOXA *boxa;
+PIX *pix1, *pix2, *pix3;
+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) {
+ pix1 = pixMorphSequence(pixs, "c1.6", 0);
+ boxa = pixConnComp(pix1, &pixat, 8);
+ pixa = pixaClipToPix(pixat, pixs);
+ pixDestroy(&pix1);
+ 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;
+ pix1 = pixClone(pixs);
+ } else if (res <= 400) {
+ redfactor = 2;
+ pix1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
+ } else {
+ redfactor = 4;
+ pix1 = 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(pix1, &pix2, NULL, NULL);
+
+ /* Expand the optimally dilated word mask to full res. */
+ pix3 = pixExpandReplicate(pix2, redfactor);
+
+ /* Pull out the pixels in pixs corresponding to the mask
+ * components in pix3. 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, pix3, pixs, 8);
+ * pixOr(pix3, pix3, pixt4);
+ * to insure that the mask coverage is complete over pixs. */
+ boxa = pixConnComp(pix3, &pixat, 4);
+ pixa = pixaClipToPix(pixat, pixs);
+ pixaDestroy(&pixat);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+
+ /* 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;
+}
+
+
+/*!
+ * \brief pixWordMaskByDilation()
+ *
+ * \param[in] pixs 1 bpp; typ. at 75 to 150 ppi
+ * \param[out] pmask [optional] dilated word mask
+ * \param[out] psize [optional] size of good horizontal dilation
+ * \param[out] pixadb [optional] debug: pixa of intermediate steps
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This gives an estimate of the word masks. See
+ * pixWordBoxesByDilation() for further filtering of the word boxes.
+ * (2) The resolution should be between 75 and 150 ppi, and the optimal
+ * dilation will be between 3 and 10.
+ * (3) A good size for dilating to get word masks is optionally returned.
+ * (4) Typically, the number of c.c. reduced with each successive
+ * dilation (stored in nadiff) decreases quickly to a minimum
+ * (where the characters in a word are joined), and then
+ * increases again as the smaller number of words are joined.
+ * For the typical case, you can then look for this minimum
+ * and dilate to get the word mask. However, there are many
+ * cases where the function is not so simple. For example, if the
+ * pix has been upscaled 2x, the nadiff function oscillates, with
+ * every other value being zero! And for some images it tails
+ * off without a clear minimum to indicate where to break.
+ * So a more simple and robust method is to find the dilation
+ * where the initial number of c.c. has been reduced by some
+ * fraction (we use a 70% reduction).
+ * </pre>
+ */
+l_ok
+pixWordMaskByDilation(PIX *pixs,
+ PIX **ppixm,
+ l_int32 *psize,
+ PIXA *pixadb)
+{
+l_int32 i, n, ndil, maxdiff, diff, ibest;
+l_int32 check, count, total, xres;
+l_int32 ncc[13]; /* max dilation + 1 */
+l_int32 *diffa;
+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 a good dilation to create the word mask, by successively
+ * increasing dilation size and counting the connected components. */
+ pix1 = pixCopy(NULL, pixs);
+ ndil = 12; /* appropriate for 75 to 150 ppi */
+ nacc = numaCreate(ndil + 1);
+ nadiff = numaCreate(ndil + 1);
+ for (i = 0; i <= ndil; 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, (l_float32)ncc[i]);
+ if (i == 0) total = ncc[0];
+ if (i > 0) {
+ diff = ncc[i - 1] - ncc[i];
+ numaAddNumber(nadiff, (l_float32)diff);
+ }
+ pixDestroy(&pix1);
+ pix1 = pix2;
+ boxaDestroy(&boxa);
+ }
+ pixDestroy(&pix1);
+
+ /* Find the dilation at which the c.c. count has reduced
+ * to 30% of the initial value. Although 30% seems high,
+ * it seems better to use this but add one to ibest. */
+ diffa = numaGetIArray(nadiff);
+ n = numaGetCount(nadiff);
+ maxdiff = 0;
+ check = TRUE;
+ ibest = 2;
+ for (i = 1; i < n; i++) {
+ numaGetIValue(nacc, i, &count);
+ if (check && count < 0.3 * total) {
+ ibest = i + 1;
+ check = FALSE;
+ }
+ diff = diffa[i];
+ if (diff > maxdiff)
+ maxdiff = diff;
+ }
+ LEPT_FREE(diffa);
+
+ /* Add small compensation for higher resolution */
+ xres = pixGetXRes(pixs);
+ if (xres == 0) xres = 150;
+ if (xres > 110) ibest++;
+ if (ibest < 2) {
+ L_INFO("setting ibest to minimum allowed value of 2\n", procName);
+ ibest = 2;
+ }
+
+ if (pixadb) {
+ lept_mkdir("lept/jb");
+ {NUMA *naseq;
+ PIX *pix3, *pix4;
+ L_INFO("Best dilation: %d\n", procName, L_MAX(3, ibest + 1));
+ naseq = numaMakeSequence(1, 1, numaGetCount(nacc));
+ pix3 = gplotGeneralPix2(naseq, nacc, GPLOT_LINES,
+ "/tmp/lept/jb/numcc",
+ "Number of cc vs. horizontal dilation",
+ "Sel horiz", "Number of cc");
+ pixaAddPix(pixadb, pix3, L_INSERT);
+ numaDestroy(&naseq);
+ naseq = numaMakeSequence(1, 1, numaGetCount(nadiff));
+ pix3 = gplotGeneralPix2(naseq, nadiff, GPLOT_LINES,
+ "/tmp/lept/jb/diffcc",
+ "Diff count of cc vs. horizontal dilation",
+ "Sel horiz", "Diff in cc");
+ pixaAddPix(pixadb, pix3, L_INSERT);
+ numaDestroy(&naseq);
+ pix3 = pixCloseBrick(NULL, pixs, ibest + 1, 1);
+ pix4 = pixScaleToSize(pix3, 600, 0);
+ pixaAddPix(pixadb, pix4, L_INSERT);
+ pixDestroy(&pix3);
+ }
+ }
+
+ if (psize) *psize = ibest + 1;
+ if (ppixm)
+ *ppixm = pixCloseBrick(NULL, pixs, ibest + 1, 1);
+
+ numaDestroy(&nacc);
+ numaDestroy(&nadiff);
+ return 0;
+}
+
+
+/*!
+ * \brief pixWordBoxesByDilation()
+ *
+ * \param[in] pixs 1 bpp; typ. 75 - 200 ppi
+ * \param[in] minwidth saved components; smaller are discarded
+ * \param[in] minheight saved components; smaller are discarded
+ * \param[in] maxwidth saved components; larger are discarded
+ * \param[in] maxheight saved components; larger are discarded
+ * \param[out] pboxa of dilated word mask
+ * \param[out] psize [optional] size of good horizontal dilation
+ * \param[out] pixadb [optional] debug: pixa of intermediate steps
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Returns a pruned set of word boxes.
+ * (2) See pixWordMaskByDilation().
+ * </pre>
+ */
+l_ok
+pixWordBoxesByDilation(PIX *pixs,
+ l_int32 minwidth,
+ l_int32 minheight,
+ l_int32 maxwidth,
+ l_int32 maxheight,
+ BOXA **pboxa,
+ l_int32 *psize,
+ PIXA *pixadb)
+{
+BOXA *boxa1, *boxa2;
+PIX *pix1, *pix2;
+
+ 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 mask */
+ if (pixWordMaskByDilation(pixs, &pix1, psize, pixadb))
+ return ERROR_INT("pixWordMaskByDilation() failed", procName, 1);
+
+ /* Prune the word mask. 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(pix1, 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);
+ if (pixadb) {
+ pix2 = pixUnpackBinary(pixs, 32, 1);
+ pixRenderBoxaArb(pix2, boxa1, 2, 255, 0, 0);
+ pixaAddPix(pixadb, pix2, L_INSERT);
+ pix2 = pixUnpackBinary(pixs, 32, 1);
+ pixRenderBoxaArb(pix2, boxa2, 2, 0, 255, 0);
+ pixaAddPix(pixadb, pix2, L_INSERT);
+ }
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ pixDestroy(&pix1);
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Build grayscale composites (templates) *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief jbAccumulateComposites()
+ *
+ * \param[in] pixaa one pixa for each class
+ * \param[out] ppna number of samples used to build each composite
+ * \param[out] pptat 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;
+}
+
+
+/*!
+ * \brief jbTemplatesFromComposites()
+ *
+ * \param[in] pixac one pix of composites for each class
+ * \param[in] 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.f / nt;
+ pixMultConstAccumulate(pixsum, factor, 0); /* changes pixsum */
+ pixd = pixFinalAccumulate(pixsum, 0, 8);
+ pixaAddPix(pixad, pixd, L_INSERT);
+ pixDestroy(&pixsum);
+ }
+
+ return pixad;
+}
+
+
+
+/*----------------------------------------------------------------------*
+ * jbig2 utility routines *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief jbClasserCreate()
+ *
+ * \param[in] method JB_RANKHAUS, JB_CORRELATION
+ * \param[in] 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 (method != JB_RANKHAUS && method != JB_CORRELATION)
+ return (JBCLASSER *)ERROR_PTR("invalid method", procName, NULL);
+ if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
+ components != JB_WORDS)
+ return (JBCLASSER *)ERROR_PTR("invalid component", procName, NULL);
+
+ classer = (JBCLASSER *)LEPT_CALLOC(1, sizeof(JBCLASSER));
+ 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;
+}
+
+
+/*
+ * \brief jbClasserDestroy()
+ *
+ * \param[in,out] pclasser will be set to null before returning
+ * \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;
+}
+
+
+/*!
+ * \brief jbDataSave()
+ *
+ * \param[in] jbclasser
+ * \param[in] latticew cell width used to store each connected
+ * component in the composite
+ * \param[in] latticeh ditto for cell height
+ * \return jbdata, or NULL on error
+ *
+ * <pre>
+ * 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,
+ * </pre>
+ */
+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);
+
+ data = (JBDATA *)LEPT_CALLOC(1, sizeof(JBDATA));
+ 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;
+}
+
+
+/*
+ * \brief jbDataDestroy()
+ *
+ * \param[in,out] pdata will be set to null before returning
+ * \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;
+}
+
+
+/*!
+ * \brief jbDataWrite()
+ *
+ * \param[in] rootname for output files; everything but the extension
+ * \param[in] jbdata
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serialization function that writes data in jbdata to file.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief jbDataRead()
+ *
+ * \param[in] 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, ninit;
+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) {
+ pixDestroy(&pixs);
+ return (JBDATA *)ERROR_PTR("data not read", procName, NULL);
+ }
+
+ if ((sa = sarrayCreateLinesFromString((char *)data, 0)) == NULL) {
+ pixDestroy(&pixs);
+ LEPT_FREE(data);
+ 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") != 0) {
+ pixDestroy(&pixs);
+ LEPT_FREE(data);
+ sarrayDestroy(&sa);
+ 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
+ lept_stderr("num pages = %d\n", npages);
+ lept_stderr("page size: w = %d, h = %d\n", w, h);
+ lept_stderr("num components = %d\n", ncomp);
+ lept_stderr("num classes = %d\n", nclass);
+ lept_stderr("template lattice size: w = %d, h = %d\n", cellw, cellh);
+#endif
+
+ ninit = ncomp;
+ if (ncomp > 1000000) { /* fuzz protection */
+ L_WARNING("ncomp > 1M\n", procName);
+ ninit = 1000000;
+ }
+ naclass = numaCreate(ninit);
+ napage = numaCreate(ninit);
+ ptaul = ptaCreate(ninit);
+ 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, (l_float32)ipage);
+ numaAddNumber(naclass, (l_float32)iclass);
+ ptaAddPt(ptaul, (l_float32)x, (l_float32)y);
+ }
+
+ jbdata = (JBDATA *)LEPT_CALLOC(1, sizeof(JBDATA));
+ 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;
+}
+
+
+/*!
+ * \brief jbDataRender()
+ *
+ * \param[in] jbdata
+ * \param[in] 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) {
+ pixaDestroy(&pixad);
+ 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;
+}
+
+
+/*!
+ * \brief jbGetULCorners()
+ *
+ * \param[in] jbclasser
+ * \param[in] pixs full res image
+ * \param[in] boxa of c.c. bounding rectangles for this page
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) {
+ LEPT_FREE(sumtab);
+ 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)
+ lept_stderr("dx = %d, dy = %d\n", dx, dy); */
+ ptaAddPt(ptaul, (l_float32)(x - idelx + dx), (l_float32)(y - idely + dy));
+ boxDestroy(&box);
+ pixDestroy(&pixt);
+ }
+
+ LEPT_FREE(sumtab);
+ return 0;
+}
+
+
+/*!
+ * \brief jbGetLLCorners()
+ *
+ * \param[in] jbclasser
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, (l_float32)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};
+
+
+/*!
+ * \brief findSimilarSizedTemplatesInit()
+ *
+ * \param[in] classer
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief findSimilarSizedTemplatesNext()
+ *
+ * \param[in] 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,
+ (l_uint64)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;
+ }
+}
+
+
+/*!
+ * \brief finalPositioningForAlignment()
+ *
+ * \param[in] pixs input page image
+ * \param[in] x, y location of UL corner of bb of component in pixs
+ * \param[in] idelx, idely compensation to match centroids of component
+ * and template
+ * \param[in] pixt template, with JB_ADDED_PIXELS of padding
+ * on all sides
+ * \param[in] sumtab for summing fg pixels in an image
+ * \param[in] pdx, pdy 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 */
+ pixGetDimensions(pixt, &w, &h, NULL);
+ 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/leptonica/src/jbclass.h b/leptonica/src/jbclass.h
new file mode 100644
index 00000000..62aad60a
--- /dev/null
+++ b/leptonica/src/jbclass.h
@@ -0,0 +1,142 @@
+/*====================================================================*
+ - 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
+
+/*!
+ * \file jbclass.h
+ *
+ * JbClasser
+ * JbData
+ */
+
+
+ /*!
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 components already processed */
+ /*!< on fully processed pages */
+ struct Numa *nacomps; /*!< number of components on each page */
+ l_int32 sizehaus; /*!< size of square struct elem 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;
+
+
+ /*!
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+
+
+/*! JB Classifier */
+enum {
+ JB_RANKHAUS = 0,
+ JB_CORRELATION = 1
+};
+
+ /*! For jbGetComponents(): type of component to extract from images */
+/*! JB Component */
+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/leptonica/src/jp2kheader.c b/leptonica/src/jp2kheader.c
new file mode 100644
index 00000000..74ba763b
--- /dev/null
+++ b/leptonica/src/jp2kheader.c
@@ -0,0 +1,321 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file jp2kheader.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief readHeaderJp2k()
+ *
+ * \param[in] filename
+ * \param[out] pw [optional]
+ * \param[out] ph [optional]
+ * \param[out] pbps [optional] bits/sample
+ * \param[out] pspp [optional] samples/pixel
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+readHeaderJp2k(const char *filename,
+ l_int32 *pw,
+ l_int32 *ph,
+ l_int32 *pbps,
+ l_int32 *pspp)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("readHeaderJp2k");
+
+ 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;
+}
+
+
+/*!
+ * \brief freadHeaderJp2k()
+ *
+ * \param[in] fp file stream opened for read
+ * \param[out] pw [optional]
+ * \param[out] ph [optional]
+ * \param[out] pbps [optional] bits/sample
+ * \param[out] pspp [optional] samples/pixel
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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, ret;
+
+ PROCNAME("freadHeaderJp2k");
+
+ 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);
+
+ ret = readHeaderMemJp2k(buf, sizeof(buf), pw, ph, pbps, pspp);
+ rewind(fp);
+ return ret;
+}
+
+
+/*!
+ * \brief readHeaderMemJp2k()
+ *
+ * \param[in] data
+ * \param[in] size at least 80
+ * \param[out] pw [optional]
+ * \param[out] ph [optional]
+ * \param[out] pbps [optional] bits/sample
+ * \param[out] pspp [optional] samples/pixel
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+l_ok
+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;
+ if (4 * (windex + 2) + 2 >= size)
+ return ERROR_INT("image parameters end are outside of header",
+ procName, 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 < 1 || h < 1)
+ return ERROR_INT("w and h must both be > 0", procName, 1);
+ if (w > MAX_JP2K_WIDTH || h > MAX_JP2K_HEIGHT)
+ return ERROR_INT("unrealistically large sizes", procName, 1);
+ if (spp != 1 && spp != 3 && spp != 4)
+ return ERROR_INT("spp must be in 1, 3 or 4", procName, 1);
+ if (bps != 8 && bps != 16)
+ return ERROR_INT("bps must be 8 or 16", procName, 1);
+ if (pw) *pw = w;
+ if (ph) *ph = h;
+ if (pspp) *pspp = spp;
+ if (pbps) *pbps = bps;
+ return 0;
+}
+
+
+/*
+ * fgetJp2kResolution()
+ *
+ * Input: fp (file 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, maxres;
+
+ 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;
+ }
+ if (nbytes < 80 || loc >= nbytes - 13) {
+ L_WARNING("image resolution found without enough space\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);
+ if (ydenom == 0 || xdenom == 0) {
+ L_WARNING("bad data: ydenom or xdenom is 0\n", procName);
+ LEPT_FREE(data);
+ return 1;
+ }
+ 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);
+
+ /* Sanity check for bad data */
+ maxres = 100000.0; /* ppi */
+ if (xres > maxres || yres > maxres) {
+ L_WARNING("ridiculously large resolution\n", procName);
+ } else {
+ *pyres = (l_int32)(yres + 0.5);
+ *pxres = (l_int32)(xres + 0.5);
+ }
+
+ LEPT_FREE(data);
+ return 0;
+}
+
+/* --------------------------------------------*/
+#endif /* USE_JP2KHEADER */
diff --git a/leptonica/src/jp2kheaderstub.c b/leptonica/src/jp2kheaderstub.c
new file mode 100644
index 00000000..41756c63
--- /dev/null
+++ b/leptonica/src/jp2kheaderstub.c
@@ -0,0 +1,75 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file jp2kheaderstub.c
+ * <pre>
+ *
+ * Stubs for jp2kheader.c functions
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if !USE_JP2KHEADER /* defined in environ.h */
+/* --------------------------------------------*/
+
+l_ok 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_ok freadHeaderJp2k(FILE *fp, l_int32 *pw, l_int32 *ph,
+ l_int32 *pbps, l_int32 *pspp)
+{
+ return ERROR_INT("function not present", "freadHeaderJp2k", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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/leptonica/src/jp2kio.c b/leptonica/src/jp2kio.c
new file mode 100644
index 00000000..58a9c36e
--- /dev/null
+++ b/leptonica/src/jp2kio.c
@@ -0,0 +1,949 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file jp2kio.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#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 versions 2.0 and newer */
+#ifdef LIBJP2K_HEADER
+#include LIBJP2K_HEADER
+#else
+#include <openjpeg.h>
+#endif
+
+ /* 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) *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixReadJp2k()
+ *
+ * \param[in] filename
+ * \param[in] reduction scaling factor: 1, 2, 4, 8, 16
+ * \param[in] box [optional] for extracting a subregion, can be null
+ * \param[in] hint a bitwise OR of L_JP2K_* values; 0 for default
+ * \param[in] debug output callback messages, etc
+ * \return pix 8 or 32 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixReadStreamJp2k()
+ *
+ * \param[in] fp file stream
+ * \param[in] reduction scaling factor: 1, 2, 4, 8
+ * \param[in] box [optional] for extracting a subregion, can be null
+ * \param[in] hint a bitwise OR of L_JP2K_* values; 0 for default
+ * \param[in] debug output callback messages, etc
+ * \return pix 8 or 32 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixReadJp2k() for usage.
+ * </pre>
+ */
+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;
+
+ /* Get a decoder handle */
+ if ((l_codec = opj_create_decompress(OPJ_CODEC_JP2)) == NULL) {
+ L_ERROR("failed to make the codec\n", procName);
+ 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_destroy_codec(l_codec);
+ return NULL;
+ }
+
+ /* 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);
+ 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;
+ }
+
+ /* Finished with the byte stream and the codec */
+ opj_stream_destroy(l_stream);
+ opj_destroy_codec(l_codec);
+
+ /* 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);
+ }
+
+ /* 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixWriteJp2k()
+ *
+ * \param[in] filename
+ * \param[in] pix any depth, cmap is OK
+ * \param[in] quality SNR > 0; 0 for default (34); 100 for lossless
+ * \param[in] nlevels resolution levels; <= 10; default = 5
+ * \param[in] hint a bitwise OR of L_JP2K_* values; 0 for default
+ * \param[in] debug output callback messages, etc
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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; 100 for lossless.
+ * (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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixWriteStreamJp2k()
+ *
+ * \param[in] fp file stream
+ * \param[in] pix any depth, cmap is OK
+ * \param[in] quality SNR > 0; 0 for default (34); 100 for lossless
+ * \param[in] nlevels <= 10
+ * \param[in] hint a bitwise OR of L_JP2K_* values; 0 for default
+ * \param[in] debug output callback messages, etc
+ * \return 0 if OK, 1 on error
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+pixWriteStreamJp2k(FILE *fp,
+ PIX *pix,
+ l_int32 quality,
+ l_int32 nlevels,
+ l_int32 hint,
+ l_int32 debug)
+{
+l_int32 w, h, d, success;
+l_float32 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);
+
+ snr = (l_float32)quality;
+ if (snr <= 0) snr = 34.0; /* default */
+ if (snr < 27)
+ L_WARNING("SNR = %d < 27; very low\n", procName, (l_int32)snr);
+ if (snr == 100) snr = 0; /* for lossless */
+ if (snr > 45) {
+ L_WARNING("SNR > 45; using lossless encoding\n", procName);
+ snr = 0;
+ }
+
+ 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. */
+ pixSetPadBits(pixs, 0);
+ 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;
+
+ /* 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);
+}
+
+
+/*!
+ * \brief pixConvertToOpjImage()
+ *
+ * \param[in] pix 8 or 32 bpp
+ * \return opj_image, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Input pix is 8 bpp grayscale, 32 bpp rgb, or 32 bpp rgba.
+ * (2) Gray + alpha pix are all represented as rgba.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixReadMemJp2k()
+ *
+ * \param[in] data const; jpeg-encoded
+ * \param[in] size of data
+ * \param[in] reduction scaling factor: 1, 2, 4, 8
+ * \param[in] box [optional] for extracting a subregion, can be null
+ * \param[in] hint a bitwise OR of L_JP2K_* values; 0 for default
+ * \param[in] debug output callback messages, etc
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (PIX *)ERROR_PTR("stream not opened", procName, NULL);
+ pix = pixReadStreamJp2k(fp, reduction, box, hint, debug);
+ fclose(fp);
+ if (!pix) L_ERROR("pix not read\n", procName);
+ return pix;
+}
+
+
+/*!
+ * \brief pixWriteMemJp2k()
+ *
+ * \param[out] pdata data of jpeg compressed image
+ * \param[out] psize size of returned data
+ * \param[in] pix 8 or 32 bpp
+ * \param[in] quality SNR > 0; 0 for default (34); 100 for lossless
+ * \param[in] nlevels 0 for default
+ * \param[in] hint a bitwise OR of L_JP2K_* values; 0 for default
+ * \param[in] debug output callback messages, etc
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixWriteJp2k() for usage. This version writes to
+ * memory instead of to a file stream.
+ * </pre>
+ */
+l_ok
+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_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ 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("opjCreateStream");
+
+ 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_LIBJP2K */
+/* --------------------------------------------*/
diff --git a/leptonica/src/jp2kiostub.c b/leptonica/src/jp2kiostub.c
new file mode 100644
index 00000000..f8340677
--- /dev/null
+++ b/leptonica/src/jp2kiostub.c
@@ -0,0 +1,98 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file jp2kiostub.c
+ * <pre>
+ *
+ * Stubs for jp2kio.c functions
+ * </pre>
+ */
+
+#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_ok 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_ok 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_ok 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/leptonica/src/jpegio.c b/leptonica/src/jpegio.c
new file mode 100644
index 00000000..993683da
--- /dev/null
+++ b/leptonica/src/jpegio.c
@@ -0,0 +1,1303 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file jpegio.c
+ * <pre>
+ *
+ * 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 readResolutionMemJpeg()
+ * 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
+ * --------------------------------------------
+ * By default, the U,V (chroma) channels use 2x2 subsampling (aka 4.2.0).
+ * Higher quality for color, using full resolution (4.4.4) for the chroma,
+ * is obtained by setting a field in the pix before writing:
+ * pixSetChromaSampling(pix, L_NO_CHROMA_SAMPLING_JPEG);
+ * The field can be reset for default 4.2.0 subsampling with
+ * pixSetChromaSampling(pix, 0);
+ *
+ * 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 continue to read if the data is corrupted
+ * ------------------------------------------------
+ * By default, if data is corrupted we make every effort to fail
+ * to return a pix. (Failure is not always possible with bad
+ * data, because in some situations, such as during arithmetic
+ * decoding, the low-level jpeg library will not abort or raise
+ * a warning.) To attempt to ignore warnings and get a pix when data
+ * is corrupted, use L_JPEG_CONTINUE_WITH_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().
+ * </pre>
+ */
+
+#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) *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixReadJpeg()
+ *
+ * \param[in] filename
+ * \param[in] cmapflag 0 for no colormap in returned pix;
+ * 1 to return an 8 bpp cmapped pix if spp = 3 or 4
+ * \param[in] reduction scaling factor: 1, 2, 4 or 8
+ * \param[out] pnwarn [optional] number of warnings about
+ * corrupted data
+ * \param[in] hint a bitwise OR of L_JPEG_* values; 0 for default
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * 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, depending on the severity of the
+ * data corruption one of two things will happen:
+ * (a) 0 or more warnings are generated, or
+ * (b) the library will immediately attempt to exit. This is
+ * caught by our error handler and no pix will be returned.
+ * If data corruption causes a warning, the default action
+ * is to abort the read. The reason is that malformed jpeg
+ * data sequences exist that prevent termination of the read.
+ * To allow the decoding to continue after corrupted data is
+ * encountered, include L_JPEG_CONTINUE_WITH_BAD_DATA in %hint.
+ * (5) The possible hint values are given in the enum in imageio.h:
+ * * L_JPEG_READ_LUMINANCE
+ * * L_JPEG_CONTINUE_WITH_BAD_DATA
+ * Default (0) is to do neither, and to fail on warning of data
+ * corruption.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixReadStreamJpeg()
+ *
+ * \param[in] fp file stream
+ * \param[in] cmapflag 0 for no colormap in returned pix;
+ * 1 to return an 8 bpp cmapped pix if spp = 3 or 4
+ * \param[in] reduction scaling factor: 1, 2, 4 or 8
+ * \param[out] pnwarn [optional] number of warnings
+ * \param[in] hint a bitwise OR of L_JPEG_* values; 0 for default
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For usage, see pixReadJpeg().
+ * (2) The jpeg comment, if it exists, is not stored in the pix.
+ * </pre>
+ */
+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 nlinesread, abort_on_warning;
+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)) {
+ jpeg_destroy_decompress(&cinfo);
+ 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) {
+ jpeg_destroy_decompress(&cinfo);
+ 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), (size_t)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);
+ rowbuffer = NULL;
+ pixDestroy(&pix);
+ jpeg_destroy_decompress(&cinfo);
+ return (PIX *)ERROR_PTR("rowbuffer or pix not made", procName, NULL);
+ }
+
+ /* Initialize decompression.
+ * Set up a colormap for color quantization if requested.
+ * Arithmetic coding is rarely used on the jpeg data, but if it
+ * is, jpeg_start_decompress() handles the decoding.
+ * With corrupted encoded data, this can take an arbitrarily
+ * long time, and fuzzers are finding examples. Unfortunately,
+ * there is no way to get a callback from an error in this phase. */
+ 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. It appears that jpeg_read_scanlines() always
+ * returns 1 when you ask for one scanline, but we test anyway.
+ * During decoding of scanlines, warnings are issued if corrupted
+ * data is found. The default behavior is to abort reading
+ * when a warning is encountered. By setting the hint to have
+ * the same bit set as in L_JPEG_CONTINUE_WITH_BAD_DATA, e.g.,
+ * hint = hint | L_JPEG_CONTINUE_WITH_BAD_DATA
+ * reading will continue after warnings, in an attempt to return
+ * the (possibly corrupted) image. */
+ abort_on_warning = (hint & L_JPEG_CONTINUE_WITH_BAD_DATA) ? 0 : 1;
+ for (i = 0; i < h; i++) {
+ nlinesread = jpeg_read_scanlines(&cinfo, &rowbuffer, (JDIMENSION)1);
+ nwarn = cinfo.err->num_warnings;
+ if (nlinesread == 0 || (abort_on_warning && nwarn > 0)) {
+ L_ERROR("read error at scanline %d; nwarn = %d\n",
+ procName, i, nwarn);
+ pixDestroy(&pix);
+ jpeg_destroy_decompress(&cinfo);
+ LEPT_FREE(rowbuffer);
+ rowbuffer = NULL;
+ if (pnwarn) *pnwarn = nwarn;
+ 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]);
+ }
+ }
+
+ /* 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)
+ lept_stderr("output spp = %d, spp = %d\n",
+ cinfo.output_components, spp);
+
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ LEPT_FREE(rowbuffer);
+ rowbuffer = NULL;
+ if (pnwarn) *pnwarn = nwarn;
+ if (nwarn > 0)
+ L_WARNING("%d warning(s) of bad data\n", procName, nwarn);
+ return pix;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Read jpeg metadata from file *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief readHeaderJpeg()
+ *
+ * \param[in] filename
+ * \param[out] pw [optional]
+ * \param[out] ph [optional]
+ * \param[out] pspp [optional] samples/pixel
+ * \param[out] pycck [optional] 1 if ycck color space; 0 otherwise
+ * \param[out] pcmyk [optional] 1 if cmyk color space; 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief freadHeaderJpeg()
+ *
+ * \param[in] fp file stream
+ * \param[out] pw [optional]
+ * \param[out] ph [optional]
+ * \param[out] pspp [optional] samples/pixel
+ * \param[out] pycck [optional] 1 if ycck color space; 0 otherwise
+ * \param[out] pcmyk [optional] 1 if cmyk color space; 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+freadHeaderJpeg(FILE *fp,
+ l_int32 *pw,
+ l_int32 *ph,
+ l_int32 *pspp,
+ l_int32 *pycck,
+ l_int32 *pcmyk)
+{
+l_int32 spp, w, h;
+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;
+ w = cinfo.output_width;
+ h = cinfo.output_height;
+ if (w < 1 || h < 1 || spp < 1 || spp > 4) {
+ jpeg_destroy_decompress(&cinfo);
+ rewind(fp);
+ return ERROR_INT("bad jpeg image parameters", procName, 1);
+ }
+
+ 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;
+}
+
+
+/*
+ * \brief fgetJpegResolution()
+ *
+ * \param[in] fp file stream
+ * \param[out] pxres, pyres resolutions
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*
+ * \brief fgetJpegComment()
+ *
+ * \param[in] fp file stream opened for read
+ * \param[out] pcomment comment
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Side-effect: this rewinds the stream.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixWriteJpeg()
+ *
+ * \param[in] filename
+ * \param[in] pix any depth; cmap is OK
+ * \param[in] quality 1 - 100; 75 is default
+ * \param[in] progressive 0 for baseline sequential; 1 for progressive
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixWriteStreamJpeg()
+ *
+ * \param[in] fp file stream
+ * \param[in] pixs any depth; cmap is OK
+ * \param[in] quality 1 - 100; 75 is default value; 0 is also default
+ * \param[in] progressive 0 for baseline sequential; 1 for progressive
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+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 (quality > 100) {
+ L_ERROR("invalid jpeg quality; setting to 75\n", procName);
+ quality = 75;
+ }
+
+ /* 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);
+ pixSetPadBits(pix, 0);
+
+ 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);
+
+ /* Cap the text the length limit, 65533, for JPEG_COM payload.
+ * Just to be safe, subtract 100 to cover the Adobe name space. */
+ if ((text = pixGetText(pix)) != NULL) {
+ if (strlen(text) > 65433) {
+ L_WARNING("text is %zu bytes; clipping to 65433\n",
+ procName, strlen(text));
+ text[65433] = '\0';
+ }
+ 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);
+ rowbuffer = NULL;
+ jpeg_destroy_compress(&cinfo);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Read/write to memory *
+ *---------------------------------------------------------------------*/
+
+/*!
+ * \brief pixReadMemJpeg()
+ *
+ * \param[in] data const; jpeg-encoded
+ * \param[in] size of data
+ * \param[in] cmflag colormap flag 0 means return RGB image if color;
+ * 1 means create a colormap and return
+ * an 8 bpp colormapped image if color
+ * \param[in] reduction scaling factor: 1, 2, 4 or 8
+ * \param[out] pnwarn [optional] number of warnings
+ * \param[in] hint a bitwise OR of L_JPEG_* values; 0 for default
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (PIX *)ERROR_PTR("stream not opened", procName, NULL);
+ 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;
+}
+
+
+/*!
+ * \brief readHeaderMemJpeg()
+ *
+ * \param[in] data const; jpeg-encoded
+ * \param[in] size of data
+ * \param[out] pw [optional] width
+ * \param[out] ph [optional] height
+ * \param[out] pspp [optional] samples/pixel
+ * \param[out] pycck [optional] 1 if ycck color space; 0 otherwise
+ * \param[out] pcmyk [optional] 1 if cmyk color space; 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = freadHeaderJpeg(fp, pw, ph, pspp, pycck, pcmyk);
+ fclose(fp);
+ return ret;
+}
+
+
+/*!
+ * \brief readResolutionMemJpeg()
+ *
+ * \param[in] data const; jpeg-encoded
+ * \param[in] size of data
+ * \param[out] pxres [optional]
+ * \param[out] pyres [optional]
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+readResolutionMemJpeg(const l_uint8 *data,
+ size_t size,
+ l_int32 *pxres,
+ l_int32 *pyres)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("readResolutionMemJpeg");
+
+ if (pxres) *pxres = 0;
+ if (pyres) *pyres = 0;
+ if (!data)
+ return ERROR_INT("data not defined", procName, 1);
+ if (!pxres && !pyres)
+ return ERROR_INT("no results requested", procName, 1);
+
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = fgetJpegResolution(fp, pxres, pyres);
+ fclose(fp);
+ return ret;
+}
+
+
+/*!
+ * \brief pixWriteMemJpeg()
+ *
+ * \param[out] pdata data of jpeg compressed image
+ * \param[out] psize size of returned data
+ * \param[in] pix any depth; cmap is OK
+ * \param[in] quality 1 - 100; 75 is default value; 0 is also default
+ * \param[in] progressive 0 for baseline sequential; 1 for progressive
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixWriteStreamJpeg() for usage. This version writes to
+ * memory instead of to a file stream.
+ * </pre>
+ */
+l_ok
+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_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixSetChromaSampling()
+ *
+ * \param[in] pix
+ * \param[in] sampling 1 for subsampling; 0 for no subsampling
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief 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);
+}
+
+/*!
+ * \brief 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);
+}
+
+/* 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++);
+}
+
+/*!
+ * \brief 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;
+ if (pcb_data->comment) { /* clear before overwriting previous comment */
+ LEPT_FREE(pcb_data->comment);
+ pcb_data->comment = NULL;
+ }
+ pcb_data->comment = comment;
+ return 1;
+}
+
+/* --------------------------------------------*/
+#endif /* HAVE_LIBJPEG */
+/* --------------------------------------------*/
diff --git a/leptonica/src/jpegiostub.c b/leptonica/src/jpegiostub.c
new file mode 100644
index 00000000..7fa18142
--- /dev/null
+++ b/leptonica/src/jpegiostub.c
@@ -0,0 +1,151 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file jpegiostub.c
+ * <pre>
+ *
+ * Stubs for jpegio.c functions
+ * </pre>
+ */
+
+#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_ok 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_ok 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_ok pixWriteJpeg(const char *filename, PIX *pix, l_int32 quality,
+ l_int32 progressive)
+{
+ return ERROR_INT("function not present", "pixWriteJpeg", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok 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_ok readResolutionMemJpeg(const l_uint8 *data, size_t size,
+ l_int32 *pxres, l_int32 *pyres)
+{
+ return ERROR_INT("function not present", "readResolutionMemJpeg", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWriteMemJpeg(l_uint8 **pdata, size_t *psize, PIX *pix,
+ l_int32 quality, l_int32 progressive)
+{
+ return ERROR_INT("function not present", "pixWriteMemJpeg", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixSetChromaSampling(PIX *pix, l_int32 sampling)
+{
+ return ERROR_INT("function not present", "pixSetChromaSampling", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+/* --------------------------------------------*/
+#endif /* !HAVE_LIBJPEG */
+/* --------------------------------------------*/
diff --git a/leptonica/src/kernel.c b/leptonica/src/kernel.c
new file mode 100644
index 00000000..9564e50c
--- /dev/null
+++ b/leptonica/src/kernel.c
@@ -0,0 +1,1286 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file kernel.c
+ * <pre>
+ *
+ * Basic operations on kernels for image convolution
+ *
+ * Create/destroy/copy
+ * L_KERNEL *kernelCreate()
+ * void kernelDestroy()
+ * L_KERNEL *kernelCopy()
+ *
+ * Accessors:
+ * l_int32 kernelGetElement()
+ * l_int32 kernelSetElement()
+ * l_int32 kernelGetParameters()
+ * l_int32 kernelSetOrigin()
+ * l_int32 kernelGetSum()
+ * l_int32 kernelGetMinMax()
+ *
+ * Normalize/invert
+ * L_KERNEL *kernelNormalize()
+ * L_KERNEL *kernelInvert()
+ *
+ * Helper function
+ * l_float32 **create2dFloatArray()
+ *
+ * Serialized I/O
+ * L_KERNEL *kernelRead()
+ * L_KERNEL *kernelReadStream()
+ * l_int32 kernelWrite()
+ * l_int32 kernelWriteStream()
+ *
+ * Making a kernel from a compiled string
+ * L_KERNEL *kernelCreateFromString()
+ *
+ * Making a kernel from a simple file format
+ * L_KERNEL *kernelCreateFromFile()
+ *
+ * Making a kernel from a Pix
+ * L_KERNEL *kernelCreateFromPix()
+ *
+ * Display a kernel in a pix
+ * PIX *kernelDisplayInPix()
+ *
+ * Parse string to extract numbers
+ * NUMA *parseStringForNumbers()
+ *
+ * Simple parametric kernels
+ * L_KERNEL *makeFlatKernel()
+ * L_KERNEL *makeGaussianKernel()
+ * L_KERNEL *makeGaussianKernelSep()
+ * L_KERNEL *makeDoGKernel()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+ /* Array size must be > 0 and not larger than this */
+static const l_uint32 MaxArraySize = 100000;
+
+/*------------------------------------------------------------------------*
+ * Create / Destroy *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief kernelCreate()
+ *
+ * \param[in] height, width
+ * \return kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) kernelCreate() initializes all values to 0.
+ * (2) After this call, (cy,cx) and nonzero data values must be
+ * assigned.
+ * (2) The number of kernel elements must be less than 2^29.
+ * </pre>
+ */
+L_KERNEL *
+kernelCreate(l_int32 height,
+ l_int32 width)
+{
+l_uint64 size64;
+L_KERNEL *kel;
+
+ PROCNAME("kernelCreate");
+
+ if (width <= 0)
+ return (L_KERNEL *)ERROR_PTR("width must be > 0", procName, NULL);
+ if (height <= 0)
+ return (L_KERNEL *)ERROR_PTR("height must be > 0", procName, NULL);
+
+ /* Avoid overflow in malloc arg */
+ size64 = (l_uint64)width * (l_uint64)height;
+ if (size64 >= (1LL << 29)) {
+ L_ERROR("requested width = %d, height = %d\n", procName, width, height);
+ return (L_KERNEL *)ERROR_PTR("size >= 2^29", procName, NULL);
+ }
+
+ kel = (L_KERNEL *)LEPT_CALLOC(1, sizeof(L_KERNEL));
+ kel->sy = height;
+ kel->sx = width;
+ if ((kel->data = create2dFloatArray(height, width)) == NULL) {
+ LEPT_FREE(kel);
+ return (L_KERNEL *)ERROR_PTR("data not allocated", procName, NULL);
+ }
+ return kel;
+}
+
+
+/*!
+ * \brief kernelDestroy()
+ *
+ * \param[in,out] pkel will be set to null before returning
+ * \return void
+ */
+void
+kernelDestroy(L_KERNEL **pkel)
+{
+l_int32 i;
+L_KERNEL *kel;
+
+ 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;
+}
+
+
+/*!
+ * \brief kernelCopy()
+ *
+ * \param[in] kels source kernel
+ * \return keld copy of kels, or NULL on error
+ */
+L_KERNEL *
+kernelCopy(L_KERNEL *kels)
+{
+l_int32 i, j, sx, sy, cx, cy;
+L_KERNEL *keld;
+
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief kernelGetElement()
+ *
+ * \param[in] kel
+ * \param[in] row
+ * \param[in] col
+ * \param[out] pval
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+kernelGetElement(L_KERNEL *kel,
+ l_int32 row,
+ l_int32 col,
+ l_float32 *pval)
+{
+ 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;
+}
+
+
+/*!
+ * \brief kernelSetElement()
+ *
+ * \param[in] kel kernel
+ * \param[in] row
+ * \param[in] col
+ * \param[in] val
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+kernelSetElement(L_KERNEL *kel,
+ l_int32 row,
+ l_int32 col,
+ l_float32 val)
+{
+ 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;
+}
+
+
+/*!
+ * \brief kernelGetParameters()
+ *
+ * \param[in] kel kernel
+ * \param[out] psy, psx, pcy, pcx [optional] each can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+kernelGetParameters(L_KERNEL *kel,
+ l_int32 *psy,
+ l_int32 *psx,
+ l_int32 *pcy,
+ l_int32 *pcx)
+{
+ 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;
+}
+
+
+/*!
+ * \brief kernelSetOrigin()
+ *
+ * \param[in] kel kernel
+ * \param[in] cy, cx
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+kernelSetOrigin(L_KERNEL *kel,
+ l_int32 cy,
+ l_int32 cx)
+{
+ PROCNAME("kernelSetOrigin");
+
+ if (!kel)
+ return ERROR_INT("kel not defined", procName, 1);
+ kel->cy = cy;
+ kel->cx = cx;
+ return 0;
+}
+
+
+/*!
+ * \brief kernelGetSum()
+ *
+ * \param[in] kel kernel
+ * \param[out] psum sum of all kernel values
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+kernelGetSum(L_KERNEL *kel,
+ l_float32 *psum)
+{
+l_int32 sx, sy, i, j;
+
+ 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;
+}
+
+
+/*!
+ * \brief kernelGetMinMax()
+ *
+ * \param[in] kel kernel
+ * \param[out] pmin [optional] minimum value
+ * \param[out] pmax [optional] maximum value
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+kernelGetMinMax(L_KERNEL *kel,
+ l_float32 *pmin,
+ l_float32 *pmax)
+{
+l_int32 sx, sy, i, j;
+l_float32 val, minval, maxval;
+
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief kernelNormalize()
+ *
+ * \param[in] kels source kel, to be normalized
+ * \param[in] normsum desired sum of elements in keld
+ * \return keld normalized version of kels, or NULL on error
+ * or if sum of elements is very close to 0)
+ *
+ * <pre>
+ * Notes:
+ * (1) If the sum of kernel elements is close to 0, do not
+ * try to calculate the normalized kernel. Instead,
+ * return a copy of the input kernel, with a warning.
+ * </pre>
+ */
+L_KERNEL *
+kernelNormalize(L_KERNEL *kels,
+ l_float32 normsum)
+{
+l_int32 i, j, sx, sy, cx, cy;
+l_float32 sum, factor;
+L_KERNEL *keld;
+
+ 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;
+}
+
+
+/*!
+ * \brief kernelInvert()
+ *
+ * \param[in] kels source kel, to be inverted
+ * \return keld spatially inverted, about the origin, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For convolution, the kernel is spatially inverted before
+ * a "correlation" operation is done between the kernel and the image.
+ * </pre>
+ */
+L_KERNEL *
+kernelInvert(L_KERNEL *kels)
+{
+l_int32 i, j, sx, sy, cx, cy;
+L_KERNEL *keld;
+
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief create2dFloatArray()
+ *
+ * \param[in] sy rows == height
+ * \param[in] sx columns == width
+ * \return doubly indexed array i.e., an array of sy row pointers,
+ * each of which points to an array of sx floats
+ *
+ * <pre>
+ * Notes:
+ * (1) The array[%sy][%sx] is indexed in standard "matrix notation",
+ * with the row index first.
+ * (2) The caller kernelCreate() limits the size to < 2^29 pixels.
+ * </pre>
+ */
+l_float32 **
+create2dFloatArray(l_int32 sy,
+ l_int32 sx)
+{
+l_int32 i;
+l_float32 **array;
+
+ PROCNAME("create2dFloatArray");
+
+ if (sx <= 0 || sx > MaxArraySize)
+ return (l_float32 **)ERROR_PTR("sx out of bounds", procName, NULL);
+ if (sy <= 0 || sy > MaxArraySize)
+ return (l_float32 **)ERROR_PTR("sy out of bounds", procName, NULL);
+
+ 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++)
+ array[i] = (l_float32 *)LEPT_CALLOC(sx, sizeof(l_float32));
+ return array;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Kernel serialized I/O *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief kernelRead()
+ *
+ * \param[in] fname filename
+ * \return kernel, or NULL on error
+ */
+L_KERNEL *
+kernelRead(const char *fname)
+{
+FILE *fp;
+L_KERNEL *kel;
+
+ 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) {
+ fclose(fp);
+ return (L_KERNEL *)ERROR_PTR("kel not returned", procName, NULL);
+ }
+ fclose(fp);
+
+ return kel;
+}
+
+
+/*!
+ * \brief kernelReadStream()
+ *
+ * \param[in] fp file stream
+ * \return kernel, or NULL on error
+ */
+L_KERNEL *
+kernelReadStream(FILE *fp)
+{
+l_int32 sy, sx, cy, cx, i, j, ret, version, ignore;
+L_KERNEL *kel;
+
+ 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 (sx > MaxArraySize || sy > MaxArraySize) {
+ L_ERROR("sx = %d or sy = %d > %d\n", procName, sx, sy, MaxArraySize);
+ return 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;
+}
+
+
+/*!
+ * \brief kernelWrite()
+ *
+ * \param[in] fname output file
+ * \param[in] kel kernel
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+kernelWrite(const char *fname,
+ L_KERNEL *kel)
+{
+FILE *fp;
+
+ 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;
+}
+
+
+/*!
+ * \brief kernelWriteStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] kel
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+kernelWriteStream(FILE *fp,
+ L_KERNEL *kel)
+{
+l_int32 sx, sy, cx, cy, i, j;
+
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief kernelCreateFromString()
+ *
+ * \param[in] h, w height, width
+ * \param[in] cy, cx origin
+ * \param[in] kdata
+ * \return kernel of the given size, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The data is an array of chars, in row-major order, giving
+ * space separated integers in the range [-255 ... 255].
+ * (2) The only other formatting limitation is that you must
+ * leave space between the last number in each row and
+ * the double-quote. If possible, it's also nice to have each
+ * line in the string represent a line in the kernel; e.g.,
+ * static const char *kdata =
+ * " 20 50 20 "
+ * " 70 140 70 "
+ * " 20 50 20 ";
+ * </pre>
+ */
+L_KERNEL *
+kernelCreateFromString(l_int32 h,
+ l_int32 w,
+ l_int32 cy,
+ l_int32 cx,
+ const char *kdata)
+{
+l_int32 n, i, j, index;
+l_float32 val;
+L_KERNEL *kel;
+NUMA *na;
+
+ 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) {
+ kernelDestroy(&kel);
+ numaDestroy(&na);
+ lept_stderr("w = %d, h = %d, num ints = %d\n", w, h, n);
+ return (L_KERNEL *)ERROR_PTR("invalid integer data", 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief kernelCreateFromFile()
+ *
+ * \param[in] filename
+ * \return kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The file contains, in the following order:
+ * ~ Any number of comment lines starting with '#' are ignored
+ * ~ The height and width of the kernel
+ * ~ The y and x values of the kernel origin
+ * ~ The kernel data, formatted as lines of numbers (integers
+ * or floats) for the kernel values in row-major order,
+ * and with no other punctuation.
+ * (Note: this differs from kernelCreateFromString(),
+ * where each line must begin and end with a double-quote
+ * to tell the compiler it's part of a string.)
+ * ~ The kernel specification ends when a blank line,
+ * a comment line, or the end of file is reached.
+ * (2) All lines must be left-justified.
+ * (3) See kernelCreateFromString() for a description of the string
+ * format for the kernel data. As an example, here are the lines
+ * of a valid kernel description file In the file, all lines
+ * are left-justified:
+ * \code
+ * # small 3x3 kernel
+ * 3 3
+ * 1 1
+ * 25.5 51 24.3
+ * 70.2 146.3 73.4
+ * 20 50.9 18.4
+ * \endcode
+ * </pre>
+ */
+L_KERNEL *
+kernelCreateFromFile(const char *filename)
+{
+char *filestr, *line;
+l_int32 nlines, i, j, first, index, w, h, cx, cy, n;
+l_float32 val;
+size_t size;
+NUMA *na, *nat;
+SARRAY *sa;
+L_KERNEL *kel;
+
+ PROCNAME("kernelCreateFromFile");
+
+ if (!filename)
+ return (L_KERNEL *)ERROR_PTR("filename not defined", procName, NULL);
+
+ if ((filestr = (char *)l_binaryRead(filename, &size)) == NULL)
+ return (L_KERNEL *)ERROR_PTR("file not found", procName, NULL);
+ if (size == 0) {
+ LEPT_FREE(filestr);
+ return (L_KERNEL *)ERROR_PTR("file is empty", procName, NULL);
+ }
+
+ sa = sarrayCreateLinesFromString(filestr, 1);
+ LEPT_FREE(filestr);
+ nlines = sarrayGetCount(sa);
+
+ /* Find the first data line. */
+ for (i = 0, first = 0; i < nlines; i++) {
+ line = sarrayGetString(sa, i, L_NOCOPY);
+ if (line[0] != '#') {
+ first = i;
+ break;
+ }
+ }
+
+ /* Find the kernel dimensions and origin location. */
+ line = sarrayGetString(sa, first, L_NOCOPY);
+ if (sscanf(line, "%d %d", &h, &w) != 2) {
+ sarrayDestroy(&sa);
+ return (L_KERNEL *)ERROR_PTR("error reading h,w", procName, NULL);
+ }
+ if (h > MaxArraySize || w > MaxArraySize) {
+ L_ERROR("h = %d or w = %d > %d\n", procName, h, w, MaxArraySize);
+ sarrayDestroy(&sa);
+ return NULL;
+ }
+ line = sarrayGetString(sa, first + 1, L_NOCOPY);
+ if (sscanf(line, "%d %d", &cy, &cx) != 2) {
+ sarrayDestroy(&sa);
+ return (L_KERNEL *)ERROR_PTR("error reading cy,cx", 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);
+ lept_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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief kernelCreateFromPix()
+ *
+ * \param[in] pix
+ * \param[in] cy, cx origin of kernel
+ * \return kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The origin must be positive and within the dimensions of the pix.
+ * </pre>
+ */
+L_KERNEL *
+kernelCreateFromPix(PIX *pix,
+ l_int32 cy,
+ l_int32 cx)
+{
+l_int32 i, j, w, h, d;
+l_uint32 val;
+L_KERNEL *kel;
+
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief kernelDisplayInPix()
+ *
+ * \param[in] kel kernel
+ * \param[in] size of grid interiors; odd; either 1 or a minimum size
+ * of 17 is enforced
+ * \param[in] gthick grid thickness; either 0 or a minimum size of 2
+ * is enforced
+ * \return pix display of kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This gives a visual representation of a kernel.
+ * (2) There are two modes of display:
+ * (a) Grid lines of minimum width 2, surrounding regions
+ * representing kernel elements of minimum size 17,
+ * with a "plus" mark at the kernel origin, or
+ * (b) A pix without grid lines and using 1 pixel per kernel element.
+ * (3) For both cases, the kernel absolute value is displayed,
+ * normalized such that the maximum absolute value is 255.
+ * (4) Large 2D separable kernels should be used for convolution
+ * with two 1D kernels. However, for the bilateral filter,
+ * the computation time is independent of the size of the
+ * 2D content kernel.
+ * </pre>
+ */
+PIX *
+kernelDisplayInPix(L_KERNEL *kel,
+ l_int32 size,
+ l_int32 gthick)
+{
+l_int32 i, j, w, h, sx, sy, cx, cy, width, x0, y0;
+l_int32 normval;
+l_float32 minval, maxval, max, val, norm;
+PIX *pixd, *pixt0, *pixt1;
+
+ 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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief parseStringForNumbers()
+ *
+ * \param[in] str string containing numbers; not changed
+ * \param[in] seps string of characters that can be used between ints
+ * \return numa of numbers found, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The numbers can be ints or floats.
+ * </pre>
+ */
+NUMA *
+parseStringForNumbers(const char *str,
+ const char *seps)
+{
+char *newstr, *head;
+char *tail = NULL;
+l_float32 val;
+NUMA *na;
+
+ 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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief makeFlatKernel()
+ *
+ * \param[in] height, width
+ * \param[in] cy, cx origin of kernel
+ * \return kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is the same low-pass filtering kernel that is used
+ * in the block convolution functions.
+ * (2) The kernel origin (%cy, %cx) is typically placed as near
+ * the center of the kernel as possible. If height and
+ * width are odd, then using %cy = height / 2 and
+ * %cx = width / 2 places the origin at the exact center.
+ * (3) This returns a normalized kernel.
+ * </pre>
+ */
+L_KERNEL *
+makeFlatKernel(l_int32 height,
+ l_int32 width,
+ l_int32 cy,
+ l_int32 cx)
+{
+l_int32 i, j;
+l_float32 normval;
+L_KERNEL *kel;
+
+ 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;
+}
+
+
+/*!
+ * \brief makeGaussianKernel()
+ *
+ * \param[in] halfh sy = 2 * halfh + 1
+ * \param[in] halfw sx = 2 * halfw + 1
+ * \param[in] stdev standard deviation
+ * \param[in] max value at (cx,cy)
+ * \return kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The kernel size (sx, sy) = (2 * %halfw + 1, 2 * %halfh + 1)
+ * (2) The kernel center (cx, cy) = (%halfw, %halfh).
+ * (3) %halfw and %halfh are typically equal, and
+ * are typically several times larger than the standard deviation.
+ * (4) If pixConvolve() is invoked with normalization (the sum of
+ * kernel elements = 1.0), use 1.0 for max (or any number that's
+ * not too small or too large).
+ * </pre>
+ */
+L_KERNEL *
+makeGaussianKernel(l_int32 halfh,
+ l_int32 halfw,
+ l_float32 stdev,
+ l_float32 max)
+{
+l_int32 sx, sy, i, j;
+l_float32 val;
+L_KERNEL *kel;
+
+ PROCNAME("makeGaussianKernel");
+
+ sx = 2 * halfw + 1;
+ sy = 2 * halfh + 1;
+ if ((kel = kernelCreate(sy, sx)) == NULL)
+ return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
+ kernelSetOrigin(kel, halfh, halfw);
+ for (i = 0; i < sy; i++) {
+ for (j = 0; j < sx; j++) {
+ val = expf(-(l_float32)((i - halfh) * (i - halfh) +
+ (j - halfw) * (j - halfw)) /
+ (2. * stdev * stdev));
+ kernelSetElement(kel, i, j, max * val);
+ }
+ }
+
+ return kel;
+}
+
+
+/*!
+ * \brief makeGaussianKernelSep()
+ *
+ * \param[in] halfh sy = 2 * halfh + 1
+ * \param[in] halfw sx = 2 * halfw + 1
+ * \param[in] stdev standard deviation
+ * \param[in] max value at (cx,cy)
+ * \param[out] pkelx x part of kernel
+ * \param[out] pkely y part of kernel
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See makeGaussianKernel() for description of input parameters.
+ * (2) These kernels are constructed so that the result of both
+ * normalized and un-normalized convolution will be the same
+ * as when convolving with pixConvolve() using the full kernel.
+ * (3) The trick for the un-normalized convolution is to have the
+ * product of the two kernel elemets at (cx,cy) be equal to %max,
+ * not max**2. That's why %max for kely is 1.0. If instead
+ * we use sqrt(%max) for both, the results are slightly less
+ * accurate, when compared to using the full kernel in
+ * makeGaussianKernel().
+ * </pre>
+ */
+l_ok
+makeGaussianKernelSep(l_int32 halfh,
+ l_int32 halfw,
+ l_float32 stdev,
+ l_float32 max,
+ L_KERNEL **pkelx,
+ L_KERNEL **pkely)
+{
+ PROCNAME("makeGaussianKernelSep");
+
+ if (!pkelx || !pkely)
+ return ERROR_INT("&kelx and &kely not defined", procName, 1);
+
+ *pkelx = makeGaussianKernel(0, halfw, stdev, max);
+ *pkely = makeGaussianKernel(halfh, 0, stdev, 1.0);
+ return 0;
+}
+
+
+/*!
+ * \brief makeDoGKernel()
+ *
+ * \param[in] halfh sy = 2 * halfh + 1
+ * \param[in] halfw sx = 2 * halfw + 1
+ * \param[in] stdev standard deviation of narrower gaussian
+ * \param[in] ratio of stdev for wide filter to stdev for narrow one
+ * \return kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The DoG (difference of gaussians) is a wavelet mother
+ * function with null total sum. By subtracting two blurred
+ * versions of the image, it acts as a bandpass filter for
+ * frequencies passed by the narrow gaussian but stopped
+ * by the wide one.See:
+ * http://en.wikipedia.org/wiki/Difference_of_Gaussians
+ * (2) The kernel size (sx, sy) = (2 * halfw + 1, 2 * halfh + 1).
+ * (3) The kernel center (cx, cy) = (halfw, halfh).
+ * (4) %halfw and %halfh are typically equal, and are typically
+ * several times larger than the standard deviation.
+ * (5) %ratio is the ratio of standard deviations of the wide
+ * to narrow gaussian. It must be >= 1.0; 1.0 is a no-op.
+ * (6) Because the kernel is a null sum, it must be invoked without
+ * normalization in pixConvolve().
+ * </pre>
+ */
+L_KERNEL *
+makeDoGKernel(l_int32 halfh,
+ l_int32 halfw,
+ l_float32 stdev,
+ l_float32 ratio)
+{
+l_int32 sx, sy, i, j;
+l_float32 pi, squaredist, highnorm, lownorm, val;
+L_KERNEL *kel;
+
+ PROCNAME("makeDoGKernel");
+
+ sx = 2 * halfw + 1;
+ sy = 2 * halfh + 1;
+ if ((kel = kernelCreate(sy, sx)) == NULL)
+ return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
+ kernelSetOrigin(kel, halfh, halfw);
+
+ pi = 3.1415926535f;
+ for (i = 0; i < sy; i++) {
+ for (j = 0; j < sx; j++) {
+ squaredist = (l_float32)((i - halfh) * (i - halfh) +
+ (j - halfw) * (j - halfw));
+ highnorm = 1. / (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/leptonica/src/leptonica-license.txt b/leptonica/src/leptonica-license.txt
new file mode 100644
index 00000000..73d44c60
--- /dev/null
+++ b/leptonica/src/leptonica-license.txt
@@ -0,0 +1,26 @@
+/*====================================================================*
+ - Copyright (C) 2001-2020 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/src/leptwin.c b/leptonica/src/leptwin.c
new file mode 100644
index 00000000..72643a0b
--- /dev/null
+++ b/leptonica/src/leptwin.c
@@ -0,0 +1,368 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file leptwin.c
+ * <pre>
+ *
+ * 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)
+ * </pre>
+ */
+
+#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;
+}
+
+/*!
+ * \brief setColormap(LPBITMAPINFO pbmi, PIXCMAP *cmap)
+ *
+ * \param[in] pbmi pointer to a BITMAPINFO describing a DIB
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixGetWindowsHBITMAP()
+ *
+ * \param[in] pix
+ * \return Windows hBitmap, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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/leptonica/src/leptwin.h b/leptonica/src/leptwin.h
new file mode 100644
index 00000000..451da6b6
--- /dev/null
+++ b/leptonica/src/leptwin.h
@@ -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/leptonica/src/libversions.c b/leptonica/src/libversions.c
new file mode 100644
index 00000000..49f3f3e8
--- /dev/null
+++ b/leptonica/src/libversions.c
@@ -0,0 +1,208 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file libversions.c
+ * <pre>
+ *
+ * Image library version number
+ * char *getImagelibVersions()
+ * </pre>
+ */
+
+#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
+#ifdef LIBJP2K_HEADER
+#include LIBJP2K_HEADER
+#else
+#include <openjpeg.h>
+#endif
+#endif
+
+
+/*---------------------------------------------------------------------*
+ * Image Library Version number *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief getImagelibVersions()
+ *
+ * <pre>
+ * Notes:
+ * (1) This returns a 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
+ * (2) The caller must free the memory.
+ * </pre>
+ */
+char *
+getImagelibVersions(void)
+{
+#if HAVE_LIBGIF || HAVE_LIBJPEG
+char buf[128];
+#endif
+l_int32 first = TRUE;
+#if HAVE_LIBJPEG || HAVE_LIBTIFF
+char *versionNumP;
+char *nextTokenP;
+#endif
+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 */
+
+ return versionStrP;
+}
diff --git a/leptonica/src/list.c b/leptonica/src/list.c
new file mode 100644
index 00000000..ba950923
--- /dev/null
+++ b/leptonica/src/list.c
@@ -0,0 +1,814 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file list.c
+ * <pre>
+ *
+ * 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:
+ * \code
+ * for (elem = head; elem; elem = nextelem) {
+ * nextelem = elem->next; (in case we destroy elem)
+ * <do something with elem->data>
+ * }
+ * \endcode
+ * 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:
+ * \code
+ * 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
+ * \endcode
+ * 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
+ * \code
+ * while (head) {
+ * obj = listRemoveFromHead(&head);
+ * <do something with obj>
+ * }
+ * \endcode
+ * Removing successive elements from the tail is equally simple:
+ * \code
+ * while (tail) {
+ * obj = listRemoveFromTail(&head, &tail);
+ * <do something with obj>
+ * }
+ * \endcode
+ * When removing an arbitrary element from a list, use
+ * \code
+ * obj = listRemoveElement(&head, elem);
+ * \endcode
+ * 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).
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*
+ * Inserting and removing elements *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief listDestroy()
+ *
+ * \param[in,out] phead head of list; will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief listAddToHead()
+ *
+ * \param[in,out] phead [optional] input head
+ * \param[in] data void* ptr, to be added
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This makes a new cell, attaches %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.
+ * </pre>
+ */
+l_ok
+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);
+
+ cell = (DLLIST *)LEPT_CALLOC(1, sizeof(DLLIST));
+ 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;
+}
+
+
+/*!
+ * \brief listAddToTail()
+ *
+ * \param[in,out] phead [may be updated], can be NULL
+ * \param[in,out] ptail [updated], can be NULL
+ * \param[in] data void* ptr, to be hung on tail cons cell
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This makes a new cell, attaches %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.
+ * </pre>
+ */
+l_ok
+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);
+
+ cell = (DLLIST *)LEPT_CALLOC(1, sizeof(DLLIST));
+ 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;
+}
+
+
+/*!
+ * \brief listInsertBefore()
+ *
+ * \param[in,out] phead [optional] input head
+ * \param[in] elem list element to be inserted in front of;
+ * must be NULL if head is NULL
+ * \param[in] data void* address, to be added
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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:
+ * \code
+ * L_BEGIN_LIST_FORWARD(head, elem)
+ * <identify an elem to insert before>
+ * listInsertBefore(&head, elem, data);
+ * L_END_LIST
+ * \endcode
+ * </pre>
+ */
+l_ok
+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 */
+ cell = (DLLIST *)LEPT_CALLOC(1, sizeof(DLLIST));
+ 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;
+}
+
+
+/*!
+ * \brief listInsertAfter()
+ *
+ * \param[in,out] phead [optional] input head
+ * \param[in] elem list element to be inserted after;
+ * must be NULL if head is NULL
+ * \param[in] data void* ptr, to be added
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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:
+ * \code
+ * L_BEGIN_LIST_FORWARD(head, elem)
+ * <identify an elem to insert after>
+ * listInsertAfter(&head, elem, data);
+ * L_END_LIST
+ * \endcode
+ * </pre>
+ */
+l_ok
+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 */
+ cell = (DLLIST *)LEPT_CALLOC(1, sizeof(DLLIST));
+ 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;
+}
+
+
+/*!
+ * \brief listRemoveElement()
+ *
+ * \param[in,out] phead input head; can be changed
+ * \param[in] elem list element to be removed
+ * \return data void* struct on cell
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief listRemoveFromHead()
+ *
+ * \param[in,out] phead head of list; updated
+ * \return data void* struct on cell, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief listRemoveFromTail()
+ *
+ * \param[in,out] phead list head must NOT be NULL; may be changed
+ * \param[in,out] ptail list tail may be NULL; always updated
+ * \return data void* struct on cell or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief listFindElement()
+ *
+ * \param[in] head list head
+ * \param[in] data void* address, to be searched for
+ * \return cell the containing cell, or NULL if not found or on error
+ *
+ * <pre>
+ * 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?
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief listFindTail()
+ *
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief listGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief listReverse()
+ *
+ * \param[in,out] phead list head; may be changed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This reverses the list in-place.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief listJoin()
+ *
+ * \param[in,out] phead1 head of first list; may be changed
+ * \param[in,out] phead2 head of second list; to be nulled
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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/leptonica/src/list.h b/leptonica/src/list.h
new file mode 100644
index 00000000..d207e79f
--- /dev/null
+++ b/leptonica/src/list.h
@@ -0,0 +1,90 @@
+/*====================================================================*
+ - 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
+
+/*!
+ * \file list.h
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+
+struct DoubleLinkedList
+{
+ struct DoubleLinkedList *prev;
+ struct DoubleLinkedList *next;
+ void *data;
+};
+typedef struct DoubleLinkedList DLLIST;
+
+
+ /*! Simple list traverse macro - forward */
+#define L_BEGIN_LIST_FORWARD(head, element) \
+ { \
+ DLLIST *_leptvar_nextelem_; \
+ for ((element) = (head); (element); (element) = _leptvar_nextelem_) { \
+ _leptvar_nextelem_ = (element)->next;
+
+
+ /*! Simple list traverse macro - reverse */
+#define L_BEGIN_LIST_REVERSE(tail, element) \
+ { \
+ DLLIST *_leptvar_prevelem_; \
+ for ((element) = (tail); (element); (element) = _leptvar_prevelem_) { \
+ _leptvar_prevelem_ = (element)->prev;
+
+
+ /*! Simple list traverse macro - end of a list traverse */
+#define L_END_LIST }}
+
+
+#endif /* LEPTONICA_LIST_H */
diff --git a/leptonica/src/mainpage.txt b/leptonica/src/mainpage.txt
new file mode 100644
index 00000000..84daa941
--- /dev/null
+++ b/leptonica/src/mainpage.txt
@@ -0,0 +1,47 @@
+/*!
+ * \mainpage Leptonica Main Page
+ *
+ * \section intro Introduction
+ * Leptonica is a pedagogically-oriented open source site containing
+ * software that is broadly useful for image processing and image analysis applications.
+ *
+ * \section features Featured operations are
+ *
+ * + Rasterop (a.k.a. bitblt)
+ * + Affine transformations (scaling, translation, rotation, shear) on images of arbitrary pixel depth
+ * + Binary and grayscale morphology, rank order, and convolution
+ * + Seedfill and connected components
+ * + Image transformations combining changes in scale and pixel depth
+ * + Pixelwise masking, blending, enhancement, arithmetic ops, etc.
+ * + Ancillary operations include
+ *
+ * + I/O for standard image formats
+ * + Utilities to handle arrays of image-related data types
+ * + Utilities for generic stacks, queues, heaps and lists;
+ * + and for byte queues and arrays of numbers and strings
+ *
+ * \section examples Example applications include
+ *
+ * + Octcube-based color quantization, with and without dithering
+ * + Modified median-cut color quantization, with and without dithering
+ * + Skew determination of text images
+ * + Segmentation of page images with mixed text and images
+ * + jbig2 unsupervised classifier
+ * + Border representations of 1 bit/pixel images and raster conversion for SVG
+ * + PostScript generation (levels 1, 2, 3) of images for device-independent output
+ * + PDF generation (levels 1, 2) of images for device-independent output
+ * + Dewarping images of text taken with a camera
+ * + Book-adaptive text recognition
+ * + Rendering text on an image
+ * + Connectivity-preserving thinning and thickening of 1 bit/pixel images
+ * + Line removal from a grayscale sketch
+ * + Search for least-cost paths on binary and grayscale images
+ *
+ * \section homepage More information on Leptonica's homepage
+ *
+ * http://www.leptonica.com/
+ *
+ * \section contact For questions and suggestions contact
+ *
+ * Dan Bloomberg (bloomberg 'at' ieee 'dot' org)
+ */
diff --git a/leptonica/src/makefile.static b/leptonica/src/makefile.static
new file mode 100644
index 00000000..e4e53af5
--- /dev/null
+++ b/leptonica/src/makefile.static
@@ -0,0 +1,401 @@
+#/*====================================================================*
+# - 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 I/O with external libraries:
+# jpeg, png, tiff, webp, jp2k, gif
+# Set flags in environ.h. The default is to have libjpeg, libpng,
+# libtiff and libz, but not libwebp, libopenjp or 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 POSIX-compliant function fstatat().
+# The default is not to use, because some systems do not support it.
+# To use this, #define HAVE_FSTATAT 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
+SED = sed
+
+# 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?
+# - std=c89 and std=c99 both define __STRICT_ANSI__, which causes
+# the omission of declarations of a number of functions, such
+# as mkstemp, fmemopen, open_memstream, fdopen, etc. So if you
+# use one of these, also use -U__STRICT_ANSI__. e.g.,
+# gcc -std=c89 -U__STRICT_ANSI__
+# - std=gnu89 and std=gnu99 do not define __STRICT_ANSI__, so
+# declaration of these other functions is included.
+# - On mingw, it is necessary to use -D__USE_MINGW_ANSI_STDIO.
+# This has no effect on non-mingw systems.
+# - use -Wunused to identify unused variables
+# - 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 -std=c99 -U__STRICT_ANSI__ -D__USE_MINGW_ANSI_STDIO
+#CC = gcc -std=c89 -U__STRICT_ANSI__ -D__USE_MINGW_ANSI_STDIO
+#CC = gcc -std=c99 -D_POSIX_C_SOURCE=200809L -D__USE_MINGW_ANSI_STDIO -D_BSD_SOURCE -DANSI -fPIC
+#CC = gcc -std=gnu89 -D__USE_MINGW_ANSI_STDIO
+#CC = gcc -std=gnu89 -D_BSD_SOURCE -DANSI -Werror -fPIC
+#CC = gcc -std=c99 -U__STRICT_ANSI__ -D__USE_MINGW_ANSI_STDIO -DNO_CONSOLE_IO
+#CC = g++ -D_BSD_SOURCE -fPIC
+#CC = g++ -Werror -D_BSD_SOURCE -fPIC
+#CC = g++ -Wunused -D_BSD_SOURCE -fPIC
+
+# Test for processor endianness
+# This version (using the definition of __BYTE_ORDER in endian.h)
+# was provided by rofl0r. It has been modified to return L_LITTLE_ENDIAN
+# if the file endian.h does not exist, which is the situation on mingw.
+ENDIANNESS = $(shell echo __BYTE_ORDER | $(CC) -include endian.h -E - | grep -q 4321 && echo L_BIG_ENDIAN || echo L_LITTLE_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 =
+CPPFLAGS = $(INCLUDES)
+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 = 81
+PATCH_REV = 0
+
+#########################################################
+
+# 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 \
+ bootnumgen3.c bootnumgen4.c \
+ boxbasic.c boxfunc1.c boxfunc2.c \
+ boxfunc3.c boxfunc4.c boxfunc5.c \
+ bytearray.c ccbord.c ccthin.c \
+ checkerboard.c classapp.c \
+ colorcontent.c colorfill.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 dnafunc1.c dnahash.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 \
+ fmorphauto.c fmorphgen.1.c fmorphgenlow.1.c \
+ fpix1.c fpix2.c \
+ gifio.c gifiostub.c gplot.c graphics.c \
+ graymorph.c grayquant.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 partify.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 ptafunc2.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 roplow.c \
+ rotate.c rotateam.c rotateorth.c rotateshear.c \
+ runlength.c sarray1.c sarray2.c \
+ scale1.c scale2.c seedfill.c \
+ sel1.c sel2.c selgen.c \
+ shear.c skew.c spixio.c \
+ stack.c stringcode.c \
+ strokes.c sudoku.c \
+ textops.c tiffio.c tiffiostub.c \
+ utils1.c utils2.c warper.c watershed.c \
+ webpanimio.c webpanimiostub.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 \
+ colorfill.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).$(PATCH_REV)
+ cd $(LIB_SHARED); rm $(notdir $@).$(MAJOR_REV); \
+ ln -s $(notdir $@).$(MAJOR_REV).$(MINOR_REV).$(PATCH_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).$(PATCH_REV)
+ cd $(LIB_SHARED); rm $(notdir $@).$(MAJOR_REV); \
+ ln -s $(notdir $@).$(MAJOR_REV).$(MINOR_REV).$(PATCH_REV) $(notdir $@).$(MAJOR_REV)
+ cd $(LIB_SHARED); rm $(notdir $@); \
+ ln -s $(notdir $@).$(MAJOR_REV) $(notdir $@)
+
+#########################################################
+
+# Rules for compiling source
+
+endianness.h: endianness.h.in
+ $(SED) -e 's/@APPLE_UNIVERSAL_BUILD@/defined (__APPLE_CC__)/g' -e 's/@ENDIANNESS@/$(ENDIANNESS)/g' endianness.h.in > endianness.h
+
+$(OBJ_NODEBUG)/%.o: %.c $(LEPTLIB_H) endianness.h
+ @$(TEST) -d $(OBJ_NODEBUG) || $(MKDIR) $(OBJ_NODEBUG)
+ $(COMPILE.c) -o $@ $<
+
+$(OBJ_DEBUG)/%.o: %.c $(LEPTLIB_H) endianness.h
+ @$(TEST) -d $(OBJ_DEBUG) || $(MKDIR) $(OBJ_DEBUG)
+ $(COMPILE.c) -o $@ $<
+
+$(OBJ_SHARED)/%.o: %.c $(LEPTLIB_H) endianness.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 \
+ endianness.h
+
+###########################################################
+
+depend:
+ /usr/bin/makedepend -DNO_PROTOS $(CPPFLAGS) $(LEPTLIB_C)
+
+###########################################################
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+
+
diff --git a/leptonica/src/map.c b/leptonica/src/map.c
new file mode 100644
index 00000000..e66e6d45
--- /dev/null
+++ b/leptonica/src/map.c
@@ -0,0 +1,268 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file map.c
+ * <pre>
+ *
+ * This is an interface for map and set functions, based on using
+ * red-black binary search trees. Because these trees are sorted,
+ * they 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* ------------------------------------------------------------- *
+ * Interface to Map *
+ * ------------------------------------------------------------- */
+L_AMAP *
+l_amapCreate(l_int32 keytype)
+{
+L_AMAP *m;
+
+ 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);
+
+ 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)
+{
+ 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)
+{
+L_ASET *s;
+
+ 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);
+
+ 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 */
+ 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/leptonica/src/maze.c b/leptonica/src/maze.c
new file mode 100644
index 00000000..181dc9a3
--- /dev/null
+++ b/leptonica/src/maze.c
@@ -0,0 +1,912 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file maze.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#ifdef _WIN32
+#include <stdlib.h>
+#include <time.h>
+#endif /* _WIN32 */
+#include "allheaders.h"
+
+static const l_int32 MinMazeWidth = 50;
+static const l_int32 MinMazeHeight = 50;
+
+static const l_float32 DefaultWallProbability = 0.65;
+static const l_float32 DefaultAnisotropyRatio = 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief generateBinaryMaze()
+ *
+ * \param[in] w, h size of maze
+ * \param[in] xi, yi initial location
+ * \param[in] wallps probability that a pixel to the side is ON
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 < MinMazeWidth)
+ w = MinMazeWidth;
+ if (h < MinMazeHeight)
+ h = MinMazeHeight;
+ if (xi <= 0 || xi >= w)
+ xi = w / 6;
+ if (yi <= 0 || yi >= h)
+ yi = h / 5;
+ if (wallps < 0.05 || wallps > 0.95)
+ wallps = DefaultWallProbability;
+ if (ranis < 0.05 || ranis > 1.0)
+ ranis = DefaultAnisotropyRatio;
+ wallpf = wallps * ranis;
+
+#if DEBUG_MAZE
+ lept_stderr("(w, h) = (%d, %d), (xi, yi) = (%d, %d)\n", w, h, xi, yi);
+ lept_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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixSearchBinaryMaze()
+ *
+ * \param[in] pixs 1 bpp, maze
+ * \param[in] xi, yi beginning point; use same initial point
+ * that was used to generate the maze
+ * \param[in] xf, yf end point, or close to it
+ * \param[out] ppixd [optional] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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
+ lept_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++) {
+ if (GET_DATA_BYTE(linep8[i], j) != 0)
+ 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;
+}
+
+
+/*!
+ * \brief localSearchForBackground()
+ *
+ * \param[in] pix
+ * \param[out] px, py starting position for search; return found position
+ * \param[in] 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixSearchGrayMaze()
+ *
+ * \param[in] pixs 1 bpp maze; w and h must be >= 50
+ * \param[in] xi, yi beginning point; use same initial point
+ * that was used to generate the maze
+ * \param[in] xf, yf end point, or close to it
+ * \param[out] ppixd [optional] 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
+ *
+ * <pre>
+ * 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_ABSdeltaV
+ * 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!
+ * </pre>
+ */
+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 (w < 50 || h < 50)
+ return (PTA *)ERROR_PTR("too small: w and h not >= 50", procName, NULL);
+ 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;
+
+ /* Allocate stuff */
+ 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) {
+ L_ERROR("heap broken!!\n", procName);
+ goto cleanup_stuff;
+ }
+ 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
+ lept_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);
+ }
+
+cleanup_stuff:
+ lheapDestroy(&lh, TRUE);
+ pixDestroy(&pixp);
+ pixDestroy(&pixr);
+ LEPT_FREE(lines8);
+ LEPT_FREE(linep8);
+ LEPT_FREE(liner32);
+ return pta;
+}
diff --git a/leptonica/src/morph.c b/leptonica/src/morph.c
new file mode 100644
index 00000000..0f01ea21
--- /dev/null
+++ b/leptonica/src/morph.c
@@ -0,0 +1,1915 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file morph.c
+ * <pre>
+ *
+ * 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 snprintf.
+ * 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 snprintf.
+ * 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 snprintf.
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixDilate()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixErode()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixHMT()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixOpen()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixClose()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCloseSafe()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixOpenGeneralized()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCloseGeneralized()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixDilateBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!sel)
+ return (PIX *)ERROR_PTR("sel not made", procName, pixd);
+ pixd = pixDilate(pixd, pixs, sel);
+ selDestroy(&sel);
+ } else {
+ if ((selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT)) == NULL)
+ return (PIX *)ERROR_PTR("selh not made", procName, pixd);
+ if ((selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT)) == NULL) {
+ selDestroy(&selh);
+ return (PIX *)ERROR_PTR("selv not made", procName, pixd);
+ }
+ pixt = pixDilate(NULL, pixs, selh);
+ pixd = pixDilate(pixd, pixt, selv);
+ pixDestroy(&pixt);
+ selDestroy(&selh);
+ selDestroy(&selv);
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixErodeBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!sel)
+ return (PIX *)ERROR_PTR("sel not made", procName, pixd);
+ pixd = pixErode(pixd, pixs, sel);
+ selDestroy(&sel);
+ } else {
+ if ((selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT)) == NULL)
+ return (PIX *)ERROR_PTR("selh not made", procName, pixd);
+ if ((selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT)) == NULL) {
+ selDestroy(&selh);
+ return (PIX *)ERROR_PTR("selv not made", procName, pixd);
+ }
+ pixt = pixErode(NULL, pixs, selh);
+ pixd = pixErode(pixd, pixt, selv);
+ pixDestroy(&pixt);
+ selDestroy(&selh);
+ selDestroy(&selv);
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixOpenBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!sel)
+ return (PIX *)ERROR_PTR("sel not made", procName, pixd);
+ pixd = pixOpen(pixd, pixs, sel);
+ selDestroy(&sel);
+ } else { /* do separably */
+ if ((selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT)) == NULL)
+ return (PIX *)ERROR_PTR("selh not made", procName, pixd);
+ if ((selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT)) == NULL) {
+ selDestroy(&selh);
+ return (PIX *)ERROR_PTR("selv not made", procName, pixd);
+ }
+ 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;
+}
+
+
+/*!
+ * \brief pixCloseBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!sel)
+ return (PIX *)ERROR_PTR("sel not made", procName, pixd);
+ pixd = pixClose(pixd, pixs, sel);
+ selDestroy(&sel);
+ } else { /* do separably */
+ if ((selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT)) == NULL)
+ return (PIX *)ERROR_PTR("selh not made", procName, pixd);
+ if ((selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT)) == NULL) {
+ selDestroy(&selh);
+ return (PIX *)ERROR_PTR("selv not made", procName, pixd);
+ }
+ 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;
+}
+
+
+/*!
+ * \brief pixCloseSafeBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!sel) {
+ pixDestroy(&pixsb);
+ return (PIX *)ERROR_PTR("sel not made", procName, pixd);
+ }
+ 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);
+ if (!selh || !selv) {
+ selDestroy(&selh);
+ selDestroy(&selv);
+ pixDestroy(&pixsb);
+ return (PIX *)ERROR_PTR("selh and selv not both made",
+ procName, pixd);
+ }
+ 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 *
+ *-----------------------------------------------------------------*/
+/* \brief selectComposableSels()
+ *
+ * \param[in] size of composed sel
+ * \param[in] direction L_HORIZ, L_VERT
+ * \param[out] psel1 [optional] contiguous sel; can be null
+ * \param[out] psel2 [optional] comb sel; can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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 > 10000)
+ return ERROR_INT("size < 1 or size > 10000", 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;
+}
+
+
+/*!
+ * \brief selectComposableSizes()
+ *
+ * \param[in] size of sel to be decomposed
+ * \param[out] pfactor1 larger factor
+ * \param[out] pfactor2 smaller factor
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This works for Sel sizes up to 10000, 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.
+ * </pre>
+ */
+l_ok
+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 > 10000)
+ return ERROR_INT("size < 1 or size > 10000", 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;
+ index = 1; /* unimportant initial value */
+ 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;
+}
+
+
+/*!
+ * \brief pixDilateCompBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixDilateCompBrick(PIX *pixd,
+ PIX *pixs,
+ l_int32 hsize,
+ l_int32 vsize)
+{
+PIX *pix1, *pix2, *pix3;
+SEL *selh1 = NULL;
+SEL *selh2 = NULL;
+SEL *selv1 = NULL;
+SEL *selv2 = NULL;
+
+ 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);
+
+ if (hsize == 1 && vsize == 1)
+ return pixCopy(pixd, pixs);
+ if (hsize > 1) {
+ if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ return (PIX *)ERROR_PTR("horiz sels not made", procName, pixd);
+ }
+ }
+ if (vsize > 1) {
+ if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return (PIX *)ERROR_PTR("vert sels not made", procName, pixd);
+ }
+ }
+
+ pix1 = pixAddBorder(pixs, 32, 0);
+ if (vsize == 1) {
+ pix2 = pixDilate(NULL, pix1, selh1);
+ pix3 = pixDilate(NULL, pix2, selh2);
+ } else if (hsize == 1) {
+ pix2 = pixDilate(NULL, pix1, selv1);
+ pix3 = pixDilate(NULL, pix2, selv2);
+ } else {
+ pix2 = pixDilate(NULL, pix1, selh1);
+ pix3 = pixDilate(NULL, pix2, selh2);
+ pixDilate(pix2, pix3, selv1);
+ pixDilate(pix3, pix2, selv2);
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+
+ pix1 = pixRemoveBorder(pix3, 32);
+ pixDestroy(&pix3);
+ if (!pixd)
+ return pix1;
+ pixCopy(pixd, pix1);
+ pixDestroy(&pix1);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixErodeCompBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixErodeCompBrick(PIX *pixd,
+ PIX *pixs,
+ l_int32 hsize,
+ l_int32 vsize)
+{
+PIX *pixt;
+SEL *selh1 = NULL;
+SEL *selh2 = NULL;
+SEL *selv1 = NULL;
+SEL *selv2 = NULL;
+
+ 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) {
+ if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ return (PIX *)ERROR_PTR("horiz sels not made", procName, pixd);
+ }
+ }
+ if (vsize > 1) {
+ if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return (PIX *)ERROR_PTR("vert sels not made", procName, pixd);
+ }
+ }
+
+ 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);
+
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixOpenCompBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixOpenCompBrick(PIX *pixd,
+ PIX *pixs,
+ l_int32 hsize,
+ l_int32 vsize)
+{
+PIX *pixt;
+SEL *selh1 = NULL;
+SEL *selh2 = NULL;
+SEL *selv1 = NULL;
+SEL *selv2 = NULL;
+
+ 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) {
+ if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ return (PIX *)ERROR_PTR("horiz sels not made", procName, pixd);
+ }
+ }
+ if (vsize > 1) {
+ if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return (PIX *)ERROR_PTR("vert sels not made", procName, pixd);
+ }
+ }
+
+ 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);
+
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixCloseCompBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixCloseCompBrick(PIX *pixd,
+ PIX *pixs,
+ l_int32 hsize,
+ l_int32 vsize)
+{
+PIX *pixt;
+SEL *selh1 = NULL;
+SEL *selh2 = NULL;
+SEL *selv1 = NULL;
+SEL *selv2 = NULL;
+
+ 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) {
+ if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ return (PIX *)ERROR_PTR("horiz sels not made", procName, pixd);
+ }
+ }
+ if (vsize > 1) {
+ if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return (PIX *)ERROR_PTR("vert sels not made", procName, pixd);
+ }
+ }
+
+ 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);
+
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixCloseSafeCompBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixCloseSafeCompBrick(PIX *pixd,
+ PIX *pixs,
+ l_int32 hsize,
+ l_int32 vsize)
+{
+l_int32 maxtrans, bordsize;
+PIX *pixsb, *pixt, *pixdb;
+SEL *selh1 = NULL;
+SEL *selh2 = NULL;
+SEL *selv1 = NULL;
+SEL *selv2 = NULL;
+
+ 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);
+
+ if (hsize > 1) {
+ if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ return (PIX *)ERROR_PTR("horiz sels not made", procName, pixd);
+ }
+ }
+ if (vsize > 1) {
+ if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return (PIX *)ERROR_PTR("vert sels not made", procName, pixd);
+ }
+ }
+
+ maxtrans = L_MAX(hsize / 2, vsize / 2);
+ bordsize = 32 * ((maxtrans + 31) / 32); /* full 32 bit words */
+ pixsb = pixAddBorder(pixs, bordsize, 0);
+
+ 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);
+ }
+
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ * Functions associated with boundary conditions *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief resetMorphBoundaryCondition()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief getMorphBorderPixelColor()
+ *
+ * \param[in] type L_MORPH_DILATE, L_MORPH_ERODE
+ * \param[in] 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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief processMorphArgs1()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \param[out] ppixt copy or clone of %pixs
+ * \return pixd, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This is used for generic erosion, dilation and HMT.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief 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/leptonica/src/morph.h b/leptonica/src/morph.h
new file mode 100644
index 00000000..34f665fc
--- /dev/null
+++ b/leptonica/src/morph.h
@@ -0,0 +1,225 @@
+/*====================================================================*
+ - 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
+
+/*!
+ * \file morph.h
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+
+/*-------------------------------------------------------------------------*
+ * Sel and Sel array *
+ *-------------------------------------------------------------------------*/
+#define SEL_VERSION_NUMBER 1
+
+/*! Selection */
+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;
+
+/*! Array of 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
+
+/*! Kernel */
+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. *
+ *-------------------------------------------------------------------------*/
+/*! Morph Boundary */
+enum {
+ SYMMETRIC_MORPH_BC = 0,
+ ASYMMETRIC_MORPH_BC = 1
+};
+
+/*-------------------------------------------------------------------------*
+ * Structuring element vals *
+ *-------------------------------------------------------------------------*/
+/*! SEL Vals */
+enum {
+ SEL_DONT_CARE = 0,
+ SEL_HIT = 1,
+ SEL_MISS = 2
+};
+
+/*-------------------------------------------------------------------------*
+ * Runlength flags for granulometry *
+ *-------------------------------------------------------------------------*/
+/*! Runlength Polarity */
+enum {
+ L_RUN_OFF = 0,
+ L_RUN_ON = 1
+};
+
+/*-------------------------------------------------------------------------*
+ * Direction flags for grayscale morphology, granulometry, *
+ * composable Sels, convolution, etc. *
+ *-------------------------------------------------------------------------*/
+/*! Direction Flags */
+enum {
+ L_HORIZ = 1,
+ L_VERT = 2,
+ L_BOTH_DIRECTIONS = 3
+};
+
+/*-------------------------------------------------------------------------*
+ * Morphological operation flags *
+ *-------------------------------------------------------------------------*/
+/*! Morph Operator */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! Pixel Value Scaling */
+enum {
+ L_LINEAR_SCALE = 1,
+ L_LOG_SCALE = 2
+};
+
+/*-------------------------------------------------------------------------*
+ * Morphological tophat flags *
+ *-------------------------------------------------------------------------*/
+/*! Morph Tophat */
+enum {
+ L_TOPHAT_WHITE = 0,
+ L_TOPHAT_BLACK = 1
+};
+
+/*-------------------------------------------------------------------------*
+ * Arithmetic and logical operator flags *
+ * (use on grayscale images and Numas) *
+ *-------------------------------------------------------------------------*/
+/*! ArithLogical Ops */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! MinMax Selection */
+enum {
+ L_CHOOSE_MIN = 1, /* useful in a downscaling "erosion" */
+ L_CHOOSE_MAX = 2, /* useful in a downscaling "dilation" */
+ L_CHOOSE_MAXDIFF = 3, /* useful in a downscaling contrast */
+ L_CHOOSE_MIN_BOOST = 4, /* use a modification of the min value */
+ L_CHOOSE_MAX_BOOST = 5 /* use a modification of the max value */
+};
+
+/*-------------------------------------------------------------------------*
+ * Exterior value b.c. for distance function flags *
+ *-------------------------------------------------------------------------*/
+/*! Exterior Value */
+enum {
+ L_BOUNDARY_BG = 1, /* assume bg outside image */
+ L_BOUNDARY_FG = 2 /* assume fg outside image */
+};
+
+/*-------------------------------------------------------------------------*
+ * Image comparison flags *
+ *-------------------------------------------------------------------------*/
+/*! Image Comparison */
+enum {
+ L_COMPARE_XOR = 1,
+ L_COMPARE_SUBTRACT = 2,
+ L_COMPARE_ABS_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/leptonica/src/morphapp.c b/leptonica/src/morphapp.c
new file mode 100644
index 00000000..8ee41b0f
--- /dev/null
+++ b/leptonica/src/morphapp.c
@@ -0,0 +1,1636 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file morphapp.c
+ * <pre>
+ *
+ * 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 (and by scaling)
+ * PIXA *pixaExtendByMorph()
+ * PIXA *pixaExtendByScaling()
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#define SWAP(x, y) {temp = (x); (x) = (y); (y) = temp;}
+
+/*-----------------------------------------------------------------*
+ * Extraction of boundary pixels *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixExtractBoundary()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] type 0 for background pixels; 1 for foreground pixels
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Extracts the fg or bg boundary pixels for each component.
+ * Components are assumed to end at the boundary of pixs.
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixMorphSequenceMasked()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] pixm [optional] 1 bpp mask
+ * \param[in] sequence string specifying sequence of operations
+ * \param[in] dispsep horizontal separation in pixels between
+ * successive displays; use zero to suppress display
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixMorphSequenceByComponent()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] sequence string specifying sequence
+ * \param[in] connectivity 4 or 8
+ * \param[in] minw min width to consider; use 0 or 1 for any width
+ * \param[in] minh min height to consider; use 0 or 1 for any height
+ * \param[out] pboxa [optional] return boxa of c.c. in pixs
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaMorphSequenceByComponent()
+ *
+ * \param[in] pixas of 1 bpp pix
+ * \param[in] sequence string specifying sequence
+ * \param[in] minw min width to consider; use 0 or 1 for any width
+ * \param[in] minh min height to consider; use 0 or 1 for any height
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ pixaDestroy(&pixad);
+ return (PIXA *)ERROR_PTR("pix1 not found", procName, NULL);
+ }
+ if ((pix2 = pixMorphCompSequence(pix1, sequence, 0)) == NULL) {
+ pixaDestroy(&pixad);
+ 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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixMorphSequenceByRegion()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] pixm mask specifying regions
+ * \param[in] sequence string specifying sequence
+ * \param[in] connectivity 4 or 8, used on mask
+ * \param[in] minw min width to consider; use 0 or 1 for any width
+ * \param[in] minh min height to consider; use 0 or 1 for any height
+ * \param[out] pboxa [optional] return boxa of c.c. in pixm
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 (pboxa) *pboxa = NULL;
+ 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;
+}
+
+
+/*!
+ * \brief pixaMorphSequenceByRegion()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] pixam of 1 bpp mask elements
+ * \param[in] sequence string specifying sequence
+ * \param[in] minw min width to consider; use 0 or 1 for any width
+ * \param[in] minh min height to consider; use 0 or 1 for any height
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIXA *
+pixaMorphSequenceByRegion(PIX *pixs,
+ PIXA *pixam,
+ const char *sequence,
+ l_int32 minw,
+ l_int32 minh)
+{
+l_int32 n, i, w, h, same, maxd, 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);
+ pixaVerifyDepth(pixam, &same, &maxd);
+ if (maxd != 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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixUnionOfMorphOps()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] sela
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixIntersectionOfMorphOps()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] sela
+ * \param[in] 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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixSelectiveConnCompFill()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] connectivity 4 or 8
+ * \param[in] minw min width to consider; use 0 or 1 for any width
+ * \param[in] minh min 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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixRemoveMatchedPattern()
+ *
+ * \param[in] pixs input image, 1 bpp
+ * \param[in] pixp pattern to be removed from image, 1 bpp
+ * \param[in] pixe image after erosion by Sel that approximates pixp
+ * \param[in] x0, y0 center of Sel
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixDisplayMatchedPattern()
+ *
+ * \param[in] pixs input image, 1 bpp
+ * \param[in] pixp pattern to be removed from image, 1 bpp
+ * \param[in] pixe image after erosion by Sel that approximates pixp
+ * \param[in] x0, y0 center of Sel
+ * \param[in] color to paint the matched patterns; 0xrrggbb00
+ * \param[in] scale reduction factor for output pixd
+ * \param[in] nlevels if scale < 1.0, threshold to this number of levels
+ * \return pixd 8 bpp, colormapped, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 (and by scaling) *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixaExtendByMorph()
+ *
+ * \param[in] pixas
+ * \param[in] type L_MORPH_DILATE, L_MORPH_ERODE
+ * \param[in] niters
+ * \param[in] sel used for dilation, erosion; uses 2x2 if null
+ * \param[in] include 1 to include a copy of the input pixas in pixad;
+ * 0 to omit
+ * \return pixad with derived pix, using all iterations, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIXA *
+pixaExtendByMorph(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("pixaExtendByMorph");
+
+ 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;
+}
+
+
+/*!
+ * \brief pixaExtendByScaling()
+ *
+ * \param[in] pixas
+ * \param[in] nasc numa of scaling factors
+ * \param[in] type L_HORIZ, L_VERT, L_BOTH_DIRECTIONS
+ * \param[in] include 1 to include a copy of the input pixas in pixad;
+ * 0 to omit
+ * \return pixad with derived pix, using all scalings, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This scales every pix in %pixas by each factor in %nasc.
+ * and puts the results in %pixad.
+ * (2) 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.
+ * </pre>
+ */
+PIXA *
+pixaExtendByScaling(PIXA *pixas,
+ NUMA *nasc,
+ l_int32 type,
+ l_int32 include)
+{
+l_int32 i, j, n, nsc, w, h, scalew, scaleh;
+l_float32 scalefact;
+PIX *pix1, *pix2;
+PIXA *pixad;
+
+ PROCNAME("pixaExtendByScaling");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas undefined", procName, NULL);
+ if (!nasc || numaGetCount(nasc) == 0)
+ return (PIXA *)ERROR_PTR("nasc undefined or empty", procName, NULL);
+ if (type != L_HORIZ && type != L_VERT && type != L_BOTH_DIRECTIONS)
+ return (PIXA *)ERROR_PTR("invalid type", procName, NULL);
+
+ n = pixaGetCount(pixas);
+ nsc = numaGetCount(nasc);
+ if ((pixad = pixaCreate(n * (nsc + 1))) == NULL) {
+ L_ERROR("pixad not made: n = %d, nsc = %d\n", procName, n, nsc);
+ return NULL;
+ }
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ if (include) pixaAddPix(pixad, pix1, L_COPY);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ for (j = 0; j < nsc; j++) {
+ numaGetFValue(nasc, j, &scalefact);
+ scalew = w;
+ scaleh = h;
+ if (type == L_HORIZ || type == L_BOTH_DIRECTIONS)
+ scalew = w * scalefact;
+ if (type == L_VERT || type == L_BOTH_DIRECTIONS)
+ scaleh = h * scalefact;
+ pix2 = pixScaleToSize(pix1, scalew, scaleh);
+ pixaAddPix(pixad, pix2, L_INSERT);
+ }
+ pixDestroy(&pix1);
+ }
+ return pixad;
+}
+
+
+/*-----------------------------------------------------------------*
+ * Iterative morphological seed filling *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixSeedfillMorph()
+ *
+ * \param[in] pixs seed
+ * \param[in] pixm mask
+ * \param[in] maxiters use 0 to go to completion
+ * \param[in] connectivity 4 or 8
+ * \return pixd after filling into the mask or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ }
+ lept_stderr(" Num iters in binary reconstruction = %d\n", i);
+
+ pixDestroy(&pixt);
+ selDestroy(&sel_3);
+ return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ * Granulometry on binary images *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixRunHistogramMorph()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] runtype L_RUN_OFF, L_RUN_ON
+ * \param[in] direction L_HORIZ, L_VERT
+ * \param[in] 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) {
+ selDestroy(&sel_2a);
+ 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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixTophat()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize of Sel; must be odd; origin implicitly in center
+ * \param[in] vsize ditto
+ * \param[in] type L_TOPHAT_WHITE: image - opening
+ * L_TOPHAT_BLACK: closing - image
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixHDome()
+ *
+ * \param[in] pixs 8 bpp, filling mask
+ * \param[in] height of seed below the filling maskhdome; must be >= 0
+ * \param[in] connectivity 4 or 8
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFastTophat()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] xsize width of max/min op, smoothing; any integer >= 1
+ * \param[in] ysize height of max/min op, smoothing; any integer >= 1
+ * \param[in] type L_TOPHAT_WHITE: image - min
+ * L_TOPHAT_BLACK: max - image
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixMorphGradient()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] hsize sel width; must be odd; origin implicitly in center
+ * \param[in] vsize sel height
+ * \param[in] 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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixaCentroids()
+ *
+ * \param[in] pixa of components; 1 or 8 bpp
+ * \return pta of centroids relative to the UL corner of
+ * each pix, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCentroid()
+ *
+ * \param[in] pix 1 or 8 bpp
+ * \param[in] centtab [optional] table for finding centroids; can be null
+ * \param[in] sumtab [optional] table for finding pixel sums; can be null
+ * \param[out] pxave x coordinate of centroid, relative to the UL corner
+ * of the pix
+ * \param[out] pyave y coordinate of centroid, relative to the UL corner
+ * of the pix
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The sum and centroid tables are only used for 1 bpp.
+ * (2) Any table not passed in will be made internally and destroyed
+ * after use.
+ * </pre>
+ */
+l_ok
+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);
+
+ ctab = centtab;
+ stab = sumtab;
+ if (d == 1) {
+ pixSetPadBits(pix, 0);
+ if (!centtab)
+ ctab = makePixelCentroidTab8();
+ if (!sumtab)
+ stab = makePixelSumTab8();
+ }
+
+ 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/leptonica/src/morphdwa.c b/leptonica/src/morphdwa.c
new file mode 100644
index 00000000..b362599e
--- /dev/null
+++ b/leptonica/src/morphdwa.c
@@ -0,0 +1,1599 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file morphdwa.c
+ * <pre>
+ *
+ * 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.)
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#ifndef NO_CONSOLE_IO
+#define DEBUG_SEL_LOOKUP 0
+#endif /* ~NO_CONSOLE_IO */
+
+/*-----------------------------------------------------------------*
+ * Binary morphological (dwa) ops with brick Sels *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixDilateBrickDwa()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixErodeBrickDwa()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixOpenBrickDwa()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCloseBrickDwa()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixDilateCompBrickDwa()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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
+ lept_stderr("nameh1=%s, nameh2=%s, namev1=%s, namev2=%s\n",
+ selnameh1, selnameh2, selnamev1, selnamev2);
+ lept_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;
+}
+
+
+/*!
+ * \brief pixErodeCompBrickDwa()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixOpenCompBrickDwa()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCloseCompBrickDwa()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief pixDilateCompBrickExtendDwa()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 = pixCreateTemplate(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;
+}
+
+
+/*!
+ * \brief pixErodeCompBrickExtendDwa()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 = pixCreateTemplate(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;
+}
+
+
+/*!
+ * \brief pixOpenCompBrickExtendDwa()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * Notes:
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCloseCompBrickExtendDwa()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * Notes:
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief getExtendedCompositeParameters()
+ *
+ * \param[in] size of linear Sel
+ * \param[out] pn number of 63 wide convolutions
+ * \param[out] pextra size of extra Sel
+ * \param[out] pactualsize [optional] actual size used in operation
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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/leptonica/src/morphseq.c b/leptonica/src/morphseq.c
new file mode 100644
index 00000000..e98bb3b9
--- /dev/null
+++ b/leptonica/src/morphseq.c
@@ -0,0 +1,1243 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file morphseq.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*-------------------------------------------------------------------------*
+ * Run a sequence of binary rasterop morphological operations *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief pixMorphSequence()
+ *
+ * \param[in] pixs
+ * \param[in] sequence string specifying sequence
+ * \param[in] dispsep controls debug display results 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
+ *
+ * <pre>
+ * 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!
+ * </pre>
+ */
+PIX *
+pixMorphSequence(PIX *pixs,
+ const char *sequence,
+ l_int32 dispsep)
+{
+char *rawop, *op;
+char fname[256];
+l_int32 nops, i, j, nred, fact, w, h, x, border, pdfout;
+l_int32 level[4];
+PIX *pix1, *pix2;
+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);
+ }
+ border = 0;
+ pix1 = pixCopy(NULL, pixs);
+ pix2 = 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);
+ pix2 = pixDilateBrick(NULL, pix1, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'e':
+ case 'E':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pix2 = pixErodeBrick(NULL, pix1, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'o':
+ case 'O':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixOpenBrick(pix1, pix1, w, h);
+ break;
+ case 'c':
+ case 'C':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixCloseSafeBrick(pix1, pix1, 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;
+ pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1],
+ level[2], level[3]);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'x':
+ case 'X':
+ sscanf(&op[1], "%d", &fact);
+ pix2 = pixExpandReplicate(pix1, fact);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'b':
+ case 'B':
+ sscanf(&op[1], "%d", &border);
+ pix2 = pixAddBorder(pix1, border, 0);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ default:
+ /* All invalid ops are caught in the first pass */
+ break;
+ }
+ LEPT_FREE(op);
+
+ /* Debug output */
+ if (dispsep > 0) {
+ pixDisplay(pix1, x, 0);
+ x += dispsep;
+ }
+ if (pdfout)
+ pixaAddPix(pixa, pix1, L_COPY);
+ }
+ if (border > 0) {
+ pix2 = pixRemoveBorder(pix1, border);
+ pixSwapAndDestroy(&pix1, &pix2);
+ }
+
+ if (pdfout) {
+ snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
+ L_ABS(dispsep));
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&sa);
+ return pix1;
+}
+
+
+/*-------------------------------------------------------------------------*
+ * Run a sequence of binary composite rasterop morphological operations *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief pixMorphCompSequence()
+ *
+ * \param[in] pixs
+ * \param[in] sequence string specifying sequence
+ * \param[in] dispsep controls debug display of results 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
+ *
+ * <pre>
+ * 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}.
+ * </pre>
+ */
+PIX *
+pixMorphCompSequence(PIX *pixs,
+ const char *sequence,
+ l_int32 dispsep)
+{
+char *rawop, *op;
+char fname[256];
+l_int32 nops, i, j, nred, fact, w, h, x, border, pdfout;
+l_int32 level[4];
+PIX *pix1, *pix2;
+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);
+ }
+ border = 0;
+ pix1 = pixCopy(NULL, pixs);
+ pix2 = 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);
+ pix2 = pixDilateCompBrick(NULL, pix1, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'e':
+ case 'E':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pix2 = pixErodeCompBrick(NULL, pix1, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'o':
+ case 'O':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixOpenCompBrick(pix1, pix1, w, h);
+ break;
+ case 'c':
+ case 'C':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixCloseSafeCompBrick(pix1, pix1, 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;
+ pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1],
+ level[2], level[3]);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'x':
+ case 'X':
+ sscanf(&op[1], "%d", &fact);
+ pix2 = pixExpandReplicate(pix1, fact);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'b':
+ case 'B':
+ sscanf(&op[1], "%d", &border);
+ pix2 = pixAddBorder(pix1, border, 0);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ default:
+ /* All invalid ops are caught in the first pass */
+ break;
+ }
+ LEPT_FREE(op);
+
+ /* Debug output */
+ if (dispsep > 0) {
+ pixDisplay(pix1, x, 0);
+ x += dispsep;
+ }
+ if (pdfout)
+ pixaAddPix(pixa, pix1, L_COPY);
+ }
+ if (border > 0) {
+ pix2 = pixRemoveBorder(pix1, border);
+ pixSwapAndDestroy(&pix1, &pix2);
+ }
+
+ if (pdfout) {
+ snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
+ L_ABS(dispsep));
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&sa);
+ return pix1;
+}
+
+
+/*-------------------------------------------------------------------------*
+ * Run a sequence of binary dwa morphological operations *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief pixMorphSequenceDwa()
+ *
+ * \param[in] pixs
+ * \param[in] sequence string specifying sequence
+ * \param[in] dispsep controls debug display of results 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixMorphSequenceDwa(PIX *pixs,
+ const char *sequence,
+ l_int32 dispsep)
+{
+char *rawop, *op;
+char fname[256];
+l_int32 nops, i, j, nred, fact, w, h, x, border, pdfout;
+l_int32 level[4];
+PIX *pix1, *pix2;
+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);
+ }
+ border = 0;
+ pix1 = pixCopy(NULL, pixs);
+ pix2 = 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);
+ pix2 = pixDilateBrickDwa(NULL, pix1, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'e':
+ case 'E':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pix2 = pixErodeBrickDwa(NULL, pix1, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'o':
+ case 'O':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixOpenBrickDwa(pix1, pix1, w, h);
+ break;
+ case 'c':
+ case 'C':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixCloseBrickDwa(pix1, pix1, 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;
+ pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1],
+ level[2], level[3]);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'x':
+ case 'X':
+ sscanf(&op[1], "%d", &fact);
+ pix2 = pixExpandReplicate(pix1, fact);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'b':
+ case 'B':
+ sscanf(&op[1], "%d", &border);
+ pix2 = pixAddBorder(pix1, border, 0);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ default:
+ /* All invalid ops are caught in the first pass */
+ break;
+ }
+ LEPT_FREE(op);
+
+ /* Debug output */
+ if (dispsep > 0) {
+ pixDisplay(pix1, x, 0);
+ x += dispsep;
+ }
+ if (pdfout)
+ pixaAddPix(pixa, pix1, L_COPY);
+ }
+ if (border > 0) {
+ pix2 = pixRemoveBorder(pix1, border);
+ pixSwapAndDestroy(&pix1, &pix2);
+ }
+
+ if (pdfout) {
+ snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
+ L_ABS(dispsep));
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&sa);
+ return pix1;
+}
+
+
+/*-------------------------------------------------------------------------*
+ * Run a sequence of binary composite dwa morphological operations *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief pixMorphCompSequenceDwa()
+ *
+ * \param[in] pixs
+ * \param[in] sequence string specifying sequence
+ * \param[in] dispsep controls debug display of results 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixMorphCompSequenceDwa(PIX *pixs,
+ const char *sequence,
+ l_int32 dispsep)
+{
+char *rawop, *op;
+char fname[256];
+l_int32 nops, i, j, nred, fact, w, h, x, border, pdfout;
+l_int32 level[4];
+PIX *pix1, *pix2;
+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);
+ }
+ border = 0;
+ pix1 = pixCopy(NULL, pixs);
+ pix2 = 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);
+ pix2 = pixDilateCompBrickDwa(NULL, pix1, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'e':
+ case 'E':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pix2 = pixErodeCompBrickDwa(NULL, pix1, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'o':
+ case 'O':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixOpenCompBrickDwa(pix1, pix1, w, h);
+ break;
+ case 'c':
+ case 'C':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixCloseCompBrickDwa(pix1, pix1, 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;
+ pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1],
+ level[2], level[3]);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'x':
+ case 'X':
+ sscanf(&op[1], "%d", &fact);
+ pix2 = pixExpandReplicate(pix1, fact);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'b':
+ case 'B':
+ sscanf(&op[1], "%d", &border);
+ pix2 = pixAddBorder(pix1, border, 0);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ default:
+ /* All invalid ops are caught in the first pass */
+ break;
+ }
+ LEPT_FREE(op);
+
+ /* Debug output */
+ if (dispsep > 0) {
+ pixDisplay(pix1, x, 0);
+ x += dispsep;
+ }
+ if (pdfout)
+ pixaAddPix(pixa, pix1, L_COPY);
+ }
+ if (border > 0) {
+ pix2 = pixRemoveBorder(pix1, border);
+ pixSwapAndDestroy(&pix1, &pix2);
+ }
+
+ if (pdfout) {
+ snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
+ L_ABS(dispsep));
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&sa);
+ return pix1;
+}
+
+
+/*-------------------------------------------------------------------------*
+ * Parser verifier for binary morphological operations *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief morphSequenceVerify()
+ *
+ * \param[in] sa string array of operation sequence
+ * \return TRUE if valid; FALSE otherwise or on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This does verification of valid binary morphological
+ * operation sequences.
+ * (2) See pixMorphSequence() for notes on valid operations
+ * in the sequence.
+ * </pre>
+ */
+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) {
+ lept_stderr("*** op: %s invalid\n", op);
+ valid = FALSE;
+ break;
+ }
+ if (w <= 0 || h <= 0) {
+ lept_stderr("*** op: %s; w = %d, h = %d; must both be > 0\n",
+ op, w, h);
+ valid = FALSE;
+ break;
+ }
+/* lept_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) {
+ lept_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) {
+ lept_stderr("*** op = %s; level[%d] = %d is invalid\n",
+ op, j, level[j]);
+ valid = FALSE;
+ break;
+ }
+ }
+ if (!valid)
+ break;
+/* lept_stderr("op = %s", op); */
+ for (j = 0; j < nred; j++) {
+ level[j] = op[j + 1] - '0';
+/* lept_stderr(", level[%d] = %d", j, level[j]); */
+ }
+/* lept_stderr("\n"); */
+ break;
+ case 'x':
+ case 'X':
+ if (sscanf(&op[1], "%d", &fact) != 1) {
+ lept_stderr("*** op: %s; fact invalid\n", op);
+ valid = FALSE;
+ break;
+ }
+ if (fact != 2 && fact != 4 && fact != 8 && fact != 16) {
+ lept_stderr("*** op = %s; invalid fact = %d\n", op, fact);
+ valid = FALSE;
+ break;
+ }
+ netred -= intlogbase2[fact / 4];
+/* lept_stderr("op = %s; fact = %d\n", op, fact); */
+ break;
+ case 'b':
+ case 'B':
+ if (sscanf(&op[1], "%d", &fact) != 1) {
+ lept_stderr("*** op: %s; fact invalid\n", op);
+ valid = FALSE;
+ break;
+ }
+ if (i > 0) {
+ lept_stderr("*** op = %s; must be first op\n", op);
+ valid = FALSE;
+ break;
+ }
+ if (fact < 1) {
+ lept_stderr("*** op = %s; invalid fact = %d\n", op, fact);
+ valid = FALSE;
+ break;
+ }
+ border = fact;
+/* lept_stderr("op = %s; fact = %d\n", op, fact); */
+ break;
+ default:
+ lept_stderr("*** nonexistent op = %s\n", op);
+ valid = FALSE;
+ }
+ LEPT_FREE(op);
+ }
+
+ if (border != 0 && netred != 0) {
+ lept_stderr("*** op = %s; border added but net reduction not 0\n", op);
+ valid = FALSE;
+ }
+ return valid;
+}
+
+
+/*-----------------------------------------------------------------*
+ * Run a sequence of grayscale morphological operations *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixGrayMorphSequence()
+ *
+ * \param[in] pixs
+ * \param[in] sequence string specifying sequence
+ * \param[in] dispsep controls debug display of results in the sequence:
+ * 0: no output
+ * > 0: gives horizontal separation in pixels between
+ * successive displays
+ * < 0: pdf output; abs(dispsep) is used for naming
+ * \param[in] 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
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ */
+PIX *
+pixGrayMorphSequence(PIX *pixs,
+ const char *sequence,
+ l_int32 dispsep,
+ l_int32 dispy)
+{
+char *rawop, *op;
+char fname[256];
+l_int32 nops, i, valid, w, h, x, pdfout;
+PIX *pix1, *pix2;
+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) {
+ lept_stderr("*** op: %s invalid\n", op);
+ valid = FALSE;
+ break;
+ }
+ if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
+ lept_stderr("*** op: %s; w = %d, h = %d; must both be odd\n",
+ op, w, h);
+ valid = FALSE;
+ break;
+ }
+/* lept_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') {
+ lept_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 ) {
+ lept_stderr("*** op: %s; w = %d, h = %d; must both be odd\n",
+ op, w, h);
+ valid = FALSE;
+ break;
+ }
+/* lept_stderr("op = %s", op); */
+ break;
+ default:
+ lept_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);
+ }
+ pix1 = pixCopy(NULL, pixs);
+ pix2 = 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);
+ pix2 = pixDilateGray(pix1, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'e':
+ case 'E':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pix2 = pixErodeGray(pix1, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'o':
+ case 'O':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pix2 = pixOpenGray(pix1, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'c':
+ case 'C':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pix2 = pixCloseGray(pix1, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 't':
+ case 'T':
+ sscanf(&op[2], "%d.%d", &w, &h);
+ if (op[1] == 'w' || op[1] == 'W')
+ pix2 = pixTophat(pix1, w, h, L_TOPHAT_WHITE);
+ else /* 'b' or 'B' */
+ pix2 = pixTophat(pix1, w, h, L_TOPHAT_BLACK);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ default:
+ /* All invalid ops are caught in the first pass */
+ break;
+ }
+ LEPT_FREE(op);
+
+ /* Debug output */
+ if (dispsep > 0) {
+ pixDisplay(pix1, x, dispy);
+ x += dispsep;
+ }
+ if (pdfout)
+ pixaAddPix(pixa, pix1, L_COPY);
+ }
+
+ if (pdfout) {
+ snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
+ L_ABS(dispsep));
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&sa);
+ return pix1;
+}
+
+
+/*-----------------------------------------------------------------*
+ * Run a sequence of color morphological operations *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixColorMorphSequence()
+ *
+ * \param[in] pixs
+ * \param[in] sequence string specifying sequence
+ * \param[in] dispsep controls debug display of results in the sequence:
+ * 0: no output
+ * > 0: gives horizontal separation in pixels between
+ * successive displays
+ * < 0: pdf output; abs(dispsep) is used for naming
+ * \param[in] 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
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ */
+PIX *
+pixColorMorphSequence(PIX *pixs,
+ const char *sequence,
+ l_int32 dispsep,
+ l_int32 dispy)
+{
+char *rawop, *op;
+char fname[256];
+l_int32 nops, i, valid, w, h, x, pdfout;
+PIX *pix1, *pix2;
+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) {
+ lept_stderr("*** op: %s invalid\n", op);
+ valid = FALSE;
+ break;
+ }
+ if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
+ lept_stderr("*** op: %s; w = %d, h = %d; must both be odd\n",
+ op, w, h);
+ valid = FALSE;
+ break;
+ }
+/* lept_stderr("op = %s; w = %d, h = %d\n", op, w, h); */
+ break;
+ default:
+ lept_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);
+ }
+ pix1 = pixCopy(NULL, pixs);
+ pix2 = 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);
+ pix2 = pixColorMorph(pix1, L_MORPH_DILATE, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'e':
+ case 'E':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pix2 = pixColorMorph(pix1, L_MORPH_ERODE, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'o':
+ case 'O':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pix2 = pixColorMorph(pix1, L_MORPH_OPEN, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ case 'c':
+ case 'C':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pix2 = pixColorMorph(pix1, L_MORPH_CLOSE, w, h);
+ pixSwapAndDestroy(&pix1, &pix2);
+ break;
+ default:
+ /* All invalid ops are caught in the first pass */
+ break;
+ }
+ LEPT_FREE(op);
+
+ /* Debug output */
+ if (dispsep > 0) {
+ pixDisplay(pix1, x, dispy);
+ x += dispsep;
+ }
+ if (pdfout)
+ pixaAddPix(pixa, pix1, L_COPY);
+ }
+
+ if (pdfout) {
+ snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
+ L_ABS(dispsep));
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&sa);
+ return pix1;
+}
diff --git a/leptonica/src/morphtemplate1.txt b/leptonica/src/morphtemplate1.txt
new file mode 100644
index 00000000..5a769fd5
--- /dev/null
+++ b/leptonica/src/morphtemplate1.txt
@@ -0,0 +1,224 @@
+/*====================================================================*
+ - 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>", ...};
+
+/*!
+--- * \brief pixMorphDwa_*()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] operation L_MORPH_DILATE, L_MORPH_ERODE,
+ * L_MORPH_OPEN, L_MORPH_CLOSE
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+--- * \brief pixFMorphopGen_*()
+ *
+ * \param[in] pixd usual 3 choices: null, == pixs, != pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] operation L_MORPH_DILATE, L_MORPH_ERODE,
+ * L_MORPH_OPEN, L_MORPH_CLOSE
+ * \param[in] sel name
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/morphtemplate2.txt b/leptonica/src/morphtemplate2.txt
new file mode 100644
index 00000000..6b876774
--- /dev/null
+++ b/leptonica/src/morphtemplate2.txt
@@ -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;
+l_int32 j, pwpls;
+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/leptonica/src/numabasic.c b/leptonica/src/numabasic.c
new file mode 100644
index 00000000..33456181
--- /dev/null
+++ b/leptonica/src/numabasic.c
@@ -0,0 +1,2097 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file numabasic.c
+ * <pre>
+ *
+ * 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()
+ * NUMA *numaReadMem()
+ * l_int32 numaWriteDebug()
+ * l_int32 numaWrite()
+ * l_int32 numaWriteStream()
+ * l_int32 numaWriteStderr()
+ * l_int32 numaWriteMem()
+ *
+ * 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()
+ * NUMAA *numaaReadMem()
+ * l_int32 numaaWrite()
+ * l_int32 numaaWriteStream()
+ * l_int32 numaaWriteMem()
+ *
+ * (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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+ /* Bounds on initial array size */
+static const l_uint32 MaxFloatArraySize = 100000000; /* for numa */
+static const l_uint32 MaxPtrArraySize = 1000000; /* for numaa */
+static const l_int32 InitialArraySize = 50; /*!< n'importe quoi */
+
+ /* Static functions */
+static l_int32 numaExtendArray(NUMA *na);
+static l_int32 numaaExtendArray(NUMAA *naa);
+
+/*--------------------------------------------------------------------------*
+ * Numa creation, destruction, copy, clone, etc. *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief numaCreate()
+ *
+ * \param[in] n 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 > MaxFloatArraySize)
+ n = InitialArraySize;
+
+ na = (NUMA *)LEPT_CALLOC(1, sizeof(NUMA));
+ if ((na->array = (l_float32 *)LEPT_CALLOC(n, sizeof(l_float32))) == NULL) {
+ numaDestroy(&na);
+ 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;
+}
+
+
+/*!
+ * \brief numaCreateFromIArray()
+ *
+ * \param[in] iarray integer array
+ * \param[in] size of the array
+ * \return na, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaCreateFromFArray()
+ *
+ * \param[in] farray float array
+ * \param[in] size of the array
+ * \param[in] copyflag L_INSERT or L_COPY
+ * \return na, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaCreateFromString()
+ *
+ * \param[in] str string of comma-separated numbers
+ * \return na, or NULL on error
+ *
+ * <pre>
+ * 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(...).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaDestroy()
+ *
+ * \param[in,out] pna numa to be destroyed and nulled if it exists
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Decrements the ref count and, if 0, destroys the numa.
+ * (2) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaCopy()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaClone()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaEmpty()
+ *
+ * \param[in] na
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief numaAddNumber()
+ *
+ * \param[in] na
+ * \param[in] val float or int to be added; stored as a float
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ if (numaExtendArray(na))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ na->array[n] = val;
+ na->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief numaExtendArray()
+ *
+ * \param[in] na
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The max number of floats is 100M.
+ * </pre>
+ */
+static l_int32
+numaExtendArray(NUMA *na)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("numaExtendArray");
+
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+ if (na->nalloc > MaxFloatArraySize) /* belt & suspenders */
+ return ERROR_INT("na has too many ptrs", procName, 1);
+ oldsize = na->nalloc * sizeof(l_float32);
+ newsize = 2 * oldsize;
+ if (newsize > 4 * MaxFloatArraySize)
+ return ERROR_INT("newsize > 400 MB; too large", procName, 1);
+
+ if ((na->array = (l_float32 *)reallocNew((void **)&na->array,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+
+ na->nalloc *= 2;
+ return 0;
+}
+
+
+/*!
+ * \brief numaInsertNumber()
+ *
+ * \param[in] na
+ * \param[in] index location in na to insert new value
+ * \param[in] val float32 or integer to be added
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ *
+ * </pre>
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n);
+ return 1;
+ }
+
+ if (n >= na->nalloc) {
+ if (numaExtendArray(na))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ for (i = n; i > index; i--)
+ na->array[i] = na->array[i - 1];
+ na->array[index] = val;
+ na->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief numaRemoveNumber()
+ *
+ * \param[in] na
+ * \param[in] index element to be removed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n - 1);
+ return 1;
+ }
+
+ for (i = index + 1; i < n; i++)
+ na->array[i - 1] = na->array[i];
+ na->n--;
+ return 0;
+}
+
+
+/*!
+ * \brief numaReplaceNumber()
+ *
+ * \param[in] na
+ * \param[in] index element to be replaced
+ * \param[in] val new value to replace old one
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n - 1);
+ return 1;
+ }
+
+ na->array[index] = val;
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Numa accessors *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaSetCount()
+ *
+ * \param[in] na
+ * \param[in] newcount
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaGetFValue()
+ *
+ * \param[in] na
+ * \param[in] index into numa
+ * \param[out] pval float value; set to 0.0 on error
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Caller may need to check the function return value to
+ * decide if a 0.0 in the returned ival is valid.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaGetIValue()
+ *
+ * \param[in] na
+ * \param[in] index into numa
+ * \param[out] pival integer value; set to 0 on error
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Caller may need to check the function return value to
+ * decide if a 0 in the returned ival is valid.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaSetValue()
+ *
+ * \param[in] na
+ * \param[in] index to element to be set
+ * \param[in] val to set
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaShiftValue()
+ *
+ * \param[in] na
+ * \param[in] index to element to change relative to the current value
+ * \param[in] diff increment if diff > 0 or decrement if diff < 0
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaGetIArray()
+ *
+ * \param[in] na
+ * \return a copy of the bare internal array, integerized
+ * by rounding, or NULL on error
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaGetFArray()
+ *
+ * \param[in] na
+ * \param[in] copyflag L_NOCOPY or L_COPY
+ * \return either the bare internal array or a copy of it,
+ * or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaGetRefCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaChangeRefCount()
+ *
+ * \param[in] na
+ * \param[in] delta change to be applied
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+numaChangeRefcount(NUMA *na,
+ l_int32 delta)
+{
+ PROCNAME("numaChangeRefcount");
+
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+ na->refcount += delta;
+ return 0;
+}
+
+
+/*!
+ * \brief numaGetParameters()
+ *
+ * \param[in] na
+ * \param[out] pstartx [optional] startx
+ * \param[out] pdelx [optional] delx
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaSetParameters()
+ *
+ * \param[in] na
+ * \param[in] startx x value corresponding to na[0]
+ * \param[in] 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_ok
+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;
+}
+
+
+/*!
+ * \brief numaCopyParameters()
+ *
+ * \param[in] nad destination Numa
+ * \param[in] nas source Numa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaConvertToSarray()
+ *
+ * \param[in] na
+ * \param[in] size1 size of conversion field
+ * \param[in] size2 for float conversion: size of field to the right
+ * of the decimal point
+ * \param[in] addzeros for integer conversion: to add lead zeros
+ * \param[in] 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.
+ *
+ * <pre>
+ * Notes:
+ * (1) For integer conversion, size2 is ignored.
+ * For float conversion, addzeroes is ignored.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaRead()
+ *
+ * \param[in] 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);
+ na = numaReadStream(fp);
+ fclose(fp);
+ if (!na)
+ return (NUMA *)ERROR_PTR("na not read", procName, NULL);
+ return na;
+}
+
+
+/*!
+ * \brief numaReadStream()
+ *
+ * \param[in] fp file 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 (n > MaxFloatArraySize) {
+ L_ERROR("n = %d > %d\n", procName, n, MaxFloatArraySize);
+ return 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) {
+ numaDestroy(&na);
+ 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;
+}
+
+
+/*!
+ * \brief numaReadMem()
+ *
+ * \param[in] data numa serialization; in ascii
+ * \param[in] size of data; can use strlen to get it
+ * \return na, or NULL on error
+ */
+NUMA *
+numaReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+NUMA *na;
+
+ PROCNAME("numaReadMem");
+
+ if (!data)
+ return (NUMA *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (NUMA *)ERROR_PTR("stream not opened", procName, NULL);
+
+ na = numaReadStream(fp);
+ fclose(fp);
+ if (!na) L_ERROR("numa not read\n", procName);
+ return na;
+}
+
+
+/*!
+ * \brief numaWriteDebug()
+ *
+ * \param[in] filename
+ * \param[in] na
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Debug version, intended for use in the library when writing
+ * to files in a temp directory with names that are compiled in.
+ * This is used instead of numaWrite() for all such library calls.
+ * (2) The global variable LeptDebugOK defaults to 0, and can be set
+ * or cleared by the function setLeptDebugOK().
+ * </pre>
+ */
+l_ok
+numaWriteDebug(const char *filename,
+ NUMA *na)
+{
+ PROCNAME("numaWriteDebug");
+
+ if (LeptDebugOK) {
+ return numaWrite(filename, na);
+ } else {
+ L_INFO("write to named temp file %s is disabled\n", procName, filename);
+ return 0;
+ }
+}
+
+
+/*!
+ * \brief numaWrite()
+ *
+ * \param[in] filename
+ * \param[in] na
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+numaWrite(const char *filename,
+ NUMA *na)
+{
+l_int32 ret;
+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);
+ ret = numaWriteStream(fp, na);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("na not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief numaWriteStream()
+ *
+ * \param[in] fp file stream; use NULL to write to stderr
+ * \param[in] na
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+numaWriteStream(FILE *fp,
+ NUMA *na)
+{
+l_int32 i, n;
+l_float32 startx, delx;
+
+ PROCNAME("numaWriteStream");
+
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+ if (!fp)
+ return numaWriteStderr(na);
+
+ 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;
+}
+
+
+/*!
+ * \brief numaWriteStderr()
+ *
+ * \param[in] na
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+numaWriteStderr(NUMA *na)
+{
+l_int32 i, n;
+l_float32 startx, delx;
+
+ PROCNAME("numaWriteStderr");
+
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+
+ n = numaGetCount(na);
+ lept_stderr("\nNuma Version %d\n", NUMA_VERSION_NUMBER);
+ lept_stderr("Number of numbers = %d\n", n);
+ for (i = 0; i < n; i++)
+ lept_stderr(" [%d] = %f\n", i, na->array[i]);
+ lept_stderr("\n");
+
+ /* Optional data */
+ numaGetParameters(na, &startx, &delx);
+ if (startx != 0.0 || delx != 1.0)
+ lept_stderr("startx = %f, delx = %f\n", startx, delx);
+
+ return 0;
+}
+
+
+/*!
+ * \brief numaWriteMem()
+ *
+ * \param[out] pdata data of serialized numa; ascii
+ * \param[out] psize size of returned data
+ * \param[in] na
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a numa in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+numaWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ NUMA *na)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("numaWriteMem");
+
+ 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 (!na)
+ return ERROR_INT("na not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = numaWriteStream(fp, na);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = numaWriteStream(fp, na);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*--------------------------------------------------------------------------*
+ * Numaa creation, destruction *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief numaaCreate()
+ *
+ * \param[in] n 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 > MaxPtrArraySize)
+ n = InitialArraySize;
+
+ naa = (NUMAA *)LEPT_CALLOC(1, sizeof(NUMAA));
+ if ((naa->numa = (NUMA **)LEPT_CALLOC(n, sizeof(NUMA *))) == NULL) {
+ numaaDestroy(&naa);
+ return (NUMAA *)ERROR_PTR("numa ptr array not made", procName, NULL);
+ }
+
+ naa->nalloc = n;
+ naa->n = 0;
+ return naa;
+}
+
+
+/*!
+ * \brief numaaCreateFull()
+ *
+ * \param[in] nptr size of numa ptr array to be alloc'd
+ * \param[in] n size of individual numa arrays to be allocated
+ * to 0 for default
+ * \return naa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaaTruncate()
+ *
+ * \param[in] naa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaaDestroy()
+ *
+ * \param[in,out] pnaa to be destroyed and 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;
+}
+
+
+
+/*--------------------------------------------------------------------------*
+ * Add Numa to Numaa *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief numaaAddNuma()
+ *
+ * \param[in] naa
+ * \param[in] na to be added
+ * \param[in] copyflag L_INSERT, L_COPY, L_CLONE
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ if (numaaExtendArray(naa)) {
+ if (copyflag != L_INSERT)
+ numaDestroy(&nac);
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ }
+ naa->numa[n] = nac;
+ naa->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief numaaExtendArray()
+ *
+ * \param[in] naa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The max number of numa ptrs is 1M.
+ * </pre>
+ */
+static l_int32
+numaaExtendArray(NUMAA *naa)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("numaaExtendArray");
+
+ if (!naa)
+ return ERROR_INT("naa not defined", procName, 1);
+ if (naa->nalloc > MaxPtrArraySize) /* belt & suspenders */
+ return ERROR_INT("naa has too many ptrs", procName, 1);
+ oldsize = naa->nalloc * sizeof(NUMA *);
+ newsize = 2 * oldsize;
+ if (newsize > 8 * MaxPtrArraySize)
+ return ERROR_INT("newsize > 8 MB; too large", procName, 1);
+
+ if ((naa->numa = (NUMA **)reallocNew((void **)&naa->numa,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+
+ naa->nalloc *= 2;
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Numaa accessors *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaaGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaaGetNumaCount()
+ *
+ * \param[in] naa
+ * \param[in] 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]);
+}
+
+
+/*!
+ * \brief numaaGetNumberCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaaGetPtrArray()
+ *
+ * \param[in] naa
+ * \return the internal array of ptrs to Numa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+NUMA **
+numaaGetPtrArray(NUMAA *naa)
+{
+ PROCNAME("numaaGetPtrArray");
+
+ if (!naa)
+ return (NUMA **)ERROR_PTR("naa not defined", procName, NULL);
+
+ naa->n = naa->nalloc;
+ return naa->numa;
+}
+
+
+/*!
+ * \brief numaaGetNuma()
+ *
+ * \param[in] naa
+ * \param[in] index to the index-th numa
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief numaaReplaceNuma()
+ *
+ * \param[in] naa
+ * \param[in] index to the index-th numa
+ * \param[in] na insert and replace any existing one
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaaGetValue()
+ *
+ * \param[in] naa
+ * \param[in] i index of numa within numaa
+ * \param[in] j index into numa
+ * \param[out] pfval [optional] float value
+ * \param[out] pival [optional] int value
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaaAddNumber()
+ *
+ * \param[in] naa
+ * \param[in] index of numa within numaa
+ * \param[in] val float or int to be added; stored as a float
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Adds to an existing numa only.
+ * </pre>
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaaRead()
+ *
+ * \param[in] 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);
+ naa = numaaReadStream(fp);
+ fclose(fp);
+ if (!naa)
+ return (NUMAA *)ERROR_PTR("naa not read", procName, NULL);
+ return naa;
+}
+
+
+/*!
+ * \brief numaaReadStream()
+ *
+ * \param[in] fp file 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 (n > MaxPtrArraySize) {
+ L_ERROR("n = %d > %d\n", procName, n, MaxPtrArraySize);
+ return 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) {
+ numaaDestroy(&naa);
+ return (NUMAA *)ERROR_PTR("invalid numa header", procName, NULL);
+ }
+ if ((na = numaReadStream(fp)) == NULL) {
+ numaaDestroy(&naa);
+ return (NUMAA *)ERROR_PTR("na not made", procName, NULL);
+ }
+ numaaAddNuma(naa, na, L_INSERT);
+ }
+
+ return naa;
+}
+
+
+/*!
+ * \brief numaaReadMem()
+ *
+ * \param[in] data numaa serialization; in ascii
+ * \param[in] size of data; can use strlen to get it
+ * \return naa, or NULL on error
+ */
+NUMAA *
+numaaReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+NUMAA *naa;
+
+ PROCNAME("numaaReadMem");
+
+ if (!data)
+ return (NUMAA *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (NUMAA *)ERROR_PTR("stream not opened", procName, NULL);
+
+ naa = numaaReadStream(fp);
+ fclose(fp);
+ if (!naa) L_ERROR("naa not read\n", procName);
+ return naa;
+}
+
+
+/*!
+ * \brief numaaWrite()
+ *
+ * \param[in] filename
+ * \param[in] naa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+numaaWrite(const char *filename,
+ NUMAA *naa)
+{
+l_int32 ret;
+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);
+ ret = numaaWriteStream(fp, naa);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("naa not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief numaaWriteStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] naa
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaaWriteMem()
+ *
+ * \param[out] pdata data of serialized numaa; ascii
+ * \param[out] psize size of returned data
+ * \param[in] naa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a numaa in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+numaaWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ NUMAA *naa)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("numaaWriteMem");
+
+ 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 (!naa)
+ return ERROR_INT("naa not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = numaaWriteStream(fp, naa);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = numaaWriteStream(fp, naa);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
diff --git a/leptonica/src/numafunc1.c b/leptonica/src/numafunc1.c
new file mode 100644
index 00000000..acff5559
--- /dev/null
+++ b/leptonica/src/numafunc1.c
@@ -0,0 +1,3697 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file numafunc1.c
+ * <pre>
+ *
+ * --------------------------------------
+ * This file has these Numa utilities:
+ * - arithmetic operations
+ * - simple data analysis
+ * - generation of special sequences
+ * - permutations
+ * - interpolation
+ * - sorting
+ * - data analysis requiring sorting
+ * - joins and rearrangements
+ * --------------------------------------
+ *
+ * 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 numaCountNonzeroRuns()
+ * 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()
+ * l_int32 numaAddSorted()
+ * l_int32 numaFindSortedLoc()
+ *
+ * Random permutation
+ * NUMA *numaPseudorandomSequence()
+ * NUMA *numaRandomPermutation()
+ *
+ * Functions requiring sorting
+ * l_int32 numaGetRankValue()
+ * l_int32 numaGetMedian()
+ * l_int32 numaGetBinnedMedian()
+ * l_int32 numaGetMeanDevFromMedian()
+ * l_int32 numaGetMedianDevFromMedian()
+ * l_int32 numaGetMode()
+ *
+ * Rearrangements
+ * l_int32 numaJoin()
+ * l_int32 numaaJoin()
+ * NUMA *numaaFlattenToNuma()
+ *
+ * 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!
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+/*----------------------------------------------------------------------*
+ * Arithmetic and logical ops on Numas *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaArithOp()
+ *
+ * \param[in] nad [optional] can be null or equal to na1 (in-place
+ * \param[in] na1
+ * \param[in] na2
+ * \param[in] op L_ARITH_ADD, L_ARITH_SUBTRACT,
+ * L_ARITH_MULTIPLY, L_ARITH_DIVIDE
+ * \return nad always: operation applied to na1 and na2
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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:
+ lept_stderr(" Unknown arith op: %d\n", op);
+ return nad;
+ }
+ }
+
+ return nad;
+}
+
+
+/*!
+ * \brief numaLogicalOp()
+ *
+ * \param[in] nad [optional] can be null or equal to na1 (in-place
+ * \param[in] na1
+ * \param[in] na2
+ * \param[in] op L_UNION, L_INTERSECTION, L_SUBTRACTION, L_EXCLUSIVE_OR
+ * \return nad always: operation applied to na1 and na2
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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);
+ val1 = (val1 == 0) ? 0 : 1;
+ val2 = (val2 == 0) ? 0 : 1;
+ 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) ? 1 : 0;
+ numaSetValue(nad, i, val);
+ break;
+ default:
+ lept_stderr(" Unknown logical op: %d\n", op);
+ return nad;
+ }
+ }
+
+ return nad;
+}
+
+
+/*!
+ * \brief numaInvert()
+ *
+ * \param[in] nad [optional] can be null or equal to nas (in-place
+ * \param[in] nas
+ * \return nad always: 'inverts' nas
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaSimilar()
+ *
+ * \param[in] na1
+ * \param[in] na2
+ * \param[in] maxdiff use 0.0 for exact equality
+ * \param[out] psimilar 1 if similar; 0 if different
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Float values can differ slightly due to roundoff and
+ * accumulated errors. Using %maxdiff > 0.0 allows similar
+ * arrays to be identified.
+ * </pre>
+*/
+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;
+}
+
+
+/*!
+ * \brief numaAddToNumber()
+ *
+ * \param[in] na source numa
+ * \param[in] index element to be changed
+ * \param[in] val new value to be added
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+numaAddToNumber(NUMA *na,
+ l_int32 index,
+ l_float32 val)
+{
+l_int32 n;
+
+ PROCNAME("numaAddToNumber");
+
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+ if ((n = numaGetCount(na)) == 0)
+ return ERROR_INT("na is empty", procName, 1);
+ if (index < 0 || index >= n) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n - 1);
+ return 1;
+ }
+
+ na->array[index] += val;
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Simple extractions *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaGetMin()
+ *
+ * \param[in] na source numa
+ * \param[out] pminval [optional] min value
+ * \param[out] piminloc [optional] index of min location
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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);
+ if ((n = numaGetCount(na)) == 0)
+ return ERROR_INT("na is empty", procName, 1);
+
+ minval = +1000000000.;
+ iminloc = 0;
+ 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;
+}
+
+
+/*!
+ * \brief numaGetMax()
+ *
+ * \param[in] na source numa
+ * \param[out] pmaxval [optional] max value
+ * \param[out] pimaxloc [optional] index of max location
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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);
+ if ((n = numaGetCount(na)) == 0)
+ return ERROR_INT("na is empty", procName, 1);
+
+ maxval = -1000000000.;
+ imaxloc = 0;
+ 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;
+}
+
+
+/*!
+ * \brief numaGetSum()
+ *
+ * \param[in] na source numa
+ * \param[out] psum sum of values
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+numaGetSum(NUMA *na,
+ l_float32 *psum)
+{
+l_int32 i, n;
+l_float32 val, sum;
+
+ PROCNAME("numaGetSum");
+
+ if (!psum)
+ return ERROR_INT("&sum not defined", procName, 1);
+ *psum = 0;
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+
+ if ((n = numaGetCount(na)) == 0)
+ return ERROR_INT("na is empty", procName, 1);
+ sum = 0.0;
+ for (i = 0; i < n; i++) {
+ numaGetFValue(na, i, &val);
+ sum += val;
+ }
+ *psum = sum;
+ return 0;
+}
+
+
+/*!
+ * \brief numaGetPartialSums()
+ *
+ * \param[in] na source numa
+ * \return nasum, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ if ((n = numaGetCount(na)) == 0)
+ L_WARNING("na is empty\n", procName);
+ nasum = numaCreate(n);
+ sum = 0.0;
+ for (i = 0; i < n; i++) {
+ numaGetFValue(na, i, &val);
+ sum += val;
+ numaAddNumber(nasum, sum);
+ }
+ return nasum;
+}
+
+
+/*!
+ * \brief numaGetSumOnInterval()
+ *
+ * \param[in] na source numa
+ * \param[in] first beginning index
+ * \param[in] last final index; use -1 to go to the end
+ * \param[out] psum sum of values in the index interval range
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+numaGetSumOnInterval(NUMA *na,
+ l_int32 first,
+ l_int32 last,
+ l_float32 *psum)
+{
+l_int32 i, n;
+l_float32 val, sum;
+
+ PROCNAME("numaGetSumOnInterval");
+
+ if (!psum)
+ return ERROR_INT("&sum not defined", procName, 1);
+ *psum = 0.0;
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+
+ sum = 0.0;
+ if ((n = numaGetCount(na)) == 0)
+ return ERROR_INT("na is empty", procName, 1);
+ if (first < 0) first = 0;
+ if (first >= n || last < -1) /* not an error */
+ return 0;
+ if (last == -1)
+ last = n - 1;
+ last = L_MIN(last, n - 1);
+
+ for (i = first; i <= last; i++) {
+ numaGetFValue(na, i, &val);
+ sum += val;
+ }
+ *psum = sum;
+ return 0;
+}
+
+
+/*!
+ * \brief numaHasOnlyIntegers()
+ *
+ * \param[in] na source numa
+ * \param[out] pallints 1 if all sampled values are ints; else 0
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+numaHasOnlyIntegers(NUMA *na,
+ l_int32 *pallints)
+{
+l_int32 i, n;
+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 is empty", procName, 1);
+ for (i = 0; i < n; i ++) {
+ numaGetFValue(na, i, &val);
+ if (val != (l_int32)val) {
+ *pallints = FALSE;
+ return 0;
+ }
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief numaSubsample()
+ *
+ * \param[in] nas
+ * \param[in] 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);
+ if ((n = numaGetCount(nas)) == 0)
+ L_WARNING("nas is empty\n", procName);
+ for (i = 0; i < n; i++) {
+ if (i % subfactor != 0) continue;
+ numaGetFValue(nas, i, &val);
+ numaAddNumber(nad, val);
+ }
+
+ return nad;
+}
+
+
+/*!
+ * \brief numaMakeDelta()
+ *
+ * \param[in] 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;
+l_float32 prev, cur;
+NUMA *nad;
+
+ PROCNAME("numaMakeDelta");
+
+ if (!nas)
+ return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+ if ((n = numaGetCount(nas)) < 2) {
+ L_WARNING("n < 2; returning empty numa\n", procName);
+ return numaCreate(1);
+ }
+
+ nad = numaCreate(n - 1);
+ numaGetFValue(nas, 0, &prev);
+ for (i = 1; i < n; i++) {
+ numaGetFValue(nas, i, &cur);
+ numaAddNumber(nad, cur - prev);
+ prev = cur;
+ }
+ return nad;
+}
+
+
+/*!
+ * \brief numaMakeSequence()
+ *
+ * \param[in] startval
+ * \param[in] increment
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaMakeConstant()
+ *
+ * \param[in] val
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief numaMakeAbsValue()
+ *
+ * \param[in] nad can be null for new array, or the same as nas for inplace
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaAddBorder()
+ *
+ * \param[in] nas
+ * \param[in] left number of elements to add before the start
+ * \param[in] right number of elements to add after the end
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaAddSpecifiedBorder()
+ *
+ * \param[in] nas
+ * \param[in] left number of elements to add before the start
+ * \param[in] right number of elements to add after the end
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaRemoveBorder()
+ *
+ * \param[in] nas
+ * \param[in] left number of elements to remove from the start
+ * \param[in] right number of elements to remove up to the end
+ * \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;
+}
+
+
+/*!
+ * \brief numaCountNonzeroRuns()
+ *
+ * \param[in] na e.g., of pixel counts in rows or columns
+ * \param[out] pcount number of nonzero runs
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+numaCountNonzeroRuns(NUMA *na,
+ l_int32 *pcount)
+{
+l_int32 n, i, val, count, inrun;
+
+ PROCNAME("numaCountNonzeroRuns");
+
+ if (!pcount)
+ return ERROR_INT("&count not defined", procName, 1);
+ *pcount = 0;
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+ if ((n = numaGetCount(na)) == 0)
+ return ERROR_INT("na is empty", procName, 1);
+
+ count = 0;
+ inrun = FALSE;
+ for (i = 0; i < n; i++) {
+ numaGetIValue(na, i, &val);
+ if (!inrun && val > 0) {
+ count++;
+ inrun = TRUE;
+ } else if (inrun && val == 0) {
+ inrun = FALSE;
+ }
+ }
+ *pcount = count;
+ return 0;
+}
+
+
+/*!
+ * \brief numaGetNonzeroRange()
+ *
+ * \param[in] na source numa
+ * \param[in] eps largest value considered to be zero
+ * \param[out] pfirst, plast interval of array indices
+ * where values are nonzero
+ * \return 0 if OK, 1 on error or if no nonzero range is found.
+ */
+l_ok
+numaGetNonzeroRange(NUMA *na,
+ l_float32 eps,
+ l_int32 *pfirst,
+ l_int32 *plast)
+{
+l_int32 n, i, found;
+l_float32 val;
+
+ PROCNAME("numaGetNonzeroRange");
+
+ if (pfirst) *pfirst = 0;
+ if (plast) *plast = 0;
+ if (!pfirst || !plast)
+ return ERROR_INT("pfirst and plast not both defined", 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);
+
+ 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;
+}
+
+
+/*!
+ * \brief numaGetCountRelativeToZero()
+ *
+ * \param[in] na source numa
+ * \param[in] type L_LESS_THAN_ZERO, L_EQUAL_TO_ZERO, L_GREATER_THAN_ZERO
+ * \param[out] pcount count of values of given type
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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);
+ if ((n = numaGetCount(na)) == 0)
+ return ERROR_INT("na is empty", procName, 1);
+
+ 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;
+}
+
+
+/*!
+ * \brief numaClipToInterval()
+ *
+ * \param[in] nas
+ * \param[in] first >= 0; <= last
+ * \param[in] last
+ * \return numa with the same values as the input, but clipped
+ * to the specified interval
+ *
+ * <pre>
+ * Notes:
+ * 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.
+ * </pre>
+ */
+NUMA *
+numaClipToInterval(NUMA *nas,
+ l_int32 first,
+ l_int32 last)
+{
+l_int32 n, i;
+l_float32 val, startx, delx;
+NUMA *nad;
+
+ PROCNAME("numaClipToInterval");
+
+ if (!nas)
+ return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+ if ((n = numaGetCount(nas)) == 0)
+ return (NUMA *)ERROR_PTR("nas is empty", procName, NULL);
+ if (first < 0 || first > last)
+ return (NUMA *)ERROR_PTR("range not valid", procName, NULL);
+ if (first >= n)
+ return (NUMA *)ERROR_PTR("no elements in range", procName, NULL);
+
+ last = L_MIN(last, n - 1);
+ if ((nad = numaCreate(last - first + 1)) == NULL)
+ return (NUMA *)ERROR_PTR("nad not made", procName, NULL);
+ for (i = first; i <= last; i++) {
+ numaGetFValue(nas, i, &val);
+ numaAddNumber(nad, val);
+ }
+ numaGetParameters(nas, &startx, &delx);
+ numaSetParameters(nad, startx + first * delx, delx);
+ return nad;
+}
+
+
+/*!
+ * \brief numaMakeThresholdIndicator()
+ *
+ * \param[in] nas input numa
+ * \param[in] thresh threshold value
+ * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \return nad : indicator array: values are 0 and 1
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if ((n = numaGetCount(nas)) == 0)
+ return (NUMA *)ERROR_PTR("nas is empty", procName, NULL);
+
+ 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;
+}
+
+
+/*!
+ * \brief numaUniformSampling()
+ *
+ * \param[in] nas input numa
+ * \param[in] nsamp number of samples
+ * \return nad : resampled array, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This resamples the values in the array, using %nsamp
+ * equal divisions.
+ * </pre>
+ */
+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 ((n = numaGetCount(nas)) == 0)
+ return (NUMA *)ERROR_PTR("nas is empty", procName, NULL);
+ if (nsamp <= 0)
+ return (NUMA *)ERROR_PTR("nsamp must be > 0", procName, NULL);
+
+ 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;
+}
+
+
+/*!
+ * \brief numaReverse()
+ *
+ * \param[in] nad [optional] can be null or equal to nas
+ * \param[in] nas input numa
+ * \return nad : reversed, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Usage:
+ * numaReverse(nas, nas); // in-place
+ * nad = numaReverse(NULL, nas); // makes a new one
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaLowPassIntervals()
+ *
+ * \param[in] nas input numa
+ * \param[in] thresh threshold fraction of max; in [0.0 ... 1.0]
+ * \param[in] maxn for normalizing; set maxn = 0.0 to use the max in nas
+ * \return nad : interval abscissa pairs, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 ((n = numaGetCount(nas)) == 0)
+ return (NUMA *)ERROR_PTR("nas is empty", 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. */
+ 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;
+}
+
+
+/*!
+ * \brief numaThresholdEdges()
+ *
+ * \param[in] nas input numa
+ * \param[in] thresh1 low threshold as fraction of max; in [0.0 ... 1.0]
+ * \param[in] thresh2 high threshold as fraction of max; in [0.0 ... 1.0]
+ * \param[in] maxn for normalizing; set maxn = 0.0 to use the max in nas
+ * \return nad edge interval triplets, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 ((n = numaGetCount(nas)) == 0)
+ return (NUMA *)ERROR_PTR("nas is empty", 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. */
+ 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;
+}
+
+
+/*!
+ * \brief numaGetSpanValues()
+ *
+ * \param[in] na numa that is output of numaLowPassIntervals()
+ * \param[in] span span number, zero-based
+ * \param[out] pstart [optional] location of start of transition
+ * \param[out] pend [optional] location of end of transition
+ * \return 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);
+ if ((n = numaGetCount(na)) == 0)
+ return ERROR_INT("na is empty", procName, 1);
+ 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;
+}
+
+
+/*!
+ * \brief numaGetEdgeValues()
+ *
+ * \param[in] na numa that is output of numaThresholdEdges()
+ * \param[in] edge edge number, zero-based
+ * \param[out] pstart [optional] location of start of transition
+ * \param[out] pend [optional] location of end of transition
+ * \param[out] psign [optional] transition sign: +1 is rising,
+ * -1 is falling
+ * \return 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);
+ if ((n = numaGetCount(na)) == 0)
+ return ERROR_INT("na is empty", procName, 1);
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaInterpolateEqxVal()
+ *
+ * \param[in] startx xval corresponding to first element in array
+ * \param[in] deltax x increment between array elements
+ * \param[in] nay numa of ordinate values, assumed equally spaced
+ * \param[in] type L_LINEAR_INTERP, L_QUADRATIC_INTERP
+ * \param[in] xval
+ * \param[out] pyval interpolated value
+ * \return 0 if OK, 1 on error e.g., if xval is outside range
+ *
+ * <pre>
+ * 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))
+ *
+ * </pre>
+ */
+l_ok
+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);
+ if ((n = numaGetCount(nay)) < 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;
+}
+
+
+/*!
+ * \brief numaInterpolateArbxVal()
+ *
+ * \param[in] nax numa of abscissa values
+ * \param[in] nay numa of ordinate values, corresponding to nax
+ * \param[in] type L_LINEAR_INTERP, L_QUADRATIC_INTERP
+ * \param[in] xval
+ * \param[out] pyval interpolated value
+ * \return 0 if OK, 1 on error e.g., if xval is outside range
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+ }
+ im = 0;
+ dell = 0.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;
+}
+
+
+/*!
+ * \brief numaInterpolateEqxInterval()
+ *
+ * \param[in] startx xval corresponding to first element in nas
+ * \param[in] deltax x increment between array elements in nas
+ * \param[in] nasy numa of ordinate values, assumed equally spaced
+ * \param[in] type L_LINEAR_INTERP, L_QUADRATIC_INTERP
+ * \param[in] x0 start value of interval
+ * \param[in] x1 end value of interval
+ * \param[in] npts number of points to evaluate function in interval
+ * \param[out] pnax [optional] array of x values in interval
+ * \param[out] pnay array of y values in interval
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 ((n = numaGetCount(nasy)) < 2)
+ return ERROR_INT("n < 2", 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);
+ 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;
+}
+
+
+/*!
+ * \brief numaInterpolateArbxInterval()
+ *
+ * \param[in] nax numa of abscissa values
+ * \param[in] nay numa of ordinate values, corresponding to nax
+ * \param[in] type L_LINEAR_INTERP, L_QUADRATIC_INTERP
+ * \param[in] x0 start value of interval
+ * \param[in] x1 end value of interval
+ * \param[in] npts number of points to evaluate function in interval
+ * \param[out] pnadx [optional] array of x values in interval
+ * \param[out] pnady array of y values in interval
+ * \return 0 if OK, 1 on error e.g., if x0 or x1 is outside range
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) {
+ numaDestroy(&nasx);
+ numaDestroy(&nasy);
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaFitMax()
+ *
+ * \param[in] na numa of ordinate values, to fit a max to
+ * \param[out] pmaxval max value
+ * \param[in] naloc [optional] associated numa of abscissa values
+ * \param[out] pmaxloc 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
+ *
+ * <pre>
+ * Notes:
+ * 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
+ * </pre>
+ */
+l_ok
+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");
+
+ if (pmaxval) *pmaxval = 0.0;
+ if (pmaxloc) *pmaxloc = 0.0;
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+ if ((n = numaGetCount(na)) == 0)
+ return ERROR_INT("na is empty", procName, 1);
+ if (!pmaxval)
+ return ERROR_INT("&maxval not defined", procName, 1);
+ if (!pmaxloc)
+ return ERROR_INT("&maxloc not defined", procName, 1);
+ 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;
+}
+
+
+/*!
+ * \brief numaDifferentiateInterval()
+ *
+ * \param[in] nax numa of abscissa values
+ * \param[in] nay numa of ordinate values, corresponding to nax
+ * \param[in] x0 start value of interval
+ * \param[in] x1 end value of interval
+ * \param[in] npts number of points to evaluate function in interval
+ * \param[out] pnadx [optional] array of x values in interval
+ * \param[out] pnady array of derivatives in interval
+ * \return 0 if OK, 1 on error e.g., if x0 or x1 is outside range
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaIntegrateInterval()
+ *
+ * \param[in] nax numa of abscissa values
+ * \param[in] nay numa of ordinate values, corresponding to nax
+ * \param[in] x0 start value of interval
+ * \param[in] x1 end value of interval
+ * \param[in] npts number of points to evaluate function in interval
+ * \param[out] psum integral of function over interval
+ * \return 0 if OK, 1 on error e.g., if x0 or x1 is outside range
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaSortGeneral()
+ *
+ * \param[in] na source numa
+ * \param[out] pnasort [optional] sorted numa
+ * \param[out] pnaindex [optional] index of elements in na associated
+ * with each element of nasort
+ * \param[out] pnainvert [optional] index of elements in nasort associated
+ * with each element of na
+ * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING
+ * \param[in] sorttype L_SHELL_SORT or L_BIN_SORT
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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]]
+ * </pre>
+ */
+l_ok
+numaSortGeneral(NUMA *na,
+ NUMA **pnasort,
+ NUMA **pnaindex,
+ NUMA **pnainvert,
+ l_int32 sortorder,
+ l_int32 sorttype)
+{
+l_int32 isize;
+l_float32 size;
+NUMA *naindex = NULL;
+
+ PROCNAME("numaSortGeneral");
+
+ if (pnasort) *pnasort = NULL;
+ if (pnaindex) *pnaindex = NULL;
+ if (pnainvert) *pnainvert = NULL;
+ 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 (sorttype == L_BIN_SORT) {
+ numaGetMax(na, &size, NULL);
+ isize = (l_int32)size;
+ if (isize > MaxInitPtraSize - 1) {
+ L_WARNING("array too large; using shell sort\n", procName);
+ sorttype = L_SHELL_SORT;
+ } else {
+ naindex = numaGetBinSortIndex(na, sortorder);
+ }
+ }
+
+ if (sorttype == L_SHELL_SORT)
+ naindex = numaGetSortIndex(na, sortorder);
+
+ if (pnasort)
+ *pnasort = numaSortByIndex(na, naindex);
+ if (pnainvert)
+ *pnainvert = numaInvertMap(naindex);
+ if (pnaindex)
+ *pnaindex = naindex;
+ else
+ numaDestroy(&naindex);
+ return 0;
+}
+
+
+/*!
+ * \brief numaSortAutoSelect()
+ *
+ * \param[in] nas
+ * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING
+ * \return naout output sorted numa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This does either a shell sort or a bin sort, depending on
+ * the number of elements in nas and the dynamic range.
+ * </pre>
+ */
+NUMA *
+numaSortAutoSelect(NUMA *nas,
+ l_int32 sortorder)
+{
+l_int32 type;
+
+ PROCNAME("numaSortAutoSelect");
+
+ if (!nas)
+ return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+ if (numaGetCount(nas) == 0) {
+ L_WARNING("nas is empty; returning copy\n", procName);
+ return numaCopy(nas);
+ }
+ 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 && type != L_BIN_SORT)
+ return (NUMA *)ERROR_PTR("invalid sort type", procName, NULL);
+
+ if (type == L_BIN_SORT)
+ return numaBinSort(nas, sortorder);
+ else /* shell sort */
+ return numaSort(NULL, nas, sortorder);
+}
+
+
+/*!
+ * \brief numaSortIndexAutoSelect()
+ *
+ * \param[in] nas
+ * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING
+ * \return nad indices of nas, sorted by value in nas, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This does either a shell sort or a bin sort, depending on
+ * the number of elements in nas and the dynamic range.
+ * </pre>
+ */
+NUMA *
+numaSortIndexAutoSelect(NUMA *nas,
+ l_int32 sortorder)
+{
+l_int32 type;
+
+ PROCNAME("numaSortIndexAutoSelect");
+
+ if (!nas)
+ return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+ if (numaGetCount(nas) == 0) {
+ L_WARNING("nas is empty; returning copy\n", procName);
+ return numaCopy(nas);
+ }
+ 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 && type != L_BIN_SORT)
+ return (NUMA *)ERROR_PTR("invalid sort type", procName, NULL);
+
+ if (type == L_BIN_SORT)
+ return numaGetBinSortIndex(nas, sortorder);
+ else /* shell sort */
+ return numaGetSortIndex(nas, sortorder);
+}
+
+
+/*!
+ * \brief numaChooseSortType()
+ *
+ * \param[in] nas to be sorted
+ * \return sorttype L_SHELL_SORT or L_BIN_SORT, or UNDEF on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_int32
+numaChooseSortType(NUMA *nas)
+{
+l_int32 n;
+l_float32 minval, maxval;
+
+ PROCNAME("numaChooseSortType");
+
+ if (!nas)
+ return ERROR_INT("nas not defined", procName, UNDEF);
+
+ /* If small histogram or negative values; use shell sort */
+ numaGetMin(nas, &minval, NULL);
+ n = numaGetCount(nas);
+ if (minval < 0.0 || n < 200)
+ return L_SHELL_SORT;
+
+ /* If large maxval, use shell sort */
+ numaGetMax(nas, &maxval, NULL);
+ if (maxval > MaxInitPtraSize - 1)
+ return L_SHELL_SORT;
+
+ /* Otherwise, 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. */
+ if (n * log((l_float32)n) < 0.003 * maxval)
+ return L_SHELL_SORT;
+ else
+ return L_BIN_SORT;
+}
+
+
+/*!
+ * \brief numaSort()
+ *
+ * \param[in] naout output numa; can be NULL or equal to nain
+ * \param[in] nain input numa
+ * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING
+ * \return naout output sorted numa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if ((n = numaGetCount(naout)) == 0) {
+ L_WARNING("naout is empty\n", procName);
+ return naout;
+ }
+ 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;
+}
+
+
+/*!
+ * \brief numaBinSort()
+ *
+ * \param[in] nas of non-negative integers with a max that can
+ * not exceed (MaxInitPtraSize - 1)
+ * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING
+ * \return na sorted, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * (2) You can use numaSortAutoSelect() to decide which sorting
+ * method to use.
+ * </pre>
+ */
+NUMA *
+numaBinSort(NUMA *nas,
+ l_int32 sortorder)
+{
+NUMA *nat, *nad;
+
+ PROCNAME("numaBinSort");
+
+ if (!nas)
+ return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+ if (numaGetCount(nas) == 0) {
+ L_WARNING("nas is empty; returning copy\n", procName);
+ return numaCopy(nas);
+ }
+ if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+ return (NUMA *)ERROR_PTR("invalid sort order", procName, NULL);
+
+ if ((nat = numaGetBinSortIndex(nas, sortorder)) == NULL)
+ return (NUMA *)ERROR_PTR("bin sort failed", procName, NULL);
+ nad = numaSortByIndex(nas, nat);
+ numaDestroy(&nat);
+ return nad;
+}
+
+
+/*!
+ * \brief numaGetSortIndex()
+ *
+ * \param[in] na source numa
+ * \param[in] 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 (numaGetCount(na) == 0) {
+ L_WARNING("na is empty\n", procName);
+ return numaCreate(1);
+ }
+ 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) {
+ LEPT_FREE(array);
+ 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;
+}
+
+
+/*!
+ * \brief numaGetBinSortIndex()
+ *
+ * \param[in] nas of non-negative integers with a max that can
+ * not exceed (MaxInitPtraSize - 1)
+ * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING
+ * \return na sorted, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * (3) You can use numaSortIndexAutoSelect() to decide which
+ * sorting method to use.
+ * </pre>
+ */
+NUMA *
+numaGetBinSortIndex(NUMA *nas,
+ l_int32 sortorder)
+{
+l_int32 i, n, isize, ival, imax;
+l_float32 minsize, size;
+NUMA *na, *nai, *nad;
+L_PTRA *paindex;
+
+ PROCNAME("numaGetBinSortIndex");
+
+ if (!nas)
+ return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+ if (numaGetCount(nas) == 0) {
+ L_WARNING("nas is empty\n", procName);
+ return numaCreate(1);
+ }
+ if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+ return (NUMA *)ERROR_PTR("invalid sort order", procName, NULL);
+ numaGetMin(nas, &minsize, NULL);
+ if (minsize < 0)
+ return (NUMA *)ERROR_PTR("nas has negative numbers", procName, NULL);
+ numaGetMax(nas, &size, NULL);
+ isize = (l_int32)size;
+ if (isize > MaxInitPtraSize - 1) {
+ L_ERROR("array too large: %d elements > max size = %d\n",
+ procName, isize, MaxInitPtraSize - 1);
+ return 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. */
+ 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;
+}
+
+
+/*!
+ * \brief numaSortByIndex()
+ *
+ * \param[in] nas
+ * \param[in] 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, ni, 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);
+ ni = numaGetCount(naindex);
+ if (n != ni)
+ return (NUMA *)ERROR_PTR("numa sizes differ", procName, NULL);
+ if (n == 0) {
+ L_WARNING("nas is empty\n", procName);
+ return numaCopy(nas);
+ }
+
+ nad = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ numaGetIValue(naindex, i, &index);
+ numaGetFValue(nas, index, &val);
+ numaAddNumber(nad, val);
+ }
+
+ return nad;
+}
+
+
+/*!
+ * \brief numaIsSorted()
+ *
+ * \param[in] nas
+ * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING
+ * \param[out] psorted 1 if sorted; 0 if not
+ * \return 1 if OK; 0 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 ((n = numaGetCount(nas))== 0) {
+ L_WARNING("nas is empty\n", procName);
+ *psorted = TRUE;
+ return 0;
+ }
+ 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;
+}
+
+
+/*!
+ * \brief numaSortPair()
+ *
+ * \param[in] nax, nay input arrays
+ * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING
+ * \param[out] pnasx sorted
+ * \param[out] pnasy sorted exactly in order of nasx
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function sorts the two input arrays, nax and nay,
+ * together, using nax as the key for sorting.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaInvertMap()
+ *
+ * \param[in] nas
+ * \return nad the inverted map, or NULL on error or if not invertible
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if ((n = numaGetCount(nas)) == 0) {
+ L_WARNING("nas is empty\n", procName);
+ return numaCopy(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;
+}
+
+/*!
+ * \brief numaAddSorted()
+ *
+ * \param[in] na sorted input
+ * \param[in] val value to be inserted in sorted order
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The input %na is sorted. This function determines the
+ * sort order of %na and inserts %val into the array.
+ * </pre>
+ */
+l_ok
+numaAddSorted(NUMA *na,
+ l_float32 val)
+{
+l_int32 index;
+
+ PROCNAME("numaAddSorted");
+
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+
+ if (numaFindSortedLoc(na, val, &index) == 1)
+ return ERROR_INT("insert failure", procName, 1);
+ numaInsertNumber(na, index, val);
+ return 0;
+}
+
+
+/*!
+ * \brief numaFindSortedLoc()
+ *
+ * \param[in] na sorted input
+ * \param[in] val value to be inserted in sorted order
+ * \param[out] *ploc index location to insert @val
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The input %na is sorted. This determines the sort order of @na,
+ * either increasing or decreasing, and does a binary search for the
+ * location to insert %val into the array. The search is O(log n).
+ * (2) The index returned is the location to insert into the array.
+ * The value at the index, and all values to the right, are
+ * moved to the right (increasing their index location by 1).
+ * (3) If n is the size of %na, *ploc can be anything in [0 ... n].
+ * if *ploc == 0, the value is inserted at the beginning of the
+ * array; if *ploc == n, it is inserted at the end.
+ * (4) If the size of %na is 1, insert with an increasing sort.
+ * </pre>
+ */
+l_ok
+numaFindSortedLoc(NUMA *na,
+ l_float32 val,
+ l_int32 *pindex)
+{
+l_int32 n, increasing, lindex, rindex, midindex;
+l_float32 val0, valn, valmid;
+
+ PROCNAME("numaFindSortedLoc");
+
+ if (!pindex)
+ return ERROR_INT("&index not defined", procName, 1);
+ *pindex = 0;
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+
+ n = numaGetCount(na);
+ if (n == 0) return 0;
+ numaGetFValue(na, 0, &val0);
+ if (n == 1) { /* use increasing sort order */
+ if (val >= val0)
+ *pindex = 1;
+ return 0;
+ }
+
+ /* ----------------- n >= 2 ----------------- */
+ numaGetFValue(na, n - 1, &valn);
+ increasing = (valn >= val0) ? 1 : 0; /* sort order */
+
+ /* Check if outside bounds of existing array */
+ if (increasing) {
+ if (val < val0) {
+ *pindex = 0;
+ return 0;
+ } else if (val > valn) {
+ *pindex = n;
+ return 0;
+ }
+ } else { /* decreasing */
+ if (val > val0) {
+ *pindex = 0;
+ return 0;
+ } else if (val < valn) {
+ *pindex = n;
+ return 0;
+ }
+ }
+
+ /* Within bounds of existing array; search */
+ lindex = 0;
+ rindex = n - 1;
+ while (1) {
+ midindex = (lindex + rindex) / 2;
+ if (midindex == lindex || midindex == rindex) break;
+ numaGetFValue(na, midindex, &valmid);
+ if (increasing) {
+ if (val > valmid)
+ lindex = midindex;
+ else
+ rindex = midindex;
+ } else { /* decreasing */
+ if (val > valmid)
+ rindex = midindex;
+ else
+ lindex = midindex;
+ }
+ }
+ *pindex = rindex;
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Random permutation *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaPseudorandomSequence()
+ *
+ * \param[in] size of sequence
+ * \param[in] seed for random number generation
+ * \return na pseudorandom on [0,...,size - 1], or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaRandomPermutation()
+ *
+ * \param[in] nas input array
+ * \param[in] 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);
+ if ((size = numaGetCount(nas)) == 0) {
+ L_WARNING("nas is empty\n", procName);
+ return numaCopy(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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaGetRankValue()
+ *
+ * \param[in] na source numa
+ * \param[in] fract use 0.0 for smallest, 1.0 for largest
+ * \param[in] nasort [optional] increasing sorted version of na
+ * \param[in] usebins 0 for general sort; 1 for bin sort
+ * \param[out] pval rank val
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 ((n = numaGetCount(na)) == 0)
+ return ERROR_INT("na empty", procName, 1);
+ if (fract < 0.0 || fract > 1.0)
+ return ERROR_INT("fract not in [0.0 ... 1.0]", 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;
+}
+
+
+/*!
+ * \brief numaGetMedian()
+ *
+ * \param[in] na source numa
+ * \param[out] pval median value
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Computes the median value of the numbers in the numa, by
+ * sorting and finding the middle value in the sorted array.
+ * </pre>
+ */
+l_ok
+numaGetMedian(NUMA *na,
+ l_float32 *pval)
+{
+ PROCNAME("numaGetMedian");
+
+ if (!pval)
+ return ERROR_INT("&val not defined", procName, 1);
+ *pval = 0.0; /* init */
+ if (!na || numaGetCount(na) == 0)
+ return ERROR_INT("na not defined or empty", procName, 1);
+
+ return numaGetRankValue(na, 0.5, NULL, 0, pval);
+}
+
+
+/*!
+ * \brief numaGetBinnedMedian()
+ *
+ * \param[in] na source numa
+ * \param[out] pval integer median value
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+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 || numaGetCount(na) == 0)
+ return ERROR_INT("na not defined or empty", procName, 1);
+
+ ret = numaGetRankValue(na, 0.5, NULL, 1, &fval);
+ *pval = lept_roundftoi(fval);
+ return ret;
+}
+
+
+/*!
+ * \brief numaGetMeanDevFromMedian()
+ *
+ * \param[in] na source numa
+ * \param[in] med median value
+ * \param[out] pdev average absolute value deviation from median value
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+numaGetMeanDevFromMedian(NUMA *na,
+ l_float32 med,
+ l_float32 *pdev)
+{
+l_int32 i, n;
+l_float32 val, dev;
+
+ PROCNAME("numaGetMeanDevFromMedian");
+
+ if (!pdev)
+ return ERROR_INT("&dev not defined", procName, 1);
+ *pdev = 0.0; /* init */
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+ if ((n = numaGetCount(na)) == 0)
+ return ERROR_INT("na is empty", procName, 1);
+
+ dev = 0.0;
+ for (i = 0; i < n; i++) {
+ numaGetFValue(na, i, &val);
+ dev += L_ABS(val - med);
+ }
+ *pdev = dev / (l_float32)n;
+ return 0;
+}
+
+
+/*!
+ * \brief numaGetMedianDevFromMedian()
+ *
+ * \param[in] na source numa
+ * \param[out] pmed [optional] median value
+ * \param[out] pdev median deviation from median val
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Finds the median of the absolute value of the deviation 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 deviation,
+ * and this is not useful.
+ * </pre>
+ */
+l_ok
+numaGetMedianDevFromMedian(NUMA *na,
+ l_float32 *pmed,
+ l_float32 *pdev)
+{
+l_int32 n, i;
+l_float32 val, med;
+NUMA *nadev;
+
+ PROCNAME("numaGetMedianDevFromMedian");
+
+ if (pmed) *pmed = 0.0;
+ if (!pdev)
+ return ERROR_INT("&dev not defined", procName, 1);
+ *pdev = 0.0;
+ if (!na || numaGetCount(na) == 0)
+ return ERROR_INT("na not defined or empty", procName, 1);
+
+ numaGetMedian(na, &med);
+ if (pmed) *pmed = med;
+ n = numaGetCount(na);
+ nadev = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ numaGetFValue(na, i, &val);
+ numaAddNumber(nadev, L_ABS(val - med));
+ }
+ numaGetMedian(nadev, pdev);
+
+ numaDestroy(&nadev);
+ return 0;
+}
+
+
+/*!
+ * \brief numaGetMode()
+ *
+ * \param[in] na source numa
+ * \param[out] pval mode val
+ * \param[out] pcount [optional] mode count
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 ERROR_INT("na is empty", procName, 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;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Rearrangements *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaJoin()
+ *
+ * \param[in] nad dest numa; add to this one
+ * \param[in] nas [optional] source numa; add from this one
+ * \param[in] istart starting index in nas
+ * \param[in] iend ending index in nas; use -1 to cat all
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaaJoin()
+ *
+ * \param[in] naad dest naa; add to this one
+ * \param[in] naas [optional] source naa; add from this one
+ * \param[in] istart starting index in nas
+ * \param[in] iend ending index in naas; use -1 to cat all
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaaFlattenToNuma()
+ *
+ * \param[in] naa
+ * \return numa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
diff --git a/leptonica/src/numafunc2.c b/leptonica/src/numafunc2.c
new file mode 100644
index 00000000..6113c70c
--- /dev/null
+++ b/leptonica/src/numafunc2.c
@@ -0,0 +1,3319 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file numafunc2.c
+ * <pre>
+ *
+ * --------------------------------------
+ * This file has these Numa utilities:
+ * - morphological operations
+ * - arithmetic transforms
+ * - windowed statistical operations
+ * - histogram extraction
+ * - histogram comparison
+ * - extrema finding
+ * - frequency and crossing analysis
+ * --------------------------------------
+
+ * 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 numaDiscretizeSortedInBins()
+ * l_int32 numaDiscretizeHistoInBins()
+ * l_int32 numaGetRankBinValues()
+ * NUMA *numaGetUniformBinSizes()
+ *
+ * Splitting a distribution
+ * l_int32 numaSplitDistribution()
+ *
+ * Comparing histograms
+ * l_int32 grayHistogramsToEMD()
+ * l_int32 numaEarthMoverDistance()
+ * l_int32 grayInterHistogramStats()
+ *
+ * Extrema finding
+ * NUMA *numaFindPeaks()
+ * NUMA *numaFindExtrema()
+ * NUMA *numaFindLocForThreshold()
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaErode()
+ *
+ * \param[in] nas
+ * \param[in] size of sel; greater than 0, odd. The origin
+ * is implicitly in the center.
+ * \return nad eroded, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.0e37f;
+ for (i = hsize + n; i < len; i++)
+ fas[i] = 1.0e37f;
+ 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.0e37f; /* start big */
+ for (j = 0; j < size; j++)
+ minval = L_MIN(minval, fas[i + j]);
+ fad[i] = minval;
+ }
+
+ LEPT_FREE(fas);
+ return nad;
+}
+
+
+/*!
+ * \brief numaDilate()
+ *
+ * \param[in] nas
+ * \param[in] size of sel; greater than 0, odd. The origin
+ * is implicitly in the center.
+ * \return nad dilated, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The structuring element (sel) is linear, all "hits"
+ * (2) If size == 1, this returns a copy
+ * </pre>
+ */
+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.0e37f;
+ for (i = hsize + n; i < len; i++)
+ fas[i] = -1.0e37f;
+ 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.0e37f; /* start small */
+ for (j = 0; j < size; j++)
+ maxval = L_MAX(maxval, fas[i + j]);
+ fad[i] = maxval;
+ }
+
+ LEPT_FREE(fas);
+ return nad;
+}
+
+
+/*!
+ * \brief numaOpen()
+ *
+ * \param[in] nas
+ * \param[in] size of sel; greater than 0, odd. The origin
+ * is implicitly in the center.
+ * \return nad opened, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The structuring element (sel) is linear, all "hits"
+ * (2) If size == 1, this returns a copy
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaClose()
+ *
+ * \param[in] nas
+ * \param[in] size of sel; greater than 0, odd. The origin
+ * is implicitly in the center.
+ * \return nad closed, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaTransform()
+ *
+ * \param[in] nas
+ * \param[in] shift add this to each number
+ * \param[in] scale multiply each number by this
+ * \return nad with all values shifted and scaled, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Each number is shifted before scaling.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaSimpleStats()
+ *
+ * \param[in] na input numa
+ * \param[in] first first element to use
+ * \param[in] last last element to use; -1 to go to the end
+ * \param[out] pmean [optional] mean value
+ * \param[out] pvar [optional] variance
+ * \param[out] prvar [optional] rms deviation from the mean
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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);
+ first = L_MAX(0, first);
+ if (last < 0) last = n - 1;
+ if (first >= n)
+ return ERROR_INT("invalid first", procName, 1);
+ if (last >= n) {
+ L_WARNING("last = %d is beyond max index = %d; adjusting\n",
+ procName, last, n - 1);
+ last = n - 1;
+ }
+ if (first > last)
+ return ERROR_INT("first > last\n", procName, 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;
+}
+
+
+/*!
+ * \brief numaWindowedStats()
+ *
+ * \param[in] nas input numa
+ * \param[in] wc half width of the window
+ * \param[out] pnam [optional] mean value in window
+ * \param[out] pnams [optional] mean square value in window
+ * \param[out] pnav [optional] variance in window
+ * \param[out] pnarv [optional] rms deviation from the mean
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaWindowedMean()
+ *
+ * \param[in] nas
+ * \param[in] wc half width of the convolution window
+ * \return nad after low-pass filtering, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ numaDestroy(&na1);
+ numaDestroy(&nad);
+ 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;
+}
+
+
+/*!
+ * \brief numaWindowedMeanSquare()
+ *
+ * \param[in] nas
+ * \param[in] wc half width of the window
+ * \return nad containing windowed mean square values, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The window has width = 2 * %wc + 1.
+ * (2) We add a mirrored border of size %wc to each end of the array.
+ * </pre>
+ */
+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) {
+ numaDestroy(&na1);
+ numaDestroy(&nad);
+ 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;
+}
+
+
+/*!
+ * \brief numaWindowedVariance()
+ *
+ * \param[in] nam windowed mean values
+ * \param[in] nams windowed mean square values
+ * \param[out] pnav [optional] numa of variance -- the ms deviation
+ * from the mean
+ * \param[out] pnarv [optional] numa of rms deviation from the mean
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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]
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaWindowedMedian()
+ *
+ * \param[in] nas
+ * \param[in] halfwin half width of window over which the median is found
+ * \return nad after windowed median filtering, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaConvertToInt()
+ *
+ * \param[in] nas source numa
+ * \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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaMakeHistogram()
+ *
+ * \param[in] na
+ * \param[in] maxbins max number of histogram bins
+ * \param[out] pbinsize [optional] size of histogram bins
+ * \param[out] pbinstart [optional] start val of minimum bin;
+ * input NULL to force start at 0
+ * \return na consisiting of histogram of integerized values,
+ * or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (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 optionally returned and 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.
+ * </pre>
+ */
+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 (pbinsize) *pbinsize = 0;
+ if (pbinstart) *pbinstart = 0;
+ if (!na)
+ return (NUMA *)ERROR_PTR("na not defined", procName, NULL);
+ if (maxbins < 1)
+ return (NUMA *)ERROR_PTR("maxbins < 1", 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;
+ }
+ if (pbinsize) *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
+ lept_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) {
+ numaDestroy(&nai);
+ 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;
+}
+
+
+/*!
+ * \brief numaMakeHistogramAuto()
+ *
+ * \param[in] na numa of floats; these may be integers
+ * \param[in] maxbins max number of histogram bins; >= 1
+ * \return na consisiting of histogram of quantized float values,
+ * or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, &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;
+}
+
+
+/*!
+ * \brief numaMakeHistogramClipped()
+ *
+ * \param[in] na
+ * \param[in] binsize typically 1.0
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+
+/* lept_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;
+}
+
+
+/*!
+ * \brief numaRebinHistogram()
+ *
+ * \param[in] nas input histogram
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaNormalizeHistogram()
+ *
+ * \param[in] nas input histogram
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaGetStatsUsingHistogram()
+ *
+ * \param[in] na an arbitrary set of numbers; not ordered and not
+ * a histogram
+ * \param[in] 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
+ * \param[out] pmin [optional] min value of set
+ * \param[out] pmax [optional] max value of set
+ * \param[out] pmean [optional] mean value of set
+ * \param[out] pvariance [optional] variance
+ * \param[out] pmedian [optional] median value of set
+ * \param[in] rank in [0.0 ... 1.0]; median has a rank 0.5;
+ * ignored if &rval == NULL
+ * \param[out] prval [optional] value in na corresponding to %rank
+ * \param[out] phisto [optional] Numa histogram; use NULL to prevent
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaGetHistogramStats()
+ *
+ * \param[in] nahisto histogram: y(x(i)), i = 0 ... nbins - 1
+ * \param[in] startx x value of first bin: x(0)
+ * \param[in] deltax x increment between bins; the bin size; x(1) - x(0)
+ * \param[out] pxmean [optional] mean value of histogram
+ * \param[out] pxmedian [optional] median value of histogram
+ * \param[out] pxmode [optional] mode value of histogram:
+ * xmode = x(imode), where y(xmode) >= y(x(i)) for
+ * all i != imode
+ * \param[out] pxvariance [optional] variance of x
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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, -1,
+ pxmean, pxmedian, pxmode,
+ pxvariance);
+}
+
+
+/*!
+ * \brief numaGetHistogramStatsOnInterval()
+ *
+ * \param[in] nahisto histogram: y(x(i)), i = 0 ... nbins - 1
+ * \param[in] startx x value of first bin: x(0)
+ * \param[in] deltax x increment between bins; the bin size; x(1) - x(0)
+ * \param[in] ifirst first bin to use for collecting stats
+ * \param[in] ilast last bin for collecting stats; -1 to go to the end
+ * \param[out] pxmean [optional] mean value of histogram
+ * \param[out] pxmedian [optional] median value of histogram
+ * \param[out] pxmode [optional] mode value of histogram:
+ * xmode = x(imode), where y(xmode) >= y(x(i)) for
+ * all i != imode
+ * \param[out] pxvariance [optional] variance of x
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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);
+ ifirst = L_MAX(0, ifirst);
+ if (ilast < 0) ilast = n - 1;
+ if (ifirst >= n)
+ return ERROR_INT("invalid ifirst", procName, 1);
+ if (ilast >= n) {
+ L_WARNING("ilast = %d is beyond max index = %d; adjusting\n",
+ procName, ilast, n - 1);
+ ilast = n - 1;
+ }
+ if (ifirst > ilast)
+ return ERROR_INT("ifirst > ilast", 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) {
+ imax = -1;
+ 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;
+}
+
+
+/*!
+ * \brief numaMakeRankFromHistogram()
+ *
+ * \param[in] startx xval corresponding to first element in nay
+ * \param[in] deltax x increment between array elements in nay
+ * \param[in] nasy input histogram, assumed equally spaced
+ * \param[in] npts number of points to evaluate rank function
+ * \param[out] pnax [optional] array of x values in range
+ * \param[out] pnay rank array of specified npts
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaHistogramGetRankFromVal()
+ *
+ * \param[in] na histogram
+ * \param[in] rval value of input sample for which we want the rank
+ * \param[out] prank fraction of total samples below rval
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+
+/* lept_stderr("binval = %7.3f, rank = %7.3f\n", binval, *prank); */
+
+ return 0;
+}
+
+
+/*!
+ * \brief numaHistogramGetValFromRank()
+ *
+ * \param[in] na histogram
+ * \param[in] rank fraction of total samples
+ * \param[out] prval approx. to the bin value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+
+/* lept_stderr("rank = %7.3f, val = %7.3f\n", rank, *prval); */
+
+ return 0;
+}
+
+
+/*!
+ * \brief numaDiscretizeSortedInBins()
+ *
+ * \param[in] na sorted
+ * \param[in] nbins number of equal population bins (> 1)
+ * \param[out] pnabinval average "gray" values in each bin
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The input %na is sorted in increasing value.
+ * (2) The output array has the following mapping:
+ * bin number --> average array value in bin (nabinval)
+ * (3) With %nbins == 100, nabinval is the average gray value in
+ * each of the 100 equally populated bins. It is the function
+ * gray[100 * rank].
+ * Thus it is the inverse of
+ * rank[gray]
+ * (4) Contast with numaDiscretizeHistoInBins(), where the input %na
+ * is a histogram.
+ * </pre>
+ */
+l_ok
+numaDiscretizeSortedInBins(NUMA *na,
+ l_int32 nbins,
+ NUMA **pnabinval)
+{
+NUMA *nabinval; /* average gray value in the bins */
+NUMA *naeach;
+l_int32 i, ntot, bincount, binindex, binsize;
+l_float32 sum, val, ave;
+
+ PROCNAME("numaDiscretizeSortedInBins");
+
+ if (!pnabinval)
+ return ERROR_INT("&nabinval not defined", procName, 1);
+ *pnabinval = NULL;
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+ if (nbins < 2)
+ return ERROR_INT("nbins must be > 1", procName, 1);
+
+ /* Get the number of items in each bin */
+ ntot = numaGetCount(na);
+ if ((naeach = numaGetUniformBinSizes(ntot, nbins)) == NULL)
+ return ERROR_INT("naeach not made", procName, 1);
+
+ /* Get the average value in each bin */
+ sum = 0.0;
+ bincount = 0;
+ binindex = 0;
+ numaGetIValue(naeach, 0, &binsize);
+ nabinval = numaCreate(nbins);
+ for (i = 0; i < ntot; i++) {
+ numaGetFValue(na, i, &val);
+ bincount++;
+ sum += val;
+ if (bincount == binsize) { /* add bin entry */
+ ave = sum / binsize;
+ numaAddNumber(nabinval, ave);
+ sum = 0.0;
+ bincount = 0;
+ binindex++;
+ if (binindex == nbins) break;
+ numaGetIValue(naeach, binindex, &binsize);
+ }
+ }
+ *pnabinval = nabinval;
+
+ numaDestroy(&naeach);
+ return 0;
+}
+
+
+/*!
+ * \brief numaDiscretizeHistoInBins()
+ *
+ * \param[in] na histogram
+ * \param[in] nbins number of equal population bins (> 1)
+ * \param[out] pnabinval average "gray" values in each bin
+ * \param[out] pnarank [optional] rank value of input histogram;
+ * this is a cumulative norm histogram.
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) With %nbins == 100, nabinval is the average gray value in
+ * each of the 100 equally populated bins. It is the function
+ * gray[100 * rank].
+ * Thus it is the inverse of
+ * rank[gray]
+ * which is optionally returned in narank.
+ * (2) The "gray value" is the index into the input histogram.
+ * (3) The two output arrays give the following mappings, where the
+ * input is an un-normalized histogram of array values:
+ * bin number --> average array value in bin (nabinval)
+ * array values --> cumulative normalized histogram (narank)
+ * </pre>
+ */
+l_ok
+numaDiscretizeHistoInBins(NUMA *na,
+ l_int32 nbins,
+ NUMA **pnabinval,
+ NUMA **pnarank)
+{
+NUMA *nabinval; /* average gray value in the bins */
+NUMA *naeach, *nan;
+l_int32 i, j, k, nxvals, occup, count, bincount, binindex, binsize;
+l_float32 sum, ave, ntot;
+
+ PROCNAME("numaDiscretizeHistoInBins");
+
+ if (pnarank) *pnarank = NULL;
+ if (!pnabinval)
+ return ERROR_INT("&nabinval not defined", procName, 1);
+ *pnabinval = NULL;
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+ if (nbins < 2)
+ return ERROR_INT("nbins must be > 1", procName, 1);
+
+ nxvals = numaGetCount(na);
+ numaGetSum(na, &ntot);
+ occup = ntot / nxvals;
+ if (occup < 1) L_INFO("average occupancy %d < 1\n", procName, occup);
+
+ /* Get the number of items in each bin */
+ if ((naeach = numaGetUniformBinSizes(ntot, nbins)) == NULL)
+ return ERROR_INT("naeach not made", procName, 1);
+
+ /* Get the average value in each bin */
+ sum = 0.0;
+ bincount = 0;
+ binindex = 0;
+ numaGetIValue(naeach, 0, &binsize);
+ nabinval = numaCreate(nbins);
+ k = 0; /* count up to ntot */
+ for (i = 0; i < nxvals; i++) {
+ numaGetIValue(na, i, &count);
+ for (j = 0; j < count; j++) {
+ k++;
+ bincount++;
+ sum += i;
+ if (bincount == binsize) { /* add bin entry */
+ ave = sum / binsize;
+ numaAddNumber(nabinval, ave);
+ sum = 0.0;
+ bincount = 0;
+ binindex++;
+ if (binindex == nbins) break;
+ numaGetIValue(naeach, binindex, &binsize);
+ }
+ }
+ if (binindex == nbins) break;
+ }
+ *pnabinval = nabinval;
+ if (binindex != nbins)
+ L_ERROR("binindex = %d != nbins = %d\n", procName, binindex, nbins);
+
+ /* Get cumulative normalized histogram (rank[gray value]).
+ * This is the partial sum operating on the normalized histogram. */
+ if (pnarank) {
+ nan = numaNormalizeHistogram(na, 1.0);
+ *pnarank = numaGetPartialSums(nan);
+ numaDestroy(&nan);
+ }
+ numaDestroy(&naeach);
+ return 0;
+}
+
+
+/*!
+ * \brief numaGetRankBinValues()
+ *
+ * \param[in] na an array of values
+ * \param[in] nbins number of bins at which the rank is divided
+ * \param[out] pnam mean intensity in a bin vs rank bin value,
+ * with %nbins of discretized rank values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Simple interface for getting a binned rank representation
+ * of an input array of values. This returns:
+ * rank bin number --> average array value in each rank bin (nam)
+ * (2) Uses bins either a sorted array or a histogram, depending on
+ * the values in the array and the size of the array.
+ * </pre>
+ */
+l_ok
+numaGetRankBinValues(NUMA *na,
+ l_int32 nbins,
+ NUMA **pnam)
+{
+NUMA *na1;
+l_int32 maxbins, type;
+l_float32 maxval, delx;
+
+ PROCNAME("numaGetRankBinValues");
+
+ if (!pnam)
+ return ERROR_INT("&pnam not defined", procName, 1);
+ *pnam = NULL;
+ 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);
+
+ /* Choose between sorted array and a histogram.
+ * If the input array is has a small number of numbers with
+ * a large maximum, we will sort it. At the other extreme, if
+ * the array has many numbers with a small maximum, such as the
+ * values of pixels in an 8 bpp grayscale image, generate a histogram.
+ * If type comes back as L_BIN_SORT, use a histogram. */
+ type = numaChooseSortType(na);
+ if (type == L_SHELL_SORT) { /* sort the array */
+ L_INFO("sort the array: input size = %d\n", procName, numaGetCount(na));
+ na1 = numaSort(NULL, na, L_SORT_INCREASING);
+ numaDiscretizeSortedInBins(na1, nbins, pnam);
+ numaDestroy(&na1);
+ return 0;
+ }
+
+ /* Make the histogram. Assuming there are no negative values
+ * in the array, if the max value in the array does not exceed
+ * about 100000, the bin size for generating the histogram will
+ * be 1; maxbins refers to the number of entries in the histogram. */
+ L_INFO("use a histogram: input size = %d\n", procName, numaGetCount(na));
+ numaGetMax(na, &maxval, NULL);
+ maxbins = L_MIN(100002, (l_int32)maxval + 2);
+ na1 = numaMakeHistogram(na, maxbins, NULL, NULL);
+
+ /* Warn if there is a scale change. This shouldn't happen
+ * unless the max value is above 100000. */
+ numaGetParameters(na1, NULL, &delx);
+ if (delx > 1.0)
+ L_WARNING("scale change: delx = %6.2f\n", procName, delx);
+
+ /* Rank bin the results */
+ numaDiscretizeHistoInBins(na1, nbins, pnam, NULL);
+ numaDestroy(&na1);
+ return 0;
+}
+
+
+/*!
+ * \brief numaGetUniformBinSizes()
+ *
+ * \param[in] ntotal number of values to be split up
+ * \param[in] nbins number of bins
+ * \return naeach number of values to go in each bin, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The numbers in the bins can differ by 1. The sum of
+ * bin numbers in @naeach is @ntotal.
+ * </pre>
+ */
+NUMA *
+numaGetUniformBinSizes(l_int32 ntotal,
+ l_int32 nbins)
+{
+l_int32 i, start, end;
+NUMA *naeach;
+
+ PROCNAME("numaGetUniformBinSizes");
+
+ if (ntotal <= 0)
+ return (NUMA *)ERROR_PTR("ntotal <= 0", procName, NULL);
+ if (nbins <= 0)
+ return (NUMA *)ERROR_PTR("nbins <= 0", procName, NULL);
+
+ if ((naeach = numaCreate(nbins)) == NULL)
+ return (NUMA *)ERROR_PTR("naeach not made", procName, NULL);
+ start = 0;
+ for (i = 0; i < nbins; i++) {
+ end = ntotal * (i + 1) / nbins;
+ numaAddNumber(naeach, end - start);
+ start = end;
+ }
+ return naeach;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Splitting a distribution *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaSplitDistribution()
+ *
+ * \param[in] na histogram
+ * \param[in] scorefract fraction of the max score, used to determine
+ * range over which the histogram min is searched
+ * \param[out] psplitindex [optional] index for splitting
+ * \param[out] pave1 [optional] average of lower distribution
+ * \param[out] pave2 [optional] average of upper distribution
+ * \param[out] pnum1 [optional] population of lower distribution
+ * \param[out] pnum2 [optional] population of upper distribution
+ * \param[out] pnascore [optional] for debugging; otherwise use NULL
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 / ((l_float32)(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. */
+ if ((nascore = numaCreate(n)) == NULL)
+ return ERROR_INT("nascore not made", procName, 1);
+ naave1 = (pave1) ? numaCreate(n) : NULL;
+ naave2 = (pave2) ? numaCreate(n) : NULL;
+ nanum1 = (pnum1) ? numaCreate(n) : NULL;
+ nanum2 = (pnum2) ? numaCreate(n) : NULL;
+ 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 (pnum2) 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 */
+ lept_stderr("minrange = %d, maxrange = %d\n", minrange, maxrange);
+ lept_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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief grayHistogramsToEMD()
+ *
+ * \param[in] naa1, naa2 two numaa, each with one or more 256-element
+ * histograms
+ * \param[out] pnad nad of EM distances for each histogram
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief numaEarthMoverDistance()
+ *
+ * \param[in] na1, na2 two numas of the same size, typically histograms
+ * \param[out] pdist earthmover distance
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief grayInterHistogramStats()
+ *
+ * \param[in] naa numaa with two or more 256-element histograms
+ * \param[in] wc half-width of the smoothing window
+ * \param[out] pnam [optional] mean values
+ * \param[out] pnams [optional] mean square values
+ * \param[out] pnav [optional] variances
+ * \param[out] pnarv [optional] rms deviations from the mean
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, -1, &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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaFindPeaks()
+ *
+ * \param[in] nas source numa
+ * \param[in] nmax max number of peaks to be found
+ * \param[in] fract1 min fraction of peak value
+ * \param[in] fract2 min slope
+ * \return peak na, or NULL on error.
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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) {
+ numaDestroy(&na);
+ 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;
+}
+
+
+/*!
+ * \brief numaFindExtrema()
+ *
+ * \param[in] nas input values
+ * \param[in] delta relative amount to resolve peaks and valleys
+ * \param[out] pnav [optional] values of extrema
+ * \return nad (locations of extrema, or NULL on error
+ *
+ * <pre>
+ * 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 trying to identify a peak.
+ * We have a previous valley, and also the largest value that
+ * we have seen since that valley. We can identify this as
+ * a peak if we find a value that is delta BELOW it. When
+ * we find such a value, label the peak, use the current
+ * value to label the starting point for the search for
+ * a valley, and do the same operation in reverse. Namely,
+ * keep track of the lowest point seen, and look for a value
+ * that is delta ABOVE it. Once found, the lowest point is
+ * labeled the valley, and continue, looking for the next peak.
+ * </pre>
+ */
+NUMA *
+numaFindExtrema(NUMA *nas,
+ l_float32 delta,
+ NUMA **pnav)
+{
+l_int32 i, n, found, loc, direction;
+l_float32 startval, val, maxval, minval;
+NUMA *nav, *nad;
+
+ PROCNAME("numaFindExtrema");
+
+ if (pnav) *pnav = NULL;
+ if (!nas)
+ return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+ if (delta < 0.0)
+ return (NUMA *)ERROR_PTR("delta < 0", procName, NULL);
+
+ n = numaGetCount(nas);
+ nad = numaCreate(0);
+ nav = NULL;
+ if (pnav) {
+ nav = numaCreate(0);
+ *pnav = nav;
+ }
+
+ /* 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 */
+ if (nav) numaAddNumber(nav, maxval);
+ 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 */
+ if (nav) numaAddNumber(nav, minval);
+ direction = 1; /* reverse: start looking for a max */
+ maxval = val;
+ loc = i; /* current max location */
+ }
+ }
+
+ /* Save the final extremum */
+/* numaAddNumber(nad, loc); */
+ return nad;
+}
+
+
+/*!
+ * \brief numaFindLocForThreshold()
+ *
+ * \param[in] nas input histogram
+ * \param[in] skip distance to skip to check for false min; 0 for default
+ * \param[out] pthresh threshold value
+ * \param[out] pfract [optional] fraction below or at threshold
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds a good place to set a threshold for a histogram
+ * of values that has two peaks. The peaks can differ greatly
+ * in area underneath them. The number of buckets in the
+ * histogram is expected to be 256 (e.g, from an 8 bpp gray image).
+ * (2) The input histogram should have been smoothed with a window
+ * to avoid false peak and valley detection due to noise. For
+ * example, see pixThresholdByHisto().
+ * (3) A skip value can be input to determine the look-ahead distance
+ * to ignore a false peak on the descent from the first peak.
+ * Input 0 to use the default value (it assumes a histo size of 256).
+ * (4) Optionally, the fractional area under the first peak can
+ * be returned.
+ * </pre>
+ */
+l_ok
+numaFindLocForThreshold(NUMA *na,
+ l_int32 skip,
+ l_int32 *pthresh,
+ l_float32 *pfract)
+{
+l_int32 i, n, start, index, minloc;
+l_float32 val, pval, jval, minval, sum, partsum;
+l_float32 *fa;
+
+ PROCNAME("numaFindLocForThreshold");
+
+ if (pfract) *pfract = 0.0;
+ if (!pthresh)
+ return ERROR_INT("&thresh not defined", procName, 1);
+ *pthresh = 0;
+ if (!na)
+ return ERROR_INT("na not defined", procName, 1);
+ if (skip <= 0) skip = 20;
+
+ /* Look for the top of the first peak */
+ n = numaGetCount(na);
+ fa = numaGetFArray(na, L_NOCOPY);
+ pval = fa[0];
+ for (i = 1; i < n; i++) {
+ val = fa[i];
+ index = L_MIN(i + skip, n - 1);
+ jval = fa[index];
+ if (val < pval && jval < pval) /* near the top if not there */
+ break;
+ pval = val;
+ }
+
+ /* Look for the low point in the valley */
+ start = i;
+ pval = fa[start];
+ for (i = start + 1; i < n; i++) {
+ val = fa[i];
+ if (val <= pval) { /* going down */
+ pval = val;
+ } else { /* going up */
+ index = L_MIN(i + skip, n - 1);
+ jval = fa[index]; /* junp ahead 20 */
+ if (val > jval) { /* still going down; jump ahead */
+ pval = jval;
+ i = index;
+ } else { /* really going up; passed the min */
+ break;
+ }
+ }
+ }
+
+ /* Find the location of the minimum in the interval */
+ minloc = index; /* likely passed the min; look backward */
+ minval = fa[index];
+ for (i = index - 1; i > index - skip; i--) {
+ if (fa[i] < minval) {
+ minval = fa[i];
+ minloc = i;
+ }
+ }
+ *pthresh = minloc;
+
+ /* Find the fraction under the first peak */
+ if (pfract) {
+ numaGetSumOnInterval(na, 0, minloc, &partsum);
+ numaGetSum(na, &sum);
+ if (sum > 0.0)
+ *pfract = partsum / sum;
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief numaCountReversals()
+ *
+ * \param[in] nas input values
+ * \param[in] minreversal relative amount to resolve peaks and valleys
+ * \param[out] pnr [optional] number of reversals
+ * \param[out] prd [optional] reversal density: reversals/length
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * (2) If the input numa was generated from a 1 bpp pix, the
+ * values will be 0 and 1. Use %minreversal == 1 to get
+ * the number of pixel flips. If the only values are 0 and 1,
+ * but %minreversal > 1, set the reversal count to 0 and
+ * issue a warning.
+ * </pre>
+ */
+l_ok
+numaCountReversals(NUMA *nas,
+ l_float32 minreversal,
+ l_int32 *pnr,
+ l_float32 *prd)
+{
+l_int32 i, n, nr, ival, binvals;
+l_int32 *ia;
+l_float32 fval, delx, len;
+NUMA *nat;
+
+ PROCNAME("numaCountReversals");
+
+ if (pnr) *pnr = 0;
+ if (prd) *prd = 0.0;
+ if (!pnr && !prd)
+ return ERROR_INT("neither &nr nor &rd are defined", procName, 1);
+ if (!nas)
+ return ERROR_INT("nas not defined", procName, 1);
+ if ((n = numaGetCount(nas)) == 0) {
+ L_INFO("nas is empty\n", procName);
+ return 0;
+ }
+ if (minreversal < 0.0)
+ return ERROR_INT("minreversal < 0", procName, 1);
+
+ /* Decide if the only values are 0 and 1 */
+ binvals = TRUE;
+ for (i = 0; i < n; i++) {
+ numaGetFValue(nas, i, &fval);
+ if (fval != 0.0 && fval != 1.0) {
+ binvals = FALSE;
+ break;
+ }
+ }
+
+ nr = 0;
+ if (binvals) {
+ if (minreversal > 1.0) {
+ L_WARNING("binary values but minreversal > 1\n", procName);
+ } else {
+ ia = numaGetIArray(nas);
+ ival = ia[0];
+ for (i = 1; i < n; i++) {
+ if (ia[i] != ival) {
+ nr++;
+ ival = ia[i];
+ }
+ }
+ LEPT_FREE(ia);
+ }
+ } else {
+ nat = numaFindExtrema(nas, minreversal, NULL);
+ nr = numaGetCount(nat);
+ numaDestroy(&nat);
+ }
+ if (pnr) *pnr = nr;
+ if (prd) {
+ numaGetParameters(nas, NULL, &delx);
+ len = delx * n;
+ *prd = (l_float32)nr / len;
+ }
+
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Threshold crossings and frequency analysis *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief numaSelectCrossingThreshold()
+ *
+ * \param[in] nax [optional] numa of abscissa values; can be NULL
+ * \param[in] nay signal
+ * \param[in] estthresh estimated pixel threshold for crossing:
+ * e.g., for images, white <--> black; typ. ~120
+ * \param[out] pbestthresh robust estimate of threshold to use
+ * \return 0 if OK, 1 on error or warning
+ *
+ * <pre>
+ * Notes:
+ * (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.
+ * (3) If the count of nay is less than 2, a warning is issued.
+ * </pre>
+ */
+l_ok
+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);
+ if (numaGetCount(nay) < 2) {
+ L_WARNING("nay count < 2; no threshold crossing\n", procName);
+ return 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, maxstart = 0, maxend = 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;
+ }
+ }
+
+ *pbestthresh = estthresh - 80.0 + 2.0 * (l_float32)(maxstart + maxend);
+
+#if DEBUG_CROSSINGS
+ lept_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);
+ lept_stderr("The best choice: %5.1f\n", *pbestthresh);
+ lept_stderr("Number of crossings at the 41 thresholds:");
+ numaWriteStderr(nat);
+#endif /* DEBUG_CROSSINGS */
+
+ numaDestroy(&nat);
+ return 0;
+}
+
+
+/*!
+ * \brief numaCrossingsByThreshold()
+ *
+ * \param[in] nax [optional] numa of abscissa values; can be NULL
+ * \param[in] nay numa of ordinate values, corresponding to nax
+ * \param[in] thresh threshold value for nay
+ * \return nad abscissa pts at threshold, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If nax == NULL, we use startx and delx from nay to compute
+ * the crossing values in nad.
+ * </pre>
+ */
+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);
+ if (n < 2) return nad;
+ 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;
+}
+
+
+/*!
+ * \brief numaCrossingsByPeaks()
+ *
+ * \param[in] nax [optional] numa of abscissa values
+ * \param[in] nay numa of ordinate values, corresponding to nax
+ * \param[in] delta parameter used to identify when a new peak can be found
+ * \return nad abscissa pts at threshold, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If nax == NULL, we use startx and delx from nay to compute
+ * the crossing values in nad.
+ * </pre>
+ */
+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 (!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);
+
+ /* 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, NULL);
+ numaAddNumber(nap, n - 1);
+ np = numaGetCount(nap);
+ L_INFO("Number of crossings: %d\n", procName, np);
+
+ /* Do all computation in index units of nax or the delx of nay */
+ nad = numaCreate(np); /* output crossing locations, 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;
+}
+
+
+/*!
+ * \brief numaEvalBestHaarParameters()
+ *
+ * \param[in] nas numa of non-negative signal values
+ * \param[in] 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.
+ * \param[in] nwidth number of widths to consider
+ * \param[in] nshift number of shifts to consider for each width
+ * \param[in] minwidth smallest width to consider
+ * \param[in] maxwidth largest width to consider
+ * \param[out] pbestwidth width giving largest score
+ * \param[out] pbestshift shift giving largest score
+ * \param[out] pbestscore [optional] convolution with "Haar"-like comb
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 = bestwidth = bestshift = 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
+ lept_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;
+}
+
+
+/*!
+ * \brief numaEvalHaarSum()
+ *
+ * \param[in] nas numa of non-negative signal values
+ * \param[in] width distance between +1 and -1 in convolution comb
+ * \param[in] shift phase of the comb: location of first +1
+ * \param[in] 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.
+ * \param[out] pscore convolution with "Haar"-like comb
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief genConstrainedNumaInRange()
+ *
+ * \param[in] first first number to choose; >= 0
+ * \param[in] last biggest possible number to reach; >= first
+ * \param[in] nmax maximum number of numbers to select; > 0
+ * \param[in] use_pairs 1 = select pairs of adjacent numbers;
+ * 0 = select individual numbers
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (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.
+ * </pre>
+ */
+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/leptonica/src/pageseg.c b/leptonica/src/pageseg.c
new file mode 100644
index 00000000..c04f2567
--- /dev/null
+++ b/leptonica/src/pageseg.c
@@ -0,0 +1,2473 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pageseg.c
+ * <pre>
+ *
+ * Top level page segmentation
+ * l_int32 pixGetRegionsBinary()
+ *
+ * Halftone region extraction
+ * PIX *pixGenHalftoneMask() **Deprecated wrapper**
+ * PIX *pixGenerateHalftoneMask()
+
+ *
+ * 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()
+ * PIXA *pixExtractRawTextlines()
+ *
+ * How many text columns
+ * l_int32 pixCountTextColumns()
+ *
+ * Decision: text vs photo
+ * l_int32 pixDecideIfText()
+ * l_int32 pixFindThreshFgExtent()
+ *
+ * Decision: table vs text
+ * l_int32 pixDecideIfTable()
+ * Pix *pixPrepare1bpp()
+ *
+ * Estimate the grayscale background value
+ * l_int32 pixEstimateBackground()
+ *
+ * Largest white or black rectangles in an image
+ * l_int32 pixFindLargeRectangles()
+ * l_int32 pixFindLargestRectangle()
+ *
+ * Generate rectangle inside connected component
+ * BOX *pixFindRectangleInCC()
+ *
+ * Automatic photoinvert for OCR
+ * PIX *pixAutoPhotoinvert()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+#include "math.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;
+
+/*------------------------------------------------------------------*
+ * Top level page segmentation *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixGetRegionsBinary()
+ *
+ * \param[in] pixs 1 bpp, assumed to be 300 to 400 ppi
+ * \param[out] ppixhm [optional] halftone mask
+ * \param[out] ppixtm [optional] textline mask
+ * \param[out] ppixtb [optional] textblock mask
+ * \param[in] pixadb input for collecting debug pix; use NULL to skip
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is best to deskew the image before segmenting.
+ * (2) Passing in %pixadb enables debug output.
+ * </pre>
+ */
+l_ok
+pixGetRegionsBinary(PIX *pixs,
+ PIX **ppixhm,
+ PIX **ppixtm,
+ PIX **ppixtb,
+ PIXA *pixadb)
+{
+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);
+ if (pixadb) pixaAddPix(pixadb, pixr, L_COPY);
+
+ /* Get the halftone mask */
+ pixhm2 = pixGenerateHalftoneMask(pixr, &pixtext, &htfound, pixadb);
+
+ /* Get the textline mask from the text pixels */
+ pixtm2 = pixGenTextlineMask(pixtext, &pixvws, &tlfound, pixadb);
+
+ /* Get the textblock mask from the textline mask */
+ pixtb2 = pixGenTextblockMask(pixtm2, pixvws, pixadb);
+ 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 = NULL;
+ if (pixtb2) {
+ pixtbf2 = pixSelectBySize(pixtb2, 60, 60, 4, L_SELECT_IF_EITHER,
+ L_SELECT_IF_GTE, NULL);
+ pixDestroy(&pixtb2);
+ if (pixadb) pixaAddPix(pixadb, pixtbf2, L_COPY);
+ }
+
+ /* 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(&pixhm2);
+ pixDestroy(&pix1);
+ if (pixadb) pixaAddPix(pixadb, pixhm, L_COPY);
+
+ pix1 = pixExpandReplicate(pixtm2, 2);
+ pixtm = pixDilateBrick(NULL, pix1, 3, 3);
+ pixDestroy(&pixtm2);
+ pixDestroy(&pix1);
+ if (pixadb) pixaAddPix(pixadb, pixtm, L_COPY);
+
+ if (pixtbf2) {
+ pix1 = pixExpandReplicate(pixtbf2, 2);
+ pixtb = pixDilateBrick(NULL, pix1, 3, 3);
+ pixDestroy(&pixtbf2);
+ pixDestroy(&pix1);
+ if (pixadb) pixaAddPix(pixadb, pixtb, L_COPY);
+ } else {
+ pixtb = pixCreateTemplate(pixs); /* empty mask */
+ }
+
+ /* Debug: identify objects that are neither text nor halftone image */
+ if (pixadb) {
+ pix1 = pixSubtract(NULL, pixs, pixtm); /* remove text pixels */
+ pix2 = pixSubtract(NULL, pix1, pixhm); /* remove halftone pixels */
+ pixaAddPix(pixadb, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ /* Debug: display textline components with random colors */
+ if (pixadb) {
+ 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);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ pixaDestroy(&pixa);
+ boxaDestroy(&boxa);
+ }
+
+ /* Debug: identify the outlines of each textblock */
+ if (pixadb) {
+ PIXCMAP *cmap;
+ PTAA *ptaa;
+ ptaa = pixGetOuterBordersPtaa(pixtb);
+ lept_mkdir("lept/pageseg");
+ ptaaWriteDebug("/tmp/lept/pageseg/tb_outlines.ptaa", ptaa, 1);
+ pix1 = pixRenderRandomCmapPtaa(pixtb, ptaa, 1, 16, 1);
+ cmap = pixGetColormap(pix1);
+ pixcmapResetColor(cmap, 0, 130, 130, 130);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ ptaaDestroy(&ptaa);
+ }
+
+ /* Debug: get b.b. for all mask components */
+ if (pixadb) {
+ BOXA *bahm, *batm, *batb;
+ bahm = pixConnComp(pixhm, NULL, 4);
+ batm = pixConnComp(pixtm, NULL, 4);
+ batb = pixConnComp(pixtb, NULL, 4);
+ boxaWriteDebug("/tmp/lept/pageseg/htmask.boxa", bahm);
+ boxaWriteDebug("/tmp/lept/pageseg/textmask.boxa", batm);
+ boxaWriteDebug("/tmp/lept/pageseg/textblock.boxa", batb);
+ boxaDestroy(&bahm);
+ boxaDestroy(&batm);
+ boxaDestroy(&batb);
+ }
+ if (pixadb) {
+ pixaConvertToPdf(pixadb, 0, 1.0, 0, 0, "Debug page segmentation",
+ "/tmp/lept/pageseg/debug.pdf");
+ L_INFO("Writing debug pdf to /tmp/lept/pageseg/debug.pdf\n", procName);
+ }
+
+ 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixGenHalftoneMask()
+ *
+ * <pre>
+ * Deprecated:
+ * This wrapper avoids an ABI change with tesseract 3.0.4.
+ * It should be removed when we no longer need to support 3.0.4.
+ * The debug parameter is ignored (assumed 0).
+ * </pre>
+ */
+PIX *
+pixGenHalftoneMask(PIX *pixs,
+ PIX **ppixtext,
+ l_int32 *phtfound,
+ l_int32 debug)
+{
+ return pixGenerateHalftoneMask(pixs, ppixtext, phtfound, NULL);
+}
+
+
+/*!
+ * \brief pixGenerateHalftoneMask()
+ *
+ * \param[in] pixs 1 bpp, assumed to be 150 to 200 ppi
+ * \param[out] ppixtext [optional] text part of pixs
+ * \param[out] phtfound [optional] 1 if the mask is not empty
+ * \param[in] pixadb input for collecting debug pix; use NULL to skip
+ * \return pixd halftone mask, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is not intended to work on small thumbnails. The
+ * dimensions of pixs must be at least MinWidth x MinHeight.
+ * </pre>
+ */
+PIX *
+pixGenerateHalftoneMask(PIX *pixs,
+ PIX **ppixtext,
+ l_int32 *phtfound,
+ PIXA *pixadb)
+{
+l_int32 w, h, empty;
+PIX *pix1, *pix2, *pixhs, *pixhm, *pixd;
+
+ PROCNAME("pixGenerateHalftoneMask");
+
+ 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, 0, 0);
+ pix2 = pixOpenBrick(NULL, pix1, 5, 5);
+ pixhs = pixExpandReplicate(pix2, 4); /* back to 2x reduction */
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ if (pixadb) pixaAddPix(pixadb, pixhs, L_COPY);
+
+ /* Compute mask for connected regions */
+ pixhm = pixCloseSafeBrick(NULL, pixs, 4, 4);
+ if (pixadb) pixaAddPix(pixadb, pixhm, L_COPY);
+
+ /* Fill seed into mask to get halftone mask */
+ pixd = pixSeedfillBinary(NULL, pixhs, pixhm, 4);
+ if (pixadb) pixaAddPix(pixadb, pixd, L_COPY);
+
+#if 0
+ pixOpenBrick(pixd, pixd, 9, 9);
+#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);
+ if (pixadb) pixaAddPix(pixadb, *ppixtext, L_COPY);
+ }
+
+ pixDestroy(&pixhs);
+ pixDestroy(&pixhm);
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Textline extraction *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixGenTextlineMask()
+ *
+ * \param[in] pixs 1 bpp, assumed to be 150 to 200 ppi
+ * \param[out] ppixvws vertical whitespace mask
+ * \param[out] ptlfound [optional] 1 if the mask is not empty
+ * \param[in] pixadb input for collecting debug pix; use NULL to skip
+ * \return pixd textline mask, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixGenTextlineMask(PIX *pixs,
+ PIX **ppixvws,
+ l_int32 *ptlfound,
+ PIXA *pixadb)
+{
+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);
+ if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
+ 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;
+ if (pixadb) pixaAddPix(pixadb, pixvws, L_COPY);
+ 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 = pixMorphSequence(pixs, "c30.1", 0);
+ if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
+ pixd = pixSubtract(NULL, pix1, pixvws);
+ pixOpenBrick(pixd, pixd, 3, 3);
+ if (pixadb) pixaAddPix(pixadb, pixd, L_COPY);
+ pixDestroy(&pix1);
+
+ /* Check if text line mask is empty */
+ if (ptlfound) {
+ pixZero(pixd, &empty);
+ if (!empty)
+ *ptlfound = 1;
+ }
+
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Textblock extraction *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixGenTextblockMask()
+ *
+ * \param[in] pixs 1 bpp, textline mask, assumed to be 150 to 200 ppi
+ * \param[in] pixvws vertical white space mask
+ * \param[in] pixadb input for collecting debug pix; use NULL to skip
+ * \return pixd textblock mask, or NULL if empty or on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+PIX *
+pixGenTextblockMask(PIX *pixs,
+ PIX *pixvws,
+ PIXA *pixadb)
+{
+l_int32 w, h, empty;
+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);
+ pixZero(pix1, &empty);
+ if (empty) {
+ pixDestroy(&pix1);
+ L_INFO("no fg pixels in textblock mask\n", procName);
+ return NULL;
+ }
+ if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
+
+ /* 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);
+ if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
+ pix3 = pixSubtract(NULL, pix2, pixvws);
+ if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
+ pixd = pixSelectBySize(pix3, 25, 5, 8, L_SELECT_IF_BOTH,
+ L_SELECT_IF_GTE, NULL);
+ if (pixadb) pixaAddPix(pixadb, pixd, L_COPY);
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Location of page foreground *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixFindPageForeground()
+ *
+ * \param[in] pixs full resolution (any type or depth
+ * \param[in] threshold for binarization; typically about 128
+ * \param[in] mindist min distance of text from border to allow
+ * cleaning near border; at 2x reduction, this
+ * should be larger than 50; typically about 70
+ * \param[in] erasedist when conditions are satisfied, erase anything
+ * within this distance of the edge;
+ * typically 20-30 at 2x reduction
+ * \param[in] showmorph debug: set to a negative integer to show steps
+ * in generating masks; this is typically used
+ * for debugging region extraction
+ * \param[in] pixac debug: allocate outside and pass this in to
+ * accumulate results of each call to this function,
+ * which can be displayed in a mosaic or a pdf.
+ * \return box region including foreground, with some pixel noise
+ * removed, or NULL if not found
+ *
+ * <pre>
+ * 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) This is not intended to work on small thumbnails. The
+ * dimensions of pixs must be at least MinWidth x MinHeight.
+ * (3) Debug: set showmorph to display the intermediate image in
+ * the morphological operations on this page.
+ * (4) Debug: to get pdf output of results when called repeatedly,
+ * call with an existing pixac, which will add an image of this page,
+ * with the fg outlined. If no foreground is found, there is
+ * no output for this page image.
+ * </pre>
+ */
+BOX *
+pixFindPageForeground(PIX *pixs,
+ l_int32 threshold,
+ l_int32 mindist,
+ l_int32 erasedist,
+ l_int32 showmorph,
+ PIXAC *pixac)
+{
+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) ? 100 : 0;
+ pixb = pixConvertTo1(pixs, threshold);
+ pixb2 = pixScale(pixb, 0.5, 0.5);
+ pixseed = pixMorphSequence(pixb2, "o1.2 + c9.9 + o3.3", flag);
+ pix1 = pixMorphSequence(pixb2, "o50.1", 0);
+ pixOr(pixseed, pixseed, pix1);
+ pixDestroy(&pix1);
+ pix1 = pixMorphSequence(pixb2, "o1.50", 0);
+ pixOr(pixseed, pixseed, pix1);
+ pixDestroy(&pix1);
+ pixsf = pixSeedfillBinary(NULL, pixseed, pixb2, 8);
+ pixSetOrClearBorder(pixsf, 10, 10, 10, 10, PIX_SET);
+ pixm = pixRemoveBorderConnComps(pixsf, 8);
+
+ /* 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);
+ 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);
+ boxDestroy(&boxin);
+ if (!intersects) boxDestroy(&boxfg);
+ }
+
+ boxd = NULL;
+ if (boxfg) {
+ boxAdjustSides(boxfg, boxfg, -2, 2, -2, 2); /* tiny expansion */
+ boxd = boxTransform(boxfg, 0, 0, 2.0, 2.0);
+
+ /* Save the debug image showing the box for this page */
+ if (pixac) {
+ pixg2 = pixConvert1To4Cmap(pixb);
+ pixRenderBoxArb(pixg2, boxd, 3, 255, 0, 0);
+ pixacompAddPix(pixac, pixg2, IFF_DEFAULT);
+ pixDestroy(&pixg2);
+ }
+ }
+
+ pixDestroy(&pixb);
+ pixDestroy(&pixb2);
+ pixDestroy(&pixseed);
+ pixDestroy(&pixsf);
+ pixDestroy(&pixm);
+ boxDestroy(&boxfg);
+ return boxd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Extraction of characters from image with only text *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixSplitIntoCharacters()
+ *
+ * \param[in] pixs 1 bpp, contains only deskewed text
+ * \param[in] minw min component width for initial filtering; typ. 4
+ * \param[in] minh min component height for initial filtering; typ. 4
+ * \param[out] pboxa [optional] character bounding boxes
+ * \param[out] ppixa [optional] character images
+ * \param[out] ppixdebug [optional] showing splittings
+ *
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSplitComponentWithProfile()
+ *
+ * \param[in] pixs 1 bpp, exactly one connected component
+ * \param[in] delta distance used in extrema finding in a numa; typ. 10
+ * \param[in] mindel minimum required difference between profile
+ * minimum and profile values +2 and -2 away; typ. 7
+ * \param[out] ppixdebug [optional] debug image of splitting
+ * \return boxa of c.c. after splitting, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, NULL);
+ 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) numaWriteStderr(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) {
+ lept_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) {
+ lept_mkdir("lept/split");
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/split/split", NULL);
+ }
+#endif
+
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ LEPT_FREE(array1);
+ LEPT_FREE(array2);
+
+ if (nsplit == 0) { /* no splitting */
+ numaDestroy(&nasplit);
+ 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixExtractTextlines()
+ *
+ * \param[in] pixs any depth, assumed to have nearly horizontal text
+ * \param[in] maxw, maxh initial filtering: remove any components in pixs
+ * with components larger than maxw or maxh
+ * \param[in] minw, minh final filtering: remove extracted 'lines'
+ * with sizes smaller than minw or minh; use
+ * 0 for default.
+ * \param[in] adjw, adjh final adjustment of boxes representing each
+ * text line. If > 0, these increase the box
+ * size at each edge by this amount.
+ * \param[in] pixadb pixa for saving intermediate steps; NULL to omit
+ * \return pixa of textline images, including bounding boxes, or
+ * NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function assumes that textline fragments have sufficient
+ * vertical separation and small enough skew so that a
+ * horizontal dilation sufficient to join words will not join
+ * textlines. It does not guarantee that horizontally adjacent
+ * textline fragments on the same line will be joined.
+ * (2) For images with multiple columns, it attempts to avoid joining
+ * textlines across the space between columns. If that is not
+ * a concern, you can also use pixExtractRawTextlines(),
+ * which will join them with alacrity.
+ * (3) This first removes components from pixs that are either
+ * wide (> %maxw) or tall (> %maxh).
+ * (4) A final filtering operation removes small components, such
+ * that width < %minw or height < %minh.
+ * (5) For reasonable accuracy, the resolution of pixs should be
+ * at least 100 ppi. For reasonable efficiency, the resolution
+ * should not exceed 600 ppi.
+ * (6) This can be used to determine if some region of a scanned
+ * image is horizontal text.
+ * (7) As an example, for a pix with resolution 300 ppi, a reasonable
+ * set of parameters is:
+ * pixExtractTextlines(pix, 150, 150, 36, 20, 5, 5, NULL);
+ * The defaults minw and minh for 300 ppi are about 36 and 20,
+ * so the same result is obtained with:
+ * pixExtractTextlines(pix, 150, 150, 0, 0, 5, 5, NULL);
+ * (8) The output pixa is composed of subimages, one for each textline,
+ * and the boxa in the pixa tells where in %pixs each textline goes.
+ * </pre>
+ */
+PIXA *
+pixExtractTextlines(PIX *pixs,
+ l_int32 maxw,
+ l_int32 maxh,
+ l_int32 minw,
+ l_int32 minh,
+ l_int32 adjw,
+ l_int32 adjh,
+ PIXA *pixadb)
+{
+char buf[64];
+l_int32 res, csize, empty;
+BOXA *boxa1, *boxa2, *boxa3;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa1, *pixa2, *pixa3;
+
+ 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(&pix2);
+ 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;
+ }
+ if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
+
+ /* Remove any very tall or very wide connected components */
+ pix2 = pixSelectBySize(pix1, maxw, maxh, 8, L_SELECT_IF_BOTH,
+ L_SELECT_IF_LT, NULL);
+ if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
+ 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.0);
+ snprintf(buf, sizeof(buf), "c%d.1 + o%d.1", csize, csize / 3);
+ pix3 = pixMorphCompSequence(pix2, buf, 0);
+ if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
+
+ /* Extract the connected components. These should be dilated lines */
+ boxa1 = pixConnComp(pix3, &pixa1, 4);
+ if (pixadb) {
+ pix1 = pixaDisplayRandomCmap(pixa1, 0, 0);
+ pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ }
+
+ /* Set minw, minh if default is requested */
+ minw = (minw != 0) ? minw : (l_int32)(0.12 * res);
+ minh = (minh != 0) ? minh : (l_int32)(0.07 * res);
+
+ /* Remove line components that are too small */
+ pixa2 = pixaSelectBySize(pixa1, minw, minh, L_SELECT_IF_BOTH,
+ L_SELECT_IF_GTE, NULL);
+ if (pixadb) {
+ pix1 = pixaDisplayRandomCmap(pixa2, 0, 0);
+ pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ pix1 = pixConvertTo32(pix2);
+ pixRenderBoxaArb(pix1, pixa2->boxa, 2, 255, 0, 0);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ }
+
+ /* Selectively AND with the version before dilation, and save */
+ boxa2 = pixaGetBoxa(pixa2, L_CLONE);
+ boxa3 = boxaAdjustSides(boxa2, -adjw, adjw, -adjh, adjh);
+ pixa3 = pixClipRectangles(pix2, boxa3);
+ if (pixadb) {
+ pix1 = pixaDisplayRandomCmap(pixa3, 0, 0);
+ pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ }
+
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+ return pixa3;
+}
+
+
+/*!
+ * \brief pixExtractRawTextlines()
+ *
+ * \param[in] pixs any depth, assumed to have nearly horizontal text
+ * \param[in] maxw, maxh initial filtering: remove any components in pixs
+ * with components larger than maxw or maxh;
+ * use 0 for default values.
+ * \param[in] adjw, adjh final adjustment of boxes representing each
+ * text line. If > 0, these increase the box
+ * size at each edge by this amount.
+ * \param[in] pixadb pixa for saving intermediate steps; NULL to omit
+ * \return pixa of textline images, including bounding boxes, or
+ * NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) 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. It aggressively joins textlines across multiple
+ * columns, so if that is not desired, you must either (a) make
+ * sure that %pixs is a single column of text or (b) use instead
+ * pixExtractTextlines(), which is more conservative
+ * about joining text fragments that have vertical overlap.
+ * (2) This first removes components from pixs that are either
+ * very wide (> %maxw) or very tall (> %maxh).
+ * (3) For reasonable accuracy, the resolution of pixs should be
+ * at least 100 ppi. For reasonable efficiency, the resolution
+ * should not exceed 600 ppi.
+ * (4) This can be used to determine if some region of a scanned
+ * image is horizontal text.
+ * (5) As an example, for a pix with resolution 300 ppi, a reasonable
+ * set of parameters is:
+ * pixExtractRawTextlines(pix, 150, 150, 0, 0, NULL);
+ * (6) The output pixa is composed of subimages, one for each textline,
+ * and the boxa in the pixa tells where in %pixs each textline goes.
+ * </pre>
+ */
+PIXA *
+pixExtractRawTextlines(PIX *pixs,
+ l_int32 maxw,
+ l_int32 maxh,
+ l_int32 adjw,
+ l_int32 adjh,
+ PIXA *pixadb)
+{
+char buf[64];
+l_int32 res, csize, empty;
+BOXA *boxa1, *boxa2, *boxa3;
+BOXAA *baa1;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa1, *pixa2;
+
+ PROCNAME("pixExtractRawTextlines");
+
+ if (!pixs)
+ return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
+
+ /* Set maxw, maxh if default is requested */
+ if ((res = pixGetXRes(pixs)) == 0) {
+ L_INFO("Resolution is not set: setting to 300 ppi\n", procName);
+ res = 300;
+ }
+ maxw = (maxw != 0) ? maxw : (l_int32)(0.5 * res);
+ maxh = (maxh != 0) ? maxh : (l_int32)(0.5 * res);
+
+ /* 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(&pix2);
+ 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;
+ }
+ if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
+
+ /* Remove any very tall or very wide connected components */
+ pix2 = pixSelectBySize(pix1, maxw, maxh, 8, L_SELECT_IF_BOTH,
+ L_SELECT_IF_LT, NULL);
+ if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
+ pixDestroy(&pix1);
+
+ /* Filter to solidify the text lines within the x-height region.
+ * The closing (csize) bridges gaps between words. */
+ csize = L_MIN(120., 60.0 * res / 300.0);
+ snprintf(buf, sizeof(buf), "c%d.1", csize);
+ pix3 = pixMorphCompSequence(pix2, buf, 0);
+ if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
+
+ /* Extract the connected components. These should be dilated lines */
+ boxa1 = pixConnComp(pix3, &pixa1, 4);
+ if (pixadb) {
+ pix1 = pixaDisplayRandomCmap(pixa1, 0, 0);
+ pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ }
+
+ /* Do a 2-d sort, and generate a bounding box for each set of text
+ * line segments that is aligned horizontally (i.e., has vertical
+ * overlap) into a box representing a single text line. */
+ baa1 = boxaSort2d(boxa1, NULL, -1, -1, 5);
+ boxaaGetExtent(baa1, NULL, NULL, NULL, &boxa2);
+ if (pixadb) {
+ pix1 = pixConvertTo32(pix2);
+ pixRenderBoxaArb(pix1, boxa2, 2, 255, 0, 0);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ }
+
+ /* Optionally adjust the sides of each text line box, and then
+ * use the boxes to generate a pixa of the text lines. */
+ boxa3 = boxaAdjustSides(boxa2, -adjw, adjw, -adjh, adjh);
+ pixa2 = pixClipRectangles(pix2, boxa3);
+ if (pixadb) {
+ pix1 = pixaDisplayRandomCmap(pixa2, 0, 0);
+ pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ }
+
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixaDestroy(&pixa1);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+ boxaaDestroy(&baa1);
+ return pixa2;
+}
+
+
+/*------------------------------------------------------------------*
+ * How many text columns *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixCountTextColumns()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] deltafract fraction of (max - min) to be used in the delta
+ * for extrema finding; typ 0.3
+ * \param[in] peakfract fraction of (max - min) to be used to threshold
+ * the peak value; typ. 0.5
+ * \param[in] clipfract fraction of image dimension removed on each side;
+ * typ. 0.1, which leaves w and h reduced by 0.8
+ * \param[out] pncols number of columns; -1 if not determined
+ * \param[in] pixadb [optional] pre-allocated, for showing
+ * intermediate computation; use null to skip
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 between 37 and 75 ppi;
+ * most of the processing is done at this resolution.
+ * (3) If no text is found (essentially a blank page),
+ * this returns ncols = 0.
+ * (4) For debug output, input a pre-allocated pixa.
+ * </pre>
+ */
+l_ok
+pixCountTextColumns(PIX *pixs,
+ l_float32 deltafract,
+ l_float32 peakfract,
+ l_float32 clipfract,
+ l_int32 *pncols,
+ PIXA *pixadb)
+{
+l_int32 w, h, res, i, n, npeak;
+l_float32 scalefact, redfact, minval, maxval, val4, val5, fract;
+BOX *box;
+NUMA *na1, *na2, *na3, *na4, *na5;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5;
+
+ PROCNAME("pixCountTextColumns");
+
+ if (!pncols)
+ return ERROR_INT("&ncols not defined", procName, 1);
+ *pncols = -1; /* init */
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+ if (deltafract < 0.15 || deltafract > 0.75)
+ L_WARNING("deltafract not in [0.15 ... 0.75]\n", procName);
+ if (peakfract < 0.25 || peakfract > 0.9)
+ L_WARNING("peakfract not in [0.25 ... 0.9]\n", procName);
+ if (clipfract < 0.0 || clipfract >= 0.5)
+ return ERROR_INT("clipfract not in [0.0 ... 0.5)\n", procName, 1);
+ if (pixadb) pixaAddPix(pixadb, pixs, L_COPY);
+
+ /* Scale to between 37.5 and 75 ppi */
+ if ((res = pixGetXRes(pixs)) == 0) {
+ L_WARNING("resolution undefined; set to 300\n", procName);
+ pixSetResolution(pixs, 300, 300);
+ res = 300;
+ }
+ if (res < 37) {
+ L_WARNING("resolution %d very low\n", procName, res);
+ scalefact = 37.5 / res;
+ pix1 = pixScale(pixs, scalefact, scalefact);
+ } else {
+ redfact = (l_float32)res / 37.5;
+ if (redfact < 2.0)
+ pix1 = pixClone(pixs);
+ else if (redfact < 4.0)
+ pix1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
+ else if (redfact < 8.0)
+ pix1 = pixReduceRankBinaryCascade(pixs, 1, 2, 0, 0);
+ else if (redfact < 16.0)
+ pix1 = pixReduceRankBinaryCascade(pixs, 1, 2, 2, 0);
+ else
+ pix1 = pixReduceRankBinaryCascade(pixs, 1, 2, 2, 2);
+ }
+ if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
+
+ /* Crop inner 80% of image */
+ pixGetDimensions(pix1, &w, &h, NULL);
+ box = boxCreate(clipfract * w, clipfract * h,
+ (1.0 - 2 * clipfract) * w, (1.0 - 2 * clipfract) * h);
+ pix2 = pixClipRectangle(pix1, box, NULL);
+ pixGetDimensions(pix2, &w, &h, NULL);
+ boxDestroy(&box);
+ if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
+
+ /* Deskew */
+ pix3 = pixDeskew(pix2, 0);
+ if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
+
+ /* Close to increase column counts for text */
+ pix4 = pixCloseSafeBrick(NULL, pix3, 5, 21);
+ if (pixadb) pixaAddPix(pixadb, pix4, L_COPY);
+ pixInvert(pix4, pix4);
+ na1 = pixCountByColumn(pix4, NULL);
+
+ if (pixadb) {
+ gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/plot", NULL);
+ pix5 = pixRead("/tmp/lept/plot.png");
+ pixaAddPix(pixadb, pix5, L_INSERT);
+ }
+
+ /* Analyze the column counts. na4 gives the locations of
+ * the extrema in normalized units (0.0 to 1.0) across the
+ * cropped image. na5 gives the magnitude of the
+ * extrema, normalized to the dynamic range. The peaks
+ * are values that are at least peakfract of (max - min). */
+ numaGetMax(na1, &maxval, NULL);
+ numaGetMin(na1, &minval, NULL);
+ fract = (l_float32)(maxval - minval) / h; /* is there much at all? */
+ if (fract < 0.05) {
+ L_INFO("very little content on page; 0 text columns\n", procName);
+ *pncols = 0;
+ } else {
+ na2 = numaFindExtrema(na1, deltafract * (maxval - minval), &na3);
+ na4 = numaTransform(na2, 0, 1.0 / w);
+ na5 = numaTransform(na3, -minval, 1.0 / (maxval - minval));
+ n = numaGetCount(na4);
+ for (i = 0, npeak = 0; i < n; i++) {
+ numaGetFValue(na4, i, &val4);
+ numaGetFValue(na5, i, &val5);
+ if (val4 > 0.3 && val4 < 0.7 && val5 >= peakfract) {
+ npeak++;
+ L_INFO("Peak(loc,val) = (%5.3f,%5.3f)\n", procName, val4, val5);
+ }
+ }
+ *pncols = npeak + 1;
+ numaDestroy(&na2);
+ numaDestroy(&na3);
+ numaDestroy(&na4);
+ numaDestroy(&na5);
+ }
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ numaDestroy(&na1);
+ return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ * Decision text vs photo *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixDecideIfText()
+ *
+ * \param[in] pixs any depth
+ * \param[in] box [optional] if null, use entire pixs
+ * \param[out] pistext 1 if text; 0 if photo; -1 if not determined or empty
+ * \param[in] pixadb [optional] pre-allocated, for showing intermediate
+ * computation; use NULL to skip
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+pixDecideIfText(PIX *pixs,
+ BOX *box,
+ l_int32 *pistext,
+ PIXA *pixadb)
+{
+l_int32 i, empty, maxw, w, h, n1, n2, n3, minlines, big_comp;
+l_float32 ratio1, ratio2;
+L_BMF *bmf;
+BOXA *boxa1, *boxa2, *boxa3, *boxa4, *boxa5;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
+PIXA *pixa1;
+SEL *sel1;
+
+ PROCNAME("pixDecideIfText");
+
+ if (!pistext)
+ return ERROR_INT("&istext not defined", procName, 1);
+ *pistext = -1;
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+
+ /* Crop, convert to 1 bpp, 300 ppi */
+ if ((pix1 = pixPrepare1bpp(pixs, box, 0.1f, 300)) == NULL)
+ return ERROR_INT("pix1 not made", procName, 1);
+
+ pixZero(pix1, &empty);
+ if (empty) {
+ pixDestroy(&pix1);
+ L_INFO("pix is empty\n", procName);
+ return 0;
+ }
+ w = pixGetWidth(pix1);
+
+ /* 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. */
+ pix2 = pixCreate(11, 81, 1);
+ for (i = 0; i < 81; i++)
+ pixSetPixel(pix2, 5, i, 1);
+ sel1 = selCreateFromPix(pix2, 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);
+ pix3 = pixHMT(NULL, pix1, sel1);
+ pix4 = pixSeedfillBinaryRestricted(NULL, pix3, pix1, 8, 5, 1000);
+ pix5 = pixXor(NULL, pix1, pix4);
+ pixDestroy(&pix2);
+ selDestroy(&sel1);
+
+ /* Convert the text lines to separate long horizontal components */
+ pix6 = pixMorphCompSequence(pix5, "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(pix6, NULL, &h, NULL);
+ } else { /* use height of region that has text lines */
+ pixFindThreshFgExtent(pix6, 400, NULL, &h);
+ }
+
+ if (pixadb) {
+ bmf = bmfCreate(NULL, 6);
+ pixaAddPixWithText(pixadb, pix1, 1, bmf, "threshold/crop to binary",
+ 0x0000ff00, L_ADD_BELOW);
+ pixaAddPixWithText(pixadb, pix3, 2, bmf, "hit-miss for vertical line",
+ 0x0000ff00, L_ADD_BELOW);
+ pixaAddPixWithText(pixadb, pix4, 2, bmf, "restricted seed-fill",
+ 0x0000ff00, L_ADD_BELOW);
+ pixaAddPixWithText(pixadb, pix5, 2, bmf, "remove using xor",
+ 0x0000ff00, L_ADD_BELOW);
+ pixaAddPixWithText(pixadb, pix6, 2, bmf, "make long horiz components",
+ 0x0000ff00, L_ADD_BELOW);
+ }
+
+ /* Extract the connected components */
+ if (pixadb) {
+ boxa1 = pixConnComp(pix6, &pixa1, 8);
+ pix7 = pixaDisplayRandomCmap(pixa1, 0, 0);
+ pixcmapResetColor(pixGetColormap(pix7), 0, 255, 255, 255);
+ pixaAddPixWithText(pixadb, pix7, 2, bmf, "show connected components",
+ 0x0000ff00, L_ADD_BELOW);
+ pixDestroy(&pix7);
+ pixaDestroy(&pixa1);
+ bmfDestroy(&bmf);
+ } else {
+ boxa1 = pixConnComp(pix6, 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(&pix1);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ return 0;
+}
+
+
+/*!
+ * \brief pixFindThreshFgExtent()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] thresh threshold number of pixels in row
+ * \param[out] ptop [optional] location of top of region
+ * \param[out] pbot [optional] location of bottom of region
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixFindThreshFgExtent(PIX *pixs,
+ l_int32 thresh,
+ l_int32 *ptop,
+ l_int32 *pbot)
+{
+l_int32 i, n;
+l_int32 *array;
+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;
+}
+
+
+/*------------------------------------------------------------------*
+ * Decision: table vs text *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixDecideIfTable()
+ *
+ * \param[in] pixs any depth, any resolution >= 75 ppi
+ * \param[in] box [optional] if null, use entire pixs
+ * \param[in] orient L_PORTRAIT_MODE, L_LANDSCAPE_MODE
+ * \param[out] pscore 0 - 4; -1 if not determined
+ * \param[in] pixadb [optional] pre-allocated, for showing intermediate
+ * computation; use NULL to skip
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is assumed that pixs has the correct resolution set.
+ * If the resolution is 0, we assume it is 300 ppi and issue a warning.
+ * (2) If %orient == L_LANDSCAPE_MODE, the image is rotated 90 degrees
+ * clockwise before being analyzed.
+ * (3) The interpretation of the returned score:
+ * -1 undetermined
+ * 0 no table
+ * 1 unlikely to have a table
+ * 2 likely to have a table
+ * 3 even more likely to have a table
+ * 4 extremely likely to have a table
+ * * Setting the condition for finding a table at score >= 2 works
+ * well, except for false positives on kanji and landscape text.
+ * * These false positives can be removed by setting the condition
+ * at score >= 3, but recall is lowered because it will not find
+ * tables without either horizontal or vertical lines.
+ * (4) Most of the processing takes place at 75 ppi.
+ * (5) Internally, three numbers are determined, for horizontal and
+ * vertical fg lines, and for vertical bg lines. From these,
+ * four tests are made to decide if there is a table occupying
+ * a significant part of the image.
+ * (6) Images have arbitrary content and would be likely to trigger
+ * this detector, so they are checked for first, and if found,
+ * return with a 0 (no table) score.
+ * (7) Musical scores (tablature) are likely to trigger the detector.
+ * (8) Tables of content with more than 2 columns are likely to
+ * trigger the detector.
+ * (9) For debug output, input a pre-allocated pixa.
+ * </pre>
+ */
+l_ok
+pixDecideIfTable(PIX *pixs,
+ BOX *box,
+ l_int32 orient,
+ l_int32 *pscore,
+ PIXA *pixadb)
+{
+l_int32 empty, nhb, nvb, nvw, score, htfound;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8, *pix9;
+
+ PROCNAME("pixDecideIfTable");
+
+ if (!pscore)
+ return ERROR_INT("&score not defined", procName, 1);
+ *pscore = -1;
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+
+ /* Check if there is an image region. First convert to 1 bpp
+ * at 175 ppi. If an image is found, assume there is no table. */
+ pix1 = pixPrepare1bpp(pixs, box, 0.1f, 175);
+ pix2 = pixGenerateHalftoneMask(pix1, NULL, &htfound, NULL);
+ if (htfound && pixadb) pixaAddPix(pixadb, pix2, L_COPY);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ if (htfound) {
+ *pscore = 0;
+ L_INFO("pix has an image region\n", procName);
+ return 0;
+ }
+
+ /* Crop, convert to 1 bpp, 75 ppi */
+ if ((pix1 = pixPrepare1bpp(pixs, box, 0.05f, 75)) == NULL)
+ return ERROR_INT("pix1 not made", procName, 1);
+
+ pixZero(pix1, &empty);
+ if (empty) {
+ *pscore = 0;
+ pixDestroy(&pix1);
+ L_INFO("pix is empty\n", procName);
+ return 0;
+ }
+
+ /* The 2x2 dilation on 75 ppi makes these two approaches very similar:
+ * (1) pix1 = pixPrepare1bpp(..., 300); // 300 ppi resolution
+ * pix2 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0);
+ * (2) pix1 = pixPrepare1bpp(..., 75); // 75 ppi resolution
+ * pix2 = pixDilateBrick(NULL, pix1, 2, 2);
+ * But (2) is more efficient if the input image to pixPrepare1bpp()
+ * is not at 300 ppi. */
+ pix2 = pixDilateBrick(NULL, pix1, 2, 2);
+
+ /* Deskew both horizontally and vertically; rotate by 90
+ * degrees if in landscape mode. */
+ pix3 = pixDeskewBoth(pix2, 1);
+ if (pixadb) {
+ pixaAddPix(pixadb, pix2, L_COPY);
+ pixaAddPix(pixadb, pix3, L_COPY);
+ }
+ if (orient == L_LANDSCAPE_MODE)
+ pix4 = pixRotate90(pix3, 1);
+ else
+ pix4 = pixClone(pix3);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pix1 = pixClone(pix4);
+ pixDestroy(&pix4);
+
+ /* Look for horizontal and vertical lines */
+ pix2 = pixMorphSequence(pix1, "o100.1 + c1.4", 0);
+ pix3 = pixSeedfillBinary(NULL, pix2, pix1, 8);
+ pix4 = pixMorphSequence(pix1, "o1.100 + c4.1", 0);
+ pix5 = pixSeedfillBinary(NULL, pix4, pix1, 8);
+ pix6 = pixOr(NULL, pix3, pix5);
+ if (pixadb) {
+ pixaAddPix(pixadb, pix2, L_COPY);
+ pixaAddPix(pixadb, pix4, L_COPY);
+ pixaAddPix(pixadb, pix3, L_COPY);
+ pixaAddPix(pixadb, pix5, L_COPY);
+ pixaAddPix(pixadb, pix6, L_COPY);
+ }
+ pixCountConnComp(pix2, 8, &nhb); /* number of horizontal black lines */
+ pixCountConnComp(pix4, 8, &nvb); /* number of vertical black lines */
+
+ /* Remove the lines */
+ pixSubtract(pix1, pix1, pix6);
+ if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
+
+ /* Remove noise pixels */
+ pix7 = pixMorphSequence(pix1, "c4.1 + o8.1", 0);
+ if (pixadb) pixaAddPix(pixadb, pix7, L_COPY);
+
+ /* Look for vertical white space. Invert to convert white bg
+ * to fg. Use a single rank-1 2x reduction, which closes small
+ * fg holes, for the final processing at 37.5 ppi.
+ * The vertical opening is then about 3 inches on a 300 ppi image.
+ * We also remove vertical whitespace that is less than 5 pixels
+ * wide at this resolution (about 0.1 inches) */
+ pixInvert(pix7, pix7);
+ pix8 = pixMorphSequence(pix7, "r1 + o1.100", 0);
+ pix9 = pixSelectBySize(pix8, 5, 0, 8, L_SELECT_WIDTH,
+ L_SELECT_IF_GTE, NULL);
+ pixCountConnComp(pix9, 8, &nvw); /* number of vertical white lines */
+ if (pixadb) {
+ pixaAddPix(pixadb, pixScale(pix8, 2.0, 2.0), L_INSERT);
+ pixaAddPix(pixadb, pixScale(pix9, 2.0, 2.0), L_INSERT);
+ }
+
+ /* Require at least 2 of the following 4 conditions for a table.
+ * Some tables do not have black (fg) lines, and for those we
+ * require more than 6 long vertical whitespace (bg) lines. */
+ score = 0;
+ if (nhb > 1) score++;
+ if (nvb > 2) score++;
+ if (nvw > 3) score++;
+ if (nvw > 6) score++;
+ *pscore = score;
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ pixDestroy(&pix6);
+ pixDestroy(&pix7);
+ pixDestroy(&pix8);
+ pixDestroy(&pix9);
+ return 0;
+}
+
+
+/*!
+ * \brief pixPrepare1bpp()
+ *
+ * \param[in] pixs any depth
+ * \param[in] box [optional] if null, use entire pixs
+ * \param[in] cropfract fraction to be removed from the boundary;
+ * use 0.0 to retain the entire image
+ * \param[in] outres desired resolution of output image; if the
+ * input image resolution is not set, assume
+ * 300 ppi; use 0 to skip scaling.
+ * \return pixd if OK, NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This handles some common pre-processing operations,
+ * where the page segmentation algorithm takes a 1 bpp image.
+ * </pre>
+ */
+PIX *
+pixPrepare1bpp(PIX *pixs,
+ BOX *box,
+ l_float32 cropfract,
+ l_int32 outres)
+{
+l_int32 w, h, res;
+l_float32 factor;
+BOX *box1;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5;
+
+ PROCNAME("pixPrepare1bpp");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+ /* Crop the image. If no box is given, use %cropfract to remove
+ * pixels near the image boundary; this helps avoid false
+ * negatives from noise that is often found there. */
+ if (box) {
+ pix1 = pixClipRectangle(pixs, box, NULL);
+ } else {
+ pixGetDimensions(pixs, &w, &h, NULL);
+ box1 = boxCreate((l_int32)(cropfract * w), (l_int32)(cropfract * h),
+ (l_int32)((1.0 - 2 * cropfract) * w),
+ (l_int32)((1.0 - 2 * cropfract) * h));
+ pix1 = pixClipRectangle(pixs, box1, NULL);
+ boxDestroy(&box1);
+ }
+
+ /* Convert to 1 bpp with adaptive background cleaning */
+ if (pixGetDepth(pixs) > 1) {
+ pix2 = pixConvertTo8(pix1, 0);
+ pix3 = pixCleanBackgroundToWhite(pix2, NULL, NULL, 1.0, 70, 160);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ if (!pix3) {
+ L_INFO("pix cleaning failed\n", procName);
+ return NULL;
+ }
+ pix4 = pixThresholdToBinary(pix3, 200);
+ pixDestroy(&pix3);
+ } else {
+ pix4 = pixClone(pix1);
+ pixDestroy(&pix1);
+ }
+
+ /* Scale the image to the requested output resolution;
+ do not scale if %outres <= 0 */
+ if (outres <= 0)
+ return pix4;
+ if ((res = pixGetXRes(pixs)) == 0) {
+ L_WARNING("Resolution is not set: using 300 ppi\n", procName);
+ res = 300;
+ }
+ if (res != outres) {
+ factor = (l_float32)outres / (l_float32)res;
+ pix5 = pixScale(pix4, factor, factor);
+ } else {
+ pix5 = pixClone(pix4);
+ }
+ pixDestroy(&pix4);
+ return pix5;
+}
+
+
+/*------------------------------------------------------------------*
+ * Estimate the grayscale background value *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixEstimateBackground()
+ *
+ * \param[in] pixs 8 bpp, with or without colormap
+ * \param[in] darkthresh pixels below this value are never considered
+ * part of the background; typ. 70; use 0 to skip
+ * \param[in] edgecrop fraction of half-width on each side, and of
+ * half-height at top and bottom, that are cropped
+ * \param[out] pbg estimated background, or 0 on error
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Caller should check that return bg value is > 0.
+ * </pre>
+ */
+l_ok
+pixEstimateBackground(PIX *pixs,
+ l_int32 darkthresh,
+ l_float32 edgecrop,
+ l_int32 *pbg)
+{
+l_int32 w, h, sampling;
+l_float32 fbg;
+BOX *box;
+PIX *pix1, *pix2, *pixm;
+
+ PROCNAME("pixEstimateBackground");
+
+ if (!pbg)
+ return ERROR_INT("&bg not defined", procName, 1);
+ *pbg = 0;
+ if (!pixs || pixGetDepth(pixs) != 8)
+ return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+ if (darkthresh > 128)
+ L_WARNING("darkthresh unusually large\n", procName);
+ if (edgecrop < 0.0 || edgecrop >= 1.0)
+ return ERROR_INT("edgecrop not in [0.0 ... 1.0)", procName, 1);
+
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+ pixGetDimensions(pix1, &w, &h, NULL);
+
+ /* Optionally crop inner part of image */
+ if (edgecrop > 0.0) {
+ box = boxCreate(0.5 * edgecrop * w, 0.5 * edgecrop * h,
+ (1.0 - edgecrop) * w, (1.0 - edgecrop) * h);
+ pix2 = pixClipRectangle(pix1, box, NULL);
+ boxDestroy(&box);
+ } else {
+ pix2 = pixClone(pix1);
+ }
+
+ /* We will use no more than 50K samples */
+ sampling = L_MAX(1, (l_int32)sqrt((l_float64)(w * h) / 50000. + 0.5));
+
+ /* Optionally make a mask over all pixels lighter than %darkthresh */
+ pixm = NULL;
+ if (darkthresh > 0) {
+ pixm = pixThresholdToBinary(pix2, darkthresh);
+ pixInvert(pixm, pixm);
+ }
+
+ pixGetRankValueMasked(pix2, pixm, 0, 0, sampling, 0.5, &fbg, NULL);
+ *pbg = (l_int32)(fbg + 0.5);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pixm);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Largest white or black rectangles in an image *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixFindLargeRectangles()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] polarity 0 within background, 1 within foreground
+ * \param[in] nrect number of rectangles to be found
+ * \param[out] pboxa largest rectangles, sorted by decreasing area
+ * \param[in,out] ppixdb optional return output with rectangles drawn on it
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This does a greedy search to find the largest rectangles,
+ * either black or white and without overlaps, in %pix.
+ * (2) See pixFindLargestRectangle(), which is called multiple
+ * times, for details. On each call, the largest rectangle
+ * found is painted, so that none of its pixels can be
+ * used later, before calling it again.
+ * (3) This function is surprisingly fast. Although
+ * pixFindLargestRectangle() runs at about 50 MPix/sec, when it
+ * is run multiple times by pixFindLargeRectangles(), it processes
+ * at 150 - 250 MPix/sec, and the time is approximately linear
+ * in %nrect. For example, for a 1 MPix image, searching for
+ * the largest 50 boxes takes about 0.2 seconds.
+ * </pre>
+ */
+l_ok
+pixFindLargeRectangles(PIX *pixs,
+ l_int32 polarity,
+ l_int32 nrect,
+ BOXA **pboxa,
+ PIX **ppixdb)
+{
+l_int32 i, op, bx, by, bw, bh;
+BOX *box;
+BOXA *boxa;
+PIX *pix;
+
+ PROCNAME("pixFindLargeRectangles");
+
+ if (ppixdb) *ppixdb = NULL;
+ if (!pboxa)
+ return ERROR_INT("&boxa not defined", procName, 1);
+ *pboxa = NULL;
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+ if (polarity != 0 && polarity != 1)
+ return ERROR_INT("invalid polarity", procName, 1);
+ if (nrect > 1000) {
+ L_WARNING("large num rectangles = %d requested; using 1000\n",
+ procName, nrect);
+ nrect = 1000;
+ }
+
+ pix = pixCopy(NULL, pixs);
+ boxa = boxaCreate(nrect);
+ *pboxa = boxa;
+
+ /* Sequentially find largest rectangle and fill with opposite color */
+ for (i = 0; i < nrect; i++) {
+ if (pixFindLargestRectangle(pix, polarity, &box, NULL) == 1) {
+ boxDestroy(&box);
+ L_ERROR("failure in pixFindLargestRectangle\n", procName);
+ break;
+ }
+ boxaAddBox(boxa, box, L_INSERT);
+ op = (polarity == 0) ? PIX_SET : PIX_CLR;
+ boxGetGeometry(box, &bx, &by, &bw, &bh);
+ pixRasterop(pix, bx, by, bw, bh, op, NULL, 0, 0);
+ }
+
+ if (ppixdb)
+ *ppixdb = pixDrawBoxaRandom(pixs, boxa, 3);
+
+ pixDestroy(&pix);
+ return 0;
+}
+
+
+/*!
+ * \brief pixFindLargestRectangle()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] polarity 0 within background, 1 within foreground
+ * \param[out] pbox largest area rectangle
+ * \param[in,out] ppixdb optional return output with rectangle drawn on it
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a simple and elegant solution to a problem in
+ * computational geometry that at first appears to be 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.
+ * </pre>
+ */
+l_ok
+pixFindLargestRectangle(PIX *pixs,
+ l_int32 polarity,
+ BOX **pbox,
+ PIX **ppixdb)
+{
+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 (ppixdb) *ppixdb = NULL;
+ 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 (ppixdb) {
+ *ppixdb = pixConvertTo8(pixs, TRUE);
+ pixRenderHashBoxArb(*ppixdb, box, 6, 2, L_NEG_SLOPE_LINE, 1, 255, 0, 0);
+ }
+
+ LEPT_FREE(linew);
+ LEPT_FREE(lineh);
+ LEPT_FREE(lowestfg);
+ pixDestroy(&pixw);
+ pixDestroy(&pixh);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Generate rectangle inside connected component *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixFindRectangleInCC()
+ *
+ * \param[in] pixs 1 bpp, with sufficient closings to make the fg be
+ * a single c.c. that is a convex hull
+ * \param[in] boxs [optional] if NULL, %pixs should be a minimum
+ * container of a single c.c.
+ * \param[in] fract first and all consecutive lines found must be at
+ * least this fraction of the fast scan dimension
+ * \param[in] dir L_SCAN_HORIZONTAL, L_SCAN_VERTICAL; direction of
+ * fast scan
+ * \param[in] select L_GEOMETRIC_UNION, L_GEOMETRIC_INTERSECTION,
+ * L_LARGEST_AREA, L_SMALEST_AREA
+ * \param[in] debug if 1, generates output pdf showing intermediate
+ * computation and final result
+ * \return box of included rectangle, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Computation is similar to pixFindLargestRectangle(), but allows
+ * a different set of results to choose from.
+ * (2) Select the fast scan direction. Then, scanning in the slow
+ * direction, find the longest run of ON pixels in the fast
+ * scan direction and look for the first run that is longer
+ * than %fract of the dimension. Continue until a shorter run
+ * is found. This generates a box of ON pixels fitting into the c.c.
+ * (3) Do this from both slow scan directions and use %select to get
+ * a resulting box from these two.
+ * (4) The extracted rectangle is not necessarily the largest that
+ * can fit in the c.c. To get that, use pixFindLargestRectangle().
+ */
+BOX *
+pixFindRectangleInCC(PIX *pixs,
+ BOX *boxs,
+ l_float32 fract,
+ l_int32 dir,
+ l_int32 select,
+ l_int32 debug)
+{
+l_int32 x, y, i, w, h, w1, h1, w2, h2, found, res;
+l_int32 xfirst, xlast, xstart, yfirst, ylast, length;
+BOX *box1, *box2, *box3, *box4, *box5;
+PIX *pix1, *pix2, *pixdb1, *pixdb2;
+PIXA *pixadb;
+
+ PROCNAME("pixFindRectangleInCC");
+
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return (BOX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+ if (fract <= 0.0 || fract > 1.0)
+ return (BOX *)ERROR_PTR("invalid fraction", procName, NULL);
+ if (dir != L_SCAN_VERTICAL && dir != L_SCAN_HORIZONTAL)
+ return (BOX *)ERROR_PTR("invalid scan direction", procName, NULL);
+ if (select != L_GEOMETRIC_UNION && select != L_GEOMETRIC_INTERSECTION &&
+ select != L_LARGEST_AREA && select != L_SMALLEST_AREA)
+ return (BOX *)ERROR_PTR("invalid select", procName, NULL);
+
+ /* Extract the c.c. if necessary */
+ x = y = 0;
+ if (boxs) {
+ pix1 = pixClipRectangle(pixs, boxs, NULL);
+ boxGetGeometry(boxs, &x, &y, NULL, NULL);
+ } else {
+ pix1 = pixClone(pixs);
+ }
+
+ /* All fast scans are horizontal; rotate 90 deg cw if necessary */
+ if (dir == L_SCAN_VERTICAL)
+ pix2 = pixRotate90(pix1, 1);
+ else /* L_SCAN_HORIZONTAL */
+ pix2 = pixClone(pix1);
+ pixGetDimensions(pix2, &w, &h, NULL);
+
+ pixadb = (debug) ? pixaCreate(0) : NULL;
+ pixdb1 = NULL;
+ if (pixadb) {
+ lept_mkdir("lept/rect");
+ pixaAddPix(pixadb, pix1, L_CLONE);
+ pixdb1 = pixConvertTo32(pix2);
+ }
+ pixDestroy(&pix1);
+
+ /* Scanning down, find the first scanline with a long enough run.
+ * That run goes from (xfirst, yfirst) to (xlast, yfirst). */
+ found = FALSE;
+ for (i = 0; i < h; i++) {
+ pixFindMaxHorizontalRunOnLine(pix2, i, &xstart, &length);
+ if (length >= (l_int32)(fract * w + 0.5)) {
+ yfirst = i;
+ xfirst = xstart;
+ xlast = xfirst + length - 1;
+ found = TRUE;
+ break;
+ }
+ }
+ if (!found) {
+ L_WARNING("no run of sufficient size was found\n", procName);
+ pixDestroy(&pix2);
+ pixDestroy(&pixdb1);
+ pixaDestroy(&pixadb);
+ return NULL;
+ }
+
+ /* Continue down until the condition fails */
+ w1 = xlast - xfirst + 1;
+ h1 = h - yfirst; /* init */
+ ylast = h - 1; /* init */
+ for (i = yfirst + 1; i < h; i++) {
+ pixFindMaxHorizontalRunOnLine(pix2, i, &xstart, &length);
+ if (xstart > xfirst || (xstart + length - 1 < xlast) ||
+ i == h - 1) {
+ ylast = i - 1;
+ h1 = ylast - yfirst + 1;
+ break;
+ }
+ }
+ box1 = boxCreate(xfirst, yfirst, w1, h1);
+
+ /* Scanning up, find the first scanline with a long enough run.
+ * That run goes from (xfirst, ylast) to (xlast, ylast). */
+ for (i = h - 1; i >= 0; i--) {
+ pixFindMaxHorizontalRunOnLine(pix2, i, &xstart, &length);
+ if (length >= (l_int32)(fract * w + 0.5)) {
+ ylast = i;
+ xfirst = xstart;
+ xlast = xfirst + length - 1;
+ break;
+ }
+ }
+
+ /* Continue up until the condition fails */
+ w2 = xlast - xfirst + 1;
+ h2 = ylast + 1; /* initialize */
+ for (i = ylast - 1; i >= 0; i--) {
+ pixFindMaxHorizontalRunOnLine(pix2, i, &xstart, &length);
+ if (xstart > xfirst || (xstart + length - 1 < xlast) ||
+ i == 0) {
+ yfirst = i + 1;
+ h2 = ylast - yfirst + 1;
+ break;
+ }
+ }
+ box2 = boxCreate(xfirst, yfirst, w2, h2);
+ pixDestroy(&pix2);
+
+ if (pixadb) {
+ pixRenderBoxArb(pixdb1, box1, 2, 255, 0, 0);
+ pixRenderBoxArb(pixdb1, box2, 2, 0, 255, 0);
+ pixaAddPix(pixadb, pixdb1, L_INSERT);
+ }
+
+ /* Select the final result from the two boxes */
+ if (select == L_GEOMETRIC_UNION)
+ box3 = boxBoundingRegion(box1, box2);
+ else if (select == L_GEOMETRIC_INTERSECTION)
+ box3 = boxOverlapRegion(box1, box2);
+ else if (select == L_LARGEST_AREA)
+ box3 = (w1 * h1 >= w2 * h2) ? boxCopy(box1) : boxCopy(box2);
+ else /* select == L_SMALLEST_AREA) */
+ box3 = (w1 * h1 <= w2 * h2) ? boxCopy(box1) : boxCopy(box2);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+
+ /* Rotate the box 90 degrees ccw if necessary */
+ box4 = NULL;
+ if (box3) {
+ if (dir == L_SCAN_VERTICAL)
+ box4 = boxRotateOrth(box3, w, h, 3);
+ else
+ box4 = boxCopy(box3);
+ }
+
+ /* Transform back to global coordinates if %boxs exists */
+ box5 = (box4) ? boxTransform(box4, x, y, 1.0, 1.0) : NULL;
+ boxDestroy(&box3);
+ boxDestroy(&box4);
+
+ /* Debug output */
+ if (pixadb) {
+ pixdb1 = pixConvertTo8(pixs, 0);
+ pixAddConstantGray(pixdb1, 190);
+ pixdb2 = pixConvertTo32(pixdb1);
+ if (box5) pixRenderBoxArb(pixdb2, box5, 4, 0, 0, 255);
+ pixaAddPix(pixadb, pixdb2, L_INSERT);
+ res = pixGetXRes(pixs);
+ L_INFO("Writing debug files to /tmp/lept/rect/\n", procName);
+ pixaConvertToPdf(pixadb, res, 1.0, L_DEFAULT_ENCODE, 75, NULL,
+ "/tmp/lept/rect/fitrect.pdf");
+ pix1 = pixaDisplayTiledAndScaled(pixadb, 32, 800, 1, 0, 40, 2);
+ pixWrite("/tmp/lept/rect/fitrect.png", pix1, IFF_PNG);
+ pixDestroy(&pix1);
+ pixDestroy(&pixdb1);
+ pixaDestroy(&pixadb);
+ }
+
+ return box5;
+}
+
+/*------------------------------------------------------------------*
+ * Automatic photoinvert for OCR *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixAutoPhotoinvert()
+ *
+ * \param[in] pixs any depth, colormap ok
+ * \param[in] thresh binarization threshold; use 0 for default
+ * \param[out] ppixm [optional] image regions to be inverted
+ * \param[out] pixadb [optional] debug; input NULL to skip
+ * \return pixd 1 bpp image to be sent to OCR, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) A 1 bpp image is returned, where pixels in image regions are
+ * photo-inverted.
+ * (2) If there is light text with a dark background, this will
+ * identify the region and photoinvert the pixels there if
+ * there are at least 60% fg pixels in the region.
+ * (3) For debug output, input a (typically empty) %pixadb.
+ * </pre>
+ */
+PIX *
+pixAutoPhotoinvert(PIX *pixs,
+ l_int32 thresh,
+ PIX **ppixm,
+ PIXA *pixadb)
+{
+l_int32 i, n, empty, x, y, w, h;
+l_float32 fgfract;
+BOX *box1;
+BOXA *boxa1;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5;
+
+ PROCNAME("pixAutoPhotoinvert");
+
+ if (ppixm) *ppixm = NULL;
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+ if (thresh == 0) thresh = 128;
+
+ if ((pix1 = pixConvertTo1(pixs, thresh)) == NULL)
+ return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+ if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
+
+ /* Identify regions for photo-inversion:
+ * (1) Start with the halftone mask.
+ * (2) Eliminate ordinary text and halftones in the mask.
+ * (3) Some regions of inverted text may have been removed in
+ * steps (1) and (2). Conditionally fill holes in the mask,
+ * but do not fill out to the bounding rect. */
+ pix2 = pixGenerateHalftoneMask(pix1, NULL, NULL, pixadb);
+ pix3 = pixMorphSequence(pix2, "o15.15 + c25.25", 0); /* remove noise */
+ pix4 = pixFillHolesToBoundingRect(pix3, 1, 0.5, 1.0);
+ if (pixadb) {
+ pixaAddPix(pixadb, pix2, L_CLONE);
+ pixaAddPix(pixadb, pix3, L_CLONE);
+ pixaAddPix(pixadb, pix4, L_COPY);
+ }
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixZero(pix4, &empty);
+ if (empty) {
+ pixDestroy(&pix4);
+ return pix1;
+ }
+
+ /* Examine each component and validate the inversion.
+ * Require at least 60% of pixels under each component to be FG. */
+ boxa1 = pixConnCompBB(pix4, 8);
+ n = boxaGetCount(boxa1);
+ for (i = 0; i < n; i++) {
+ box1 = boxaGetBox(boxa1, i, L_COPY);
+ pix5 = pixClipRectangle(pix1, box1, NULL);
+ pixForegroundFraction(pix5, &fgfract);
+ if (pixadb) lept_stderr("fg fraction: %5.3f\n", fgfract);
+ boxGetGeometry(box1, &x, &y, &w, &h);
+ if (fgfract < 0.6) /* erase from the mask */
+ pixRasterop(pix4, x, y, w, h, PIX_CLR, NULL, 0, 0);
+ pixDestroy(&pix5);
+ boxDestroy(&box1);
+ }
+ boxaDestroy(&boxa1);
+ pixZero(pix4, &empty);
+ if (empty) {
+ pixDestroy(&pix4);
+ return pix1;
+ }
+
+ /* Combine pixels of the photo-inverted pix with the binarized input */
+ pix5 = pixInvert(NULL, pix1);
+ pixCombineMasked(pix1, pix5, pix4);
+
+ if (pixadb) {
+ pixaAddPix(pixadb, pix5, L_CLONE);
+ pixaAddPix(pixadb, pix1, L_COPY);
+ }
+ pixDestroy(&pix5);
+ if (ppixm)
+ *ppixm = pix4;
+ else
+ pixDestroy(&pix4);
+ return pix1;
+}
diff --git a/leptonica/src/paintcmap.c b/leptonica/src/paintcmap.c
new file mode 100644
index 00000000..b0f27982
--- /dev/null
+++ b/leptonica/src/paintcmap.c
@@ -0,0 +1,765 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file paintcmap.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*-------------------------------------------------------------*
+ * Repaint selected pixels in region *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixSetSelectCmap()
+ *
+ * \param[in] pixs 1, 2, 4 or 8 bpp, with colormap
+ * \param[in] box [optional] region to set color; can be NULL
+ * \param[in] sindex colormap index of pixels to be changed
+ * \param[in] rval, gval, bval new color to paint
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixColorGrayRegionsCmap()
+ *
+ * \param[in] pixs 8 bpp, with colormap
+ * \param[in] boxa of regions in which to apply color
+ * \param[in] type L_PAINT_LIGHT, L_PAINT_DARK
+ * \param[in] rval, gval, bval target color
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 %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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixColorGrayCmap()
+ *
+ * \param[in] pixs 2, 4 or 8 bpp, with colormap
+ * \param[in] box [optional] region to set color; can be NULL
+ * \param[in] type L_PAINT_LIGHT, L_PAINT_DARK
+ * \param[in] rval, gval, bval target color
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixColorGrayMaskedCmap()
+ *
+ * \param[in] pixs 8 bpp, with colormap
+ * \param[in] pixm 1 bpp mask, through which to apply color
+ * \param[in] type L_PAINT_LIGHT, L_PAINT_DARK
+ * \param[in] rval, gval, bval target color
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief addColorizedGrayToCmap()
+ *
+ * \param[in] cmap from 2 or 4 bpp pix
+ * \param[in] type L_PAINT_LIGHT, L_PAINT_DARK
+ * \param[in] rval, gval, bval target color
+ * \param[out] pna [optional] table for mapping new cmap entries
+ * \return 0 if OK; 1 on error; 2 if new colors will not fit in cmap.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixSetSelectMaskedCmap()
+ *
+ * \param[in] pixs 2, 4 or 8 bpp, with colormap
+ * \param[in] pixm [optional] 1 bpp mask; no-op if NULL
+ * \param[in] x, y UL corner of mask relative to pixs
+ * \param[in] sindex cmap index of pixels in pixs to be changed
+ * \param[in] rval, gval, bval new color to substitute
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is an in-place operation.
+ * (2) This paints through the fg of pixm and replaces all pixels
+ * in pixs that have the 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.
+ * </pre>
+ */
+l_ok
+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 */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ 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 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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixSetMaskedCmap()
+ *
+ * \param[in] pixs 2, 4 or 8 bpp, colormapped
+ * \param[in] pixm [optional] 1 bpp mask; no-op if NULL
+ * \param[in] x, y origin of pixm relative to pixs;
+ * can be negative
+ * \param[in] rval, gval, bval new color to set at each masked pixel
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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/leptonica/src/parseprotos.c b/leptonica/src/parseprotos.c
new file mode 100644
index 00000000..56f0821c
--- /dev/null
+++ b/leptonica/src/parseprotos.c
@@ -0,0 +1,978 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*
+ * \file parseprotos.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+#define L_BUF_SIZE 2048 /* 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);
+
+
+/*
+ * \brief parseForProtos()
+ *
+ * \param[in] filein output of cpp
+ * \param[in] prestring [optional] string that prefaces each decl;
+ * use NULL to omit
+ * \return parsestr string of function prototypes, or NULL on error
+ *
+ * <pre>
+ * 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 'enum', 'union' and
+ * 'struct', are followed after a while by '{', and eventually
+ * end with '}, plus an optional token and a final ';'.
+ * Others, like 'extern', 'static' and 'typedef', 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. With the exception of an
+ * __attribute__ declaration for a struct, 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.
+ * </pre>
+ */
+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;
+/* lept_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 does not begin with
+ * static, extern or typedef. 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 lp.
+ * 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. We also have a typedef in
+ * bmpio.c that has the form:
+ * typedef struct __attribute__((....)) { ...} ... ;
+ * This is avoided by blacklisting 'typedef' along with 'extern'
+ * and 'static'. */
+ satest = sarrayCreateWordsFromString(str);
+ secondword = sarrayGetString(satest, 1, L_NOCOPY);
+ if (strcmp(secondword, "static") && /* not static */
+ strcmp(secondword, "extern") && /* not extern */
+ strcmp(secondword, "typedef")) { /* not typedef */
+ 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;
+}
+
+
+/*
+ * \brief getNextNonCommentLine()
+ *
+ * \param[in] sa output from cpp, by line)
+ * \param[in] start starting index to search)
+ * \param[out] pnext index of first uncommented line after the start line
+ * \return 0 if OK, o on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Skips over all consecutive comment lines, beginning at 'start'
+ * (2) If all lines to the end are '#' comments, return next = -1
+ * </pre>
+ */
+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;
+}
+
+
+/*
+ * \brief getNextNonBlankLine()
+ *
+ * \param[in] sa output from cpp, by line
+ * \param[in] start starting index to search
+ * \param[out] pnext index of first nonblank line after the start line
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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;
+}
+
+
+/*
+ * \brief getNextNonDoubleSlashLine()
+ *
+ * \param[in] sa output from cpp, by line
+ * \param[in] start starting index to search
+ * \param[out] pnext index of first uncommented line after the start line
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Skips over all consecutive '//' lines, beginning at 'start'
+ * (2) If all lines to the end start with '//', return next = -1
+ * </pre>
+ */
+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;
+}
+
+
+/*
+ * \brief searchForProtoSignature()
+ *
+ * \param[in] sa output from cpp, by line
+ * \param[in] begin beginning index to search
+ * \param[out] pstart starting index for function definition
+ * \param[out] pstop index of line on which proto is completed
+ * \param[out] pcharindex char index of completing ')' character
+ * \param[out] pfound 1 if valid signature is found; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*
+ * \brief captureProtoSignature()
+ *
+ * \param[in] sa output from cpp, by line
+ * \param[in] start starting index to search; never a comment line
+ * \param[in] stop index of line on which pattern is completed
+ * \param[in] charindex char index of completing ')' character
+ * \return cleanstr prototype string, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Return all characters, ending with a ';' after the ')'
+ * </pre>
+ */
+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;
+}
+
+
+/*
+ * \brief cleanProtoSignature()
+ *
+ * \param[in] instr input prototype string
+ * \return cleanstr clean prototype string, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Adds 'extern' at beginning and regularizes spaces
+ * between tokens.
+ * </pre>
+ */
+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) {
+ sarrayDestroy(&sa);
+ sarrayDestroy(&saout);
+ 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;
+}
+
+
+/*
+ * \brief skipToEndOfFunction()
+ *
+ * \param[in] sa output from cpp, by line
+ * \param[in] start index of starting line with left bracket to search
+ * \param[in] lbindex starting char index for left bracket
+ * \param[out] pnext 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;
+}
+
+
+/*
+ * \brief skipToMatchingBrace()
+ *
+ * \param[in] sa output from cpp, by line
+ * \param[in] start index of starting line with left bracket to search
+ * \param[in] lbindex starting char index for left bracket
+ * \param[out] pstop index of line with the matching right bracket
+ * \param[out] prbindex char index of matching right bracket
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If the matching right brace is not found, returns
+ * stop = -1. This shouldn't happen.
+ * </pre>
+ */
+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);
+}
+
+
+/*
+ * \brief skipToSemicolon()
+ *
+ * \param[in] sa output from cpp, by line
+ * \param[in] start index of starting line to search
+ * \param[in] charindex starting char index for search
+ * \param[out] pnext index of line containing the next ';'
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*
+ * \brief getOffsetForCharacter()
+ *
+ * \param[in] sa output from cpp, by line
+ * \param[in] start starting index in sa to search;
+ * never a comment line
+ * \param[in] tchar we are searching for the first instance of this
+ * \param[out] psoffset offset in strings from start index
+ * \param[out] pboffset offset in bytes within string in which
+ * the character is first found
+ * \param[out] ptoffset 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*
+ * \brief getOffsetForMatchingRP()
+ *
+ * \param[in] sa output from cpp, by line
+ * \param[in] start starting index in sa to search;
+ * never a comment line
+ * \param[in] soffsetlp string offset to first LP
+ * \param[in] boffsetlp byte offset within string to first LP
+ * \param[in] toffsetlp total byte offset to first LP
+ * \param[out] psoffset offset in strings from start index
+ * \param[out] pboffset offset in bytes within string in which
+ * the matching RP is found
+ * \param[out] ptoffset 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/partify.c b/leptonica/src/partify.c
new file mode 100644
index 00000000..4625b84a
--- /dev/null
+++ b/leptonica/src/partify.c
@@ -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.
+ *====================================================================*/
+
+/*!
+ * \file partify.c
+ * <pre>
+ *
+ * Top level
+ * l_int32 partifyFiles()
+ * l_int32 partifyPixac()
+ *
+ * Helpers
+ * static BOXA *pixLocateStaveSets()
+ * static l_int32 boxaRemoveVGaps()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Static helplers */
+static BOXA *pixLocateStaveSets(PIX *pixs, l_int32 pageno, PIXA *pixadb);
+static l_ok boxaRemoveVGaps(BOXA *boxa);
+
+/*---------------------------------------------------------------------*
+ * Top level *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief partifyFiles()
+ *
+ * \param[in] dirname directory of files
+ * \param[in] substr required filename substring; use NULL for all files
+ * \param[in] nparts number of parts to generate (counting from top)
+ * \param[in] outroot root name of output pdf files
+ * \param[in] debugfile [optional] set to NULL for no debug output
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) All page images are compressed in png format into a pixacomp.
+ * (2) Each page image is deskewed, binarized at 300 ppi,
+ * partified into %nparts, and saved in a set of pixacomps
+ * in tiff-g4 format.
+ * (3) Each partified pixacomp is rendered into a set of page images,
+ * and output as a pdf.
+ * </pre>
+ */
+l_ok
+partifyFiles(const char *dirname,
+ const char *substr,
+ l_int32 nparts,
+ const char *outroot,
+ const char *debugfile)
+{
+PIXA *pixadb;
+PIXAC *pixac;
+
+ PROCNAME("partifyFiles");
+
+ if (!dirname)
+ return ERROR_INT("dirname not defined", procName, 1);
+ if (nparts < 0 || nparts > 10)
+ return ERROR_INT("nparts not in [1 ... 10]", procName, 1);
+ if (!outroot || outroot[0] == '\n')
+ return ERROR_INT("outroot undefined or empty", procName, 1);
+
+ pixadb = (debugfile) ? pixaCreate(0) : NULL;
+ pixac = pixacompCreateFromFiles(dirname, substr, IFF_PNG);
+ partifyPixac(pixac, nparts, outroot, pixadb);
+ if (pixadb) {
+ L_INFO("writing debug output to %s\n", procName, debugfile);
+ pixaConvertToPdf(pixadb, 300, 1.0, L_FLATE_ENCODE, 0,
+ "Partify Debug", debugfile);
+ }
+ pixacompDestroy(&pixac);
+ pixaDestroy(&pixadb);
+ return 0;
+}
+
+
+/*!
+ * \brief partifyPixac()
+ *
+ * \param[in] pixac with at least one image
+ * \param[in] nparts number of parts to generate (counting from top)
+ * \param[in] outroot root name of output pdf files
+ * \param[in] pixadb [optional] debug pixa; can be NULL
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See partifyPixac().
+ * (2) If the image files do not have a resolution, 300 ppi is assumed.
+ * </pre>
+ */
+l_ok
+partifyPixac(PIXAC *pixac,
+ l_int32 nparts,
+ const char *outroot,
+ PIXA *pixadb)
+{
+char buf[512];
+l_int32 i, j, pageno, res, npage, nbox, icount, line;
+l_float32 factor;
+L_BMF *bmf;
+BOX *box1, *box2;
+BOXA *boxa1, *boxa2, *boxa3;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXAC **pixaca;
+
+ PROCNAME("partifyPixac");
+
+ if (!pixac)
+ return ERROR_INT("pixac not defined", procName, 1);
+ if ((npage = pixacompGetCount(pixac)) == 0)
+ return ERROR_INT("pixac is empty", procName, 1);
+ if (nparts < 1 || nparts > 10)
+ return ERROR_INT("nparts not in [1 ... 10]", procName, 1);
+ if (!outroot || outroot[0] == '\n')
+ return ERROR_INT("outroot undefined or empty", procName, 1);
+
+ /* Initialize the output array for each of the nparts */
+ pixaca = (PIXAC **)LEPT_CALLOC(nparts, sizeof(PIXAC *));
+ for (i = 0; i < nparts; i++)
+ pixaca[i] = pixacompCreate(0);
+
+ /* Process each page */
+ line = 1;
+ bmf = bmfCreate(NULL, 10);
+ for (pageno = 0; pageno < npage; pageno++) {
+ if ((pix1 = pixacompGetPix(pixac, pageno)) == NULL) {
+ L_ERROR("pix for page %d not found\n", procName, pageno);
+ continue;
+ }
+
+ /* Scale, binarize and deskew */
+ res = pixGetXRes(pix1);
+ if (res == 0 || res == 300 || res > 600) {
+ pix2 = pixClone(pix1);
+ } else {
+ factor = 300.0 / (l_float32)res;
+ if (factor > 3)
+ L_WARNING("resolution is very low\n", procName);
+ pix2 = pixScale(pix1, factor, factor);
+ }
+ pix3 = pixConvertTo1Adaptive(pix2);
+ pix4 = pixDeskew(pix3, 0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ if (!pix4) {
+ L_ERROR("pix for page %d not deskewed\n", procName, pageno);
+ continue;
+ }
+ pix1 = pixClone(pix4); /* rename */
+ pixDestroy(&pix4);
+
+ /* Find the stave sets at 4x reduction */
+ boxa1 = pixLocateStaveSets(pix1, pageno, pixadb);
+
+ /* Break each stave set into the separate staves (parts).
+ * A typical set will have more than one part, but if one of
+ * the parts is a keyboard, it will usually have two staves
+ * (also called a Grand Staff), composed of treble and
+ * bass staves. For example, a classical violin sonata
+ * could have a staff for the violin and two staves for
+ * the piano. We would set nparts == 2, and extract both
+ * of the piano staves as the piano part. */
+ nbox = boxaGetCount(boxa1);
+ lept_stderr("number of boxes in page %d: %d\n", pageno, nbox);
+ for (i = 0; i < nbox; i++, line++) {
+ snprintf(buf, sizeof(buf), "%d", line);
+ box1 = boxaGetBox(boxa1, i, L_COPY);
+ pix2 = pixClipRectangle(pix1, box1, NULL);
+ pix3 = pixMorphSequence(pix2, "d1.20 + o50.1 + o1.30", 0);
+ boxa2 = pixConnCompBB(pix3, 8);
+ boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL);
+ boxaRemoveVGaps(boxa3);
+ icount = boxaGetCount(boxa3);
+ if (icount < nparts)
+ L_WARNING("nparts requested = %d, but only found %d\n",
+ procName, nparts, icount);
+ for (j = 0; j < icount && j < nparts; j++) {
+ box2 = boxaGetBox(boxa3, j, L_COPY);
+ if (j == nparts - 1) /* extend the box to the bottom */
+ boxSetSideLocations(box2, -1, -1, -1,
+ pixGetHeight(pix1) - 1);
+ pix4 = pixClipRectangle(pix2, box2, NULL);
+ pix5 = pixAddTextlines(pix4, bmf, buf, 1, L_ADD_LEFT);
+ pixacompAddPix(pixaca[j], pix5, IFF_TIFF_G4);
+ boxDestroy(&box2);
+ pixDestroy(&pix4);
+ pixDestroy(&pix5);
+ }
+ boxaDestroy(&boxa2);
+ boxaDestroy(&boxa3);
+ boxDestroy(&box1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ boxaDestroy(&boxa1);
+ pixDestroy(&pix1);
+ }
+
+ /* Output separate pdfs for each part */
+ for (i = 0; i < nparts; i++) {
+ snprintf(buf, sizeof(buf), "%s-%d.pdf", outroot, i);
+ L_INFO("writing part %d: %s\n", procName, i, buf);
+ pixacompConvertToPdf(pixaca[i], 300, 1.0, L_G4_ENCODE, 0, NULL, buf);
+ pixacompDestroy(&pixaca[i]);
+ }
+ LEPT_FREE(pixaca);
+ bmfDestroy(&bmf);
+ return 0;
+}
+
+
+/*
+ * \brief pixLocateStaveSets()
+ *
+ * \param[in] pixs 1 bpp, 300 ppi, deskewed
+ * \param[in] pageno page number; used for debug output
+ * \param[in] pixadb [optional] debug pixa; can be NULL
+ * \return boxa containing the stave sets at full resolution
+ */
+static BOXA *
+pixLocateStaveSets(PIX *pixs,
+ l_int32 pageno,
+ PIXA *pixadb)
+{
+BOXA *boxa1, *boxa2, *boxa3, *boxa4;
+PIX *pix1, *pix2;
+
+ /* Find the stave sets at 4x reduction */
+ pix1 = pixMorphSequence(pixs, "r11", 0);
+ boxa1 = pixConnCompBB(pix1, 8);
+ boxa2 = boxaSelectByArea(boxa1, 15000, L_SELECT_IF_GT, NULL);
+ boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL);
+ if (pixadb) {
+ pix2 = pixConvertTo32(pix1);
+ pixRenderBoxaArb(pix2, boxa3, 2, 255, 0, 0);
+ pixaAddPix(pixadb, pix2, L_INSERT);
+ pixDisplay(pix2, 100 * pageno, 100);
+ }
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+
+ boxaRemoveVGaps(boxa3);
+ if (pixadb) {
+ pix2 = pixConvertTo32(pix1);
+ pixRenderBoxaArb(pix2, boxa3, 2, 0, 255, 0);
+ pixaAddPix(pixadb, pix2, L_INSERT);
+ pixDisplay(pix2, 100 * pageno, 600);
+ }
+ boxa4 = boxaTransform(boxa3, 0, 0, 4.0, 4.0); /* back to full res */
+ boxaDestroy(&boxa3);
+ pixDestroy(&pix1);
+ return boxa4;
+}
+
+
+/*
+ * \brief boxaRemoveVGaps()
+ *
+ * \param[in] boxa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The boxes in %boxa are aligned vertically. Move the horizontal
+ * edges vertically to remove the gaps between boxes.
+ * </pre>
+ */
+static l_ok
+boxaRemoveVGaps(BOXA *boxa)
+{
+l_int32 nbox, i, y1, h1, y2, h2, delta;
+
+ nbox = boxaGetCount(boxa);
+ for (i = 0; i < nbox - 1; i++) {
+ boxaGetBoxGeometry(boxa, i, NULL, &y1, NULL, &h1);
+ boxaGetBoxGeometry(boxa, i + 1, NULL, &y2, NULL, &h2);
+ delta = (y2 - y1 - h1) / 2;
+ boxaAdjustBoxSides(boxa, i, 0, 0, 0, delta);
+ boxaAdjustBoxSides(boxa, i + 1, 0, 0, -delta, 0);
+ }
+ boxaAdjustBoxSides(boxa, nbox - 1, 0, 0, 0, delta); /* bot of last */
+ return 0;
+}
diff --git a/leptonica/src/partition.c b/leptonica/src/partition.c
new file mode 100644
index 00000000..3d4c74b9
--- /dev/null
+++ b/leptonica/src/partition.c
@@ -0,0 +1,662 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file partition.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*! Partition element */
+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 DefaultMaxPops = 20000;
+
+
+#ifndef NO_CONSOLE_IO
+#define OUTPUT_HEAP_STATS 0
+#endif /* ~NO_CONSOLE_IO */
+
+/*------------------------------------------------------------------*
+ * Whitespace block extraction *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief boxaGetWhiteblocks()
+ *
+ * \param[in] boxas typ. a set of bounding boxes of fg components
+ * \param[in] box initial region; typically including all boxes
+ * in boxas; if null, it computes the region to
+ * include all boxes in boxas
+ * \param[in] 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
+ * \param[in] maxboxes max number of output whitespace boxes; e.g., 100
+ * \param[in] maxoverlap maximum fractional overlap of a box by any
+ * of the larger boxes; e.g., 0.2
+ * \param[in] maxperim maximum half-perimeter, in pixels, for which
+ * pivot is selected by proximity to box centroid;
+ * e.g., 200
+ * \param[in] fract fraction of box diagonal that is an acceptable
+ * distance from the box centroid to select
+ * the pivot; e.g., 0.2
+ * \param[in] maxpops max number of pops from the heap; use 0 as default
+ * \return boxa of sorted whitespace boxes, or NULL on error
+ *
+ * <pre>
+ * 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) It should be noted that the algorithm puts a large number
+ * of partels on the queue. Setting a limit of X partels to
+ * remove from the queue, one typically finds that there will be
+ * several times that number (say, 2X - 3X) left on the queue.
+ * For an efficient algorithm to find the largest white or
+ * or black rectangles, without permitting them to overlap,
+ * see pixFindLargeRectangles().
+ * (11) 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.
+ * </pre>
+ */
+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 = DefaultMaxPops;
+
+ 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);
+
+ npush = 1;
+ npop = 0;
+ boxad = boxaCreate(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) {
+ partelDestroy(&partel);
+ 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? */
+
+/* boxaWriteStderr(boxa4); */
+
+ boxaDestroy(&boxa4);
+ boxaDestroy(&boxa);
+ }
+
+#if OUTPUT_HEAP_STATS
+ lept_stderr("Heap statistics:\n");
+ lept_stderr(" Number of boxes pushed: %d\n", npush);
+ lept_stderr(" Number of boxes popped: %d\n", npop);
+ lept_stderr(" Number of boxes on heap: %d\n", lheapGetCount(lh));
+#endif /* OUTPUT_HEAP_STATS */
+
+ /* Clean up the heap */
+ while ((partel = (PARTEL *)lheapRemove(lh)) != NULL)
+ partelDestroy(&partel);
+ lheapDestroy(&lh, FALSE);
+
+ return boxad;
+}
+
+
+/*------------------------------------------------------------------*
+ * Helpers *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief partelCreate()
+ *
+ * \param[in] box region; inserts a copy
+ * \return partel, or NULL on error
+ */
+static PARTEL *
+partelCreate(BOX *box)
+{
+PARTEL *partel;
+
+ partel = (PARTEL *)LEPT_CALLOC(1, sizeof(PARTEL));
+ partel->box = boxCopy(box);
+ return partel;
+}
+
+
+/*!
+ * \brief partelDestroy()
+ *
+ * \param[in,out] ppartel contents 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;
+}
+
+
+/*!
+ * \brief partelSetSize()
+ *
+ * \param[in] partel
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief boxaGenerateSubboxes()
+ *
+ * \param[in] box region to be split into up to four overlapping
+ * subregions
+ * \param[in] boxa boxes of rectangles intersecting the box
+ * \param[in] maxperim maximum half-perimeter for which pivot
+ * is selected by proximity to box centroid
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief boxaSelectPivotBox()
+ *
+ * \param[in] box containing box; to be split by the pivot box
+ * \param[in] boxa boxes of rectangles, from which 1 is to be chosen
+ * \param[in] maxperim maximum half-perimeter for which pivot
+ * is selected by proximity to box centroid
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief boxCheckIfOverlapIsBig()
+ *
+ * \param[in] box to be tested
+ * \param[in] boxa of boxes already stored
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief boxaPruneSortedOnOverlap()
+ *
+ * \param[in] boxas sorted by size in decreasing order
+ * \param[in] maxoverlap maximum fractional overlap of a box by any
+ * of the larger boxes
+ * \return boxad pruned, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/pdfio1.c b/leptonica/src/pdfio1.c
new file mode 100644
index 00000000..24fc7c7f
--- /dev/null
+++ b/leptonica/src/pdfio1.c
@@ -0,0 +1,2255 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pdfio1.c
+ * <pre>
+ *
+ * Higher-level operations for generating pdf from images.
+ * Use poppler's pdftoppm or pdfimages to invert the process,
+ * extracting raster images from pdf.
+ *
+ * |=============================================================|
+ * | Important notes |
+ * |=============================================================|
+ * | Some of these functions require I/O libraries such as |
+ * | libtiff, libjpeg, libpng, libz and libopenjp2. If you do |
+ * | not have these libraries, some calls will fail. For |
+ * | example, if you do not have libopenjp2, you cannot write a |
+ * | pdf where transcoding is required to incorporate a |
+ * | jp2k image. |
+ * | |
+ * | You can manually deactivate all pdf writing by setting |
+ * | this in environ.h: |
+ * | \code |
+ * | #define USE_PDFIO 0 |
+ * | \endcode |
+ * | This will link the stub file 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 required. For jpeg and jp2k
+ * images, the bytes in each 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 (e.g., tiff-g4),
+ * 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 it into a multi-page pdf. The input can be
+ * a set of either single page pdf files or 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 evince, gv, 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 transcode them: 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 DefaultInputRes = 300;
+
+/*---------------------------------------------------------------------*
+ * Convert specified image files to pdf (one image file per page) *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief convertFilesToPdf()
+ *
+ * \param[in] dirname directory name containing images
+ * \param[in] substr [optional] substring filter on filenames;
+ * can be NULL
+ * \param[in] res input resolution of all images
+ * \param[in] scalefactor scaling factor applied to each image; > 0.0
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, L_JP2K_ENCODE or
+ * L_DEFAULT_ENCODE for default)
+ * \param[in] quality for jpeg: 1-100; 0 for default (75)
+ * for jp2k: 27-45; 0 for default (34)
+ * \param[in] title [optional] pdf title; if null, taken from
+ * the first image filename
+ * \param[in] fileout pdf file of all images
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 four 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief saConvertFilesToPdf()
+ *
+ * \param[in] sa string array of pathnames for images
+ * \param[in] res input resolution of all images
+ * \param[in] scalefactor scaling factor applied to each image; > 0.0
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, L_JP2K_ENCODE or
+ * L_DEFAULT_ENCODE for default)
+ * \param[in] quality for jpeg: 1-100; 0 for default (75)
+ * for jp2k: 27-45; 0 for default (34)
+ * \param[in] title [optional] pdf title; if null, taken from
+ * the first image filename
+ * \param[in] fileout pdf file of all images
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See convertFilesToPdf().
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief saConvertFilesToPdfData()
+ *
+ * \param[in] sa string array of pathnames for images
+ * \param[in] res input resolution of all images
+ * \param[in] scalefactor scaling factor applied to each image; > 0.0
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, L_JP2K_ENCODE or
+ * L_DEFAULT_ENCODE for default)
+ * \param[in] quality for jpeg: 1-100; 0 for default (75)
+ * for jp2k: 27-45; 0 for default (34)
+ * \param[in] title [optional] pdf title; if null, taken from
+ * the first image filename
+ * \param[out] pdata output pdf data (of all images
+ * \param[out] pnbytes size of output pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See convertFilesToPdf().
+ * </pre>
+ */
+l_ok
+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 != L_JPEG_ENCODE && type != L_G4_ENCODE &&
+ type != L_FLATE_ENCODE && type != L_JP2K_ENCODE) {
+ type = L_DEFAULT_ENCODE;
+ }
+
+ /* 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)) lept_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);
+ pixDestroy(&pixs);
+ scaledres = (l_int32)(res * scalefactor);
+
+ /* Select the encoding type */
+ if (type != L_DEFAULT_ENCODE) {
+ pagetype = type;
+ } else if (selectDefaultPdfEncoding(pix, &pagetype) != 0) {
+ pixDestroy(&pix);
+ 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);
+ if (ret) {
+ LEPT_FREE(imdata);
+ L_ERROR("pdf encoding failed for %s\n", procName, fname);
+ continue;
+ }
+ ba = l_byteaInitFromMem(imdata, imbytes);
+ 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 */
+ lept_stderr("\nconcatenating ... ");
+ ret = ptraConcatenatePdfToData(pa_data, NULL, pdata, pnbytes);
+ lept_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;
+}
+
+
+/*!
+ * \brief selectDefaultPdfEncoding()
+ *
+ * \param[in] pix
+ * \param[out] ptype L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+selectDefaultPdfEncoding(PIX *pix,
+ l_int32 *ptype)
+{
+l_int32 w, h, d, factor, ncolors;
+PIXCMAP *cmap;
+
+ PROCNAME("selectDefaultPdfEncoding");
+
+ if (!ptype)
+ return ERROR_INT("&type not defined", procName, 1);
+ *ptype = L_FLATE_ENCODE; /* default universal encoding */
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief convertUnscaledFilesToPdf()
+ *
+ * \param[in] dirname directory name containing images
+ * \param[in] substr [optional] substring filter on filenames; can be NULL
+ * \param[in] title [optional] pdf title; if null, taken from the first
+ * image filename
+ * \param[in] fileout pdf file of all images
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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) This is very fast for jpeg, jp2k and some png files, because
+ * the compressed data is wrapped up and concatenated. For tiffg4
+ * and other types of png, the images must be read and recompressed.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief saConvertUnscaledFilesToPdf()
+ *
+ * \param[in] sa string array of pathnames for images
+ * \param[in] title [optional] pdf title; if null, taken from the first
+ * image filename
+ * \param[in] fileout pdf file of all images
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See convertUnscaledFilesToPdf().
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief saConvertUnscaledFilesToPdfData()
+ *
+ * \param[in] sa string array of pathnames for image files
+ * \param[in] title [optional] pdf title; if null, taken from the first
+ * image filename
+ * \param[out] pdata output pdf data (of all images)
+ * \param[out] pnbytes size of output pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is very fast for jpeg, jp2k and some png files, because
+ * the compressed data is wrapped up and concatenated. For tiffg4
+ * and other types of png, the images must be read and recompressed.
+ * </pre>
+ */
+l_ok
+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)) lept_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 */
+ lept_stderr("\nconcatenating ... ");
+ ret = ptraConcatenatePdfToData(pa_data, NULL, pdata, pnbytes);
+ lept_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;
+}
+
+
+/*!
+ * \brief convertUnscaledToPdfData()
+ *
+ * \param[in] fname of image file in all formats
+ * \param[in] title [optional] pdf title; can be NULL
+ * \param[out] pdata output pdf data for image
+ * \param[out] pnbytes size of output pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is very fast for jpeg, jp2k and some png files, because
+ * the compressed data is wrapped up and concatenated. For tiffg4
+ * and other types of png, the images must be read and recompressed.
+ * </pre>
+ */
+l_ok
+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. Note that jpeg, jp2k and
+ * some png files are not 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) *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaConvertToPdf()
+ *
+ * \param[in] pixa containing images all at the same resolution
+ * \param[in] res override the resolution of each input image,
+ * in ppi; use 0 to respect the resolution
+ * embedded in the input images
+ * \param[in] scalefactor scaling factor applied to each image; > 0.0
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, L_JP2K_ENCODE, or
+ * L_DEFAULT_ENCODE for default)
+ * \param[in] quality for jpeg: 1-100; 0 for default (75)
+ * for jp2k: 27-45; 0 for default (34)
+ * \param[in] title [optional] pdf title
+ * \param[in] fileout pdf file of all images
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaConvertToPdfData()
+ *
+ * \param[in] pixa containing images all at the same resolution
+ * \param[in] res input resolution of all images
+ * \param[in] scalefactor scaling factor applied to each image; > 0.0
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, L_JP2K_ENCODE, or
+ * L_DEFAULT_ENCODE for default)
+ * \param[in] quality for jpeg: 1-100; 0 for default (75)
+ * for jp2k: 27-45; 0 for default (34)
+ * \param[in] title [optional] pdf title
+ * \param[out] pdata output pdf data of all images
+ * \param[out] pnbytes size of output pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixaConvertToPdf().
+ * </pre>
+ */
+l_ok
+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 != L_DEFAULT_ENCODE && type != L_JPEG_ENCODE &&
+ type != L_G4_ENCODE && type != L_FLATE_ENCODE &&
+ type != L_JP2K_ENCODE) {
+ L_WARNING("invalid compression type; using per-page default\n",
+ procName);
+ type = L_DEFAULT_ENCODE;
+ }
+
+ /* 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);
+
+ /* Select the encoding type */
+ 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) {
+ LEPT_FREE(imdata);
+ L_ERROR("pdf encoding failed for pix[%d]\n", procName, i);
+ continue;
+ }
+ ba = l_byteaInitFromMem(imdata, imbytes);
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief convertToPdf()
+ *
+ * \param[in] filein input image file -- any format
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, or L_JP2K_ENCODE)
+ * \param[in] quality for jpeg: 1-100; 0 for default (75)
+ * for jp2k: 27-45; 0 for default (34)
+ * \param[in] fileout output pdf file; only required on last
+ * image on page
+ * \param[in] 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
+ * \param[in] res override the resolution of the input image,
+ * in ppi; use 0 to respect the resolution
+ * embedded in the input images
+ * \param[in] title [optional] pdf title; if null, taken from filein
+ * \param[in,out] plpd ptr to lpd, which is created on the first
+ * invocation and returned until last image is
+ * processed, at which time it is destroyed
+ * \param[in] position in image sequence: L_FIRST_IMAGE, L_NEXT_IMAGE,
+ * L_LAST_IMAGE
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 (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;
+}
+
+
+/*!
+ * \brief convertImageDataToPdf()
+ *
+ * \param[in] imdata array of formatted image data; e.g., png, jpeg
+ * \param[in] size size of image data
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, or L_JP2K_ENCODE)
+ * \param[in] quality for jpeg: 1-100; 0 for default (75)
+ * for jp2k: 27-45; 0 for default (34)
+ * \param[in] fileout output pdf file; only required on last
+ * image on page
+ * \param[in] 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
+ * \param[in] res override the resolution of the input image,
+ * in ppi; use 0 to respect the resolution
+ * embedded in the input images
+ * \param[in] title [optional] pdf title
+ * \param[in,out] plpd ptr to lpd, which is created on the first
+ * invocation and returned until last image is
+ * processed, at which time it is destroyed
+ * \param[in] position in image sequence: L_FIRST_IMAGE, L_NEXT_IMAGE,
+ * L_LAST_IMAGE
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %res == 0 and the input resolution field is 0,
+ * this will use DefaultInputRes.
+ * (2) See comments in convertToPdf().
+ * </pre>
+ */
+l_ok
+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 (!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);
+ if (type != L_JPEG_ENCODE && type != L_G4_ENCODE &&
+ type != L_FLATE_ENCODE && type != L_JP2K_ENCODE) {
+ selectDefaultPdfEncoding(pix, &type);
+ }
+ ret = pixConvertToPdf(pix, type, quality, fileout, x, y, res,
+ title, plpd, position);
+ pixDestroy(&pix);
+ return ret;
+}
+
+
+/*!
+ * \brief convertToPdfData()
+ *
+ * \param[in] filein input image file -- any format
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, or L_JP2K_ENCODE)
+ * \param[in] quality for jpeg: 1-100; 0 for default (75)
+ * for jp2k: 27-45; 0 for default (34)
+ * \param[out] pdata pdf data in memory
+ * \param[out] pnbytes number of bytes in pdf data
+ * \param[in] 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
+ * \param[in] res override the resolution of the input image,
+ * in ppi; use 0 to respect the resolution
+ * embedded in the input images
+ * \param[in] title [optional] pdf title; if null, use filein
+ * \param[in,out] plpd ptr to lpd, which is created on the first
+ * invocation and returned until last image is
+ * processed, at which time it is destroyed
+ * \param[in] position in image sequence: L_FIRST_IMAGE, L_NEXT_IMAGE,
+ * L_LAST_IMAGE
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %res == 0 and the input resolution field is 0,
+ * this will use DefaultInputRes.
+ * (2) See comments in convertToPdf().
+ * </pre>
+ */
+l_ok
+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 ((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;
+}
+
+
+/*!
+ * \brief convertImageDataToPdfData()
+ *
+ * \param[in] imdata array of formatted image data; e.g., png, jpeg
+ * \param[in] size size of image data
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, or L_JP2K_ENCODE)
+ * \param[in] quality for jpeg: 1-100; 0 for default (75)
+ * for jp2k: 27-45; 0 for default (34)
+ * \param[out] pdata pdf data in memory
+ * \param[out] pnbytes number of bytes in pdf data
+ * \param[in] 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
+ * \param[in] res override the resolution of the input image,
+ * in ppi; use 0 to respect the resolution
+ * embedded in the input images
+ * \param[in] title [optional] pdf title
+ * \param[out] plpd ptr to lpd, which is created on the first
+ * invocation and returned until last image is
+ * processed, at which time it is destroyed
+ * \param[in] position in image sequence: L_FIRST_IMAGE, L_NEXT_IMAGE,
+ * L_LAST_IMAGE
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %res == 0 and the input resolution field is 0,
+ * this will use DefaultInputRes.
+ * (2) See comments in convertToPdf().
+ * </pre>
+ */
+l_ok
+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);
+ if (type != L_JPEG_ENCODE && type != L_G4_ENCODE &&
+ type != L_FLATE_ENCODE && type != L_JP2K_ENCODE) {
+ selectDefaultPdfEncoding(pix, &type);
+ }
+ ret = pixConvertToPdfData(pix, type, quality, pdata, pnbytes,
+ x, y, res, title, plpd, position);
+ pixDestroy(&pix);
+ return ret;
+}
+
+
+/*!
+ * \brief pixConvertToPdf()
+ *
+ * \param[in] pix
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, L_JP2K_ENCODE)
+ * \param[in] quality for jpeg: 1-100; 0 for default (75)
+ * for jp2k: 27-45; 0 for default (34)
+ * \param[in] fileout output pdf file; only required on last
+ * image on page
+ * \param[in] 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
+ * \param[in] res override the resolution of the input image,
+ * in ppi; use 0 to respect the resolution
+ * embedded in the input images
+ * \param[in] title [optional] pdf title
+ * \param[in,out] plpd ptr to lpd, which is created on the first
+ * invocation and returned until last image is
+ * processed, at which time it is destroyed
+ * \param[in] position in image sequence: L_FIRST_IMAGE, L_NEXT_IMAGE,
+ * L_LAST_IMAGE
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %res == 0 and the input resolution field is 0,
+ * this will use DefaultInputRes.
+ * (2) This only writes data to fileout if it is the last
+ * image to be written on the page.
+ * (3) See comments in convertToPdf().
+ * </pre>
+ */
+l_ok
+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 (!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)) {
+ LEPT_FREE(data);
+ 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;
+}
+
+
+/*!
+ * \brief pixWriteStreamPdf()
+ *
+ * \param[in] fp file stream opened for writing
+ * \param[in] pix all depths, cmap OK
+ * \param[in] res override the resolution of the input image, in ppi;
+ * use 0 to respect the resolution embedded in the input
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) {
+ LEPT_FREE(data);
+ 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;
+}
+
+
+/*!
+ * \brief pixWriteMemPdf()
+ *
+ * \param[out] pdata pdf as byte array
+ * \param[out] pnbytes number of bytes in pdf array
+ * \param[in] pix all depths, cmap OK
+ * \param[in] res override the resolution of the input image, in ppi;
+ * use 0 to respect the res embedded in the input
+ * \param[in] 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
+ *
+ * <pre>
+ * Notes:
+ * (1) This is the simplest interface for writing a single image
+ * with pdf encoding to memory. It uses G4 encoding for 1 bpp,
+ * and makes a guess whether to use JPEG or FLATE encoding for
+ * everything else.
+ * </pre>
+ */
+l_ok
+pixWriteMemPdf(l_uint8 **pdata,
+ size_t *pnbytes,
+ PIX *pix,
+ l_int32 res,
+ const char *title)
+{
+l_int32 ret, type;
+
+ 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);
+
+ selectDefaultPdfEncoding(pix, &type);
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief convertSegmentedFilesToPdf()
+ *
+ * \param[in] dirname directory name containing images
+ * \param[in] substr [optional] substring filter on filenames;
+ * can be NULL
+ * \param[in] res input resolution of all images
+ * \param[in] type compression type for non-image regions; the
+ * image regions are always compressed with
+ * L_JPEG_ENCODE
+ * \param[in] thresh used for converting gray --> 1 bpp with
+ * L_G4_ENCODE
+ * \param[in] baa [optional] boxaa of image regions
+ * \param[in] quality used for JPEG only; 0 for default (75)
+ * \param[in] scalefactor scaling factor applied to each image region
+ * \param[in] title [optional] pdf title; if null, taken from
+ * the first image filename
+ * \param[in] fileout pdf file of all images
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief convertNumberedMasksToBoxaa()
+ *
+ * \param[in] dirname directory name containing mask images
+ * \param[in] substr [optional] substring filter on filenames; can be NULL
+ * \param[in] numpre number of characters in name before number
+ * \param[in] numpost number of characters in name after number, up
+ * to a dot before an extension
+ * \return boxaa of mask regions, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief convertToPdfSegmented()
+ *
+ * \param[in] filein input image file -- any format
+ * \param[in] res input image resolution; typ. 300 ppi;
+ * use 0 for default
+ * \param[in] type compression type for non-image regions; image
+ * regions are always compressed with L_JPEG_ENCODE
+ * \param[in] thresh for converting gray --> 1 bpp with L_G4_ENCODE
+ * \param[in] boxa [optional] of image regions; can be null
+ * \param[in] quality used for jpeg image regions; 0 for default
+ * \param[in] scalefactor used for jpeg regions; must be <= 1.0
+ * \param[in] title [optional] pdf title; typically taken from the
+ * input file for the pix
+ * \param[in] fileout output pdf file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixConvertToPdfSegmented()
+ *
+ * \param[in] pixs any depth, cmap OK
+ * \param[in] res input image resolution; typ. 300 ppi;
+ * use 0 for default
+ * \param[in] type compression type for non-image regions; image
+ * regions are always compressed with L_JPEG_ENCODE
+ * \param[in] thresh for converting gray --> 1 bpp with L_G4_ENCODE
+ * \param[in] boxa [optional] of image regions; can be null
+ * \param[in] quality used for jpeg image regions; 0 for default
+ * \param[in] scalefactor used for jpeg regions; must be <= 1.0
+ * \param[in] title [optional] pdf title; typically taken from the
+ * input file for the pix
+ * \param[in] fileout output pdf file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See convertToPdfSegmented() for details.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief convertToPdfDataSegmented()
+ *
+ * \param[in] filein input image file -- any format
+ * \param[in] res input image resolution; typ. 300 ppi;
+ * use 0 for default
+ * \param[in] type compression type for non-image regions; image
+ * regions are always compressed with L_JPEG_ENCODE
+ * \param[in] thresh for converting gray --> 1 bpp with L_G4_ENCODE
+ * \param[in] boxa [optional] image regions; can be null
+ * \param[in] quality used for jpeg image regions; 0 for default
+ * \param[in] scalefactor used for jpeg regions; must be <= 1.0
+ * \param[in] title [optional] pdf title; if null, uses filein
+ * \param[out] pdata pdf data in memory
+ * \param[out] pnbytes number of bytes in pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixConvertToPdfDataSegmented()
+ *
+ * \param[in] pixs any depth, cmap OK
+ * \param[in] res input image resolution; typ. 300 ppi;
+ * use 0 for default
+ * \param[in] type compression type for non-image regions; image
+ * regions are always compressed with L_JPEG_ENCODE
+ * \param[in] thresh for converting gray --> 1 bpp with L_G4_ENCODE
+ * \param[in] boxa [optional] of image regions; can be null
+ * \param[in] quality used for jpeg image regions; 0 for default
+ * \param[in] scalefactor used for jpeg regions; must be <= 1.0
+ * \param[in] title [optional] pdf title; typically taken from the
+ * input file for the pix
+ * \param[out] pdata pdf data in memory
+ * \param[out] pnbytes number of bytes in pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See convertToPdfSegmented() for details.
+ * </pre>
+ */
+l_ok
+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 = DefaultInputRes;
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief concatenatePdf()
+ *
+ * \param[in] dirname directory name containing single-page pdf files
+ * \param[in] substr [optional] substring filter on filenames; can be NULL
+ * \param[in] fileout concatenated pdf file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief saConcatenatePdf()
+ *
+ * \param[in] sa string array of pathnames for single-page pdf files
+ * \param[in] fileout concatenated pdf file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This only works with leptonica-formatted single-page pdf files.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptraConcatenatePdf()
+ *
+ * \param[in] pa array of pdf strings, each for a single-page pdf file
+ * \param[in] fileout concatenated pdf file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This only works with leptonica-formatted single-page pdf files.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief concatenatePdfToData()
+ *
+ * \param[in] dirname directory name containing single-page pdf files
+ * \param[in] substr [optional] substring filter on filenames; can be NULL
+ * \param[out] pdata concatenated pdf data in memory
+ * \param[out] pnbytes number of bytes in pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief saConcatenatePdfToData()
+ *
+ * \param[in] sa string array of pathnames for single-page pdf files
+ * \param[out] pdata concatenated pdf data in memory
+ * \param[out] pnbytes number of bytes in pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This only works with leptonica-formatted single-page pdf files.
+ * </pre>
+ */
+l_ok
+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/leptonica/src/pdfio1stub.c b/leptonica/src/pdfio1stub.c
new file mode 100644
index 00000000..78cf1158
--- /dev/null
+++ b/leptonica/src/pdfio1stub.c
@@ -0,0 +1,309 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pdfio1stub.c
+ * <pre>
+ *
+ * Stubs for pdfio1.c functions
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if !USE_PDFIO /* defined in environ.h */
+/* --------------------------------------------*/
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok 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_ok 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_ok selectDefaultPdfEncoding(PIX *pix, l_int32 *ptype)
+{
+ return ERROR_INT("function not present", "selectDefaultPdfEncoding", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok convertUnscaledFilesToPdf(const char *dirname, const char *substr,
+ const char *title, const char *fileout)
+{
+ return ERROR_INT("function not present", "convertUnscaledFilesToPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok saConvertUnscaledFilesToPdf(SARRAY *sa, const char *title,
+ const char *fileout)
+{
+ return ERROR_INT("function not present", "saConvertUnscaledFilesToPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok saConvertUnscaledFilesToPdfData(SARRAY *sa, const char *title,
+ l_uint8 **pdata, size_t *pnbytes)
+{
+ return ERROR_INT("function not present",
+ "saConvertUnscaledFilesToPdfData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok convertUnscaledToPdfData(const char *fname, const char *title,
+ l_uint8 **pdata, size_t *pnbytes)
+{
+ return ERROR_INT("function not present", "convertUnscaledToPdfData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok 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_ok 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_ok 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_ok 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_ok 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_ok 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_ok pixWriteStreamPdf(FILE *fp, PIX *pix, l_int32 res, const char *title)
+{
+ return ERROR_INT("function not present", "pixWriteStreamPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWriteMemPdf(l_uint8 **pdata, size_t *pnbytes, PIX *pix,
+ l_int32 res, const char *title)
+{
+ return ERROR_INT("function not present", "pixWriteMemPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok 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_ok 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_ok 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_ok 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_ok concatenatePdf(const char *dirname, const char *substr,
+ const char *fileout)
+{
+ return ERROR_INT("function not present", "concatenatePdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok saConcatenatePdf(SARRAY *sa, const char *fileout)
+{
+ return ERROR_INT("function not present", "saConcatenatePdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok ptraConcatenatePdf(L_PTRA *pa, const char *fileout)
+{
+ return ERROR_INT("function not present", "ptraConcatenatePdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok concatenatePdfToData(const char *dirname, const char *substr,
+ l_uint8 **pdata, size_t *pnbytes)
+{
+ return ERROR_INT("function not present", "concatenatePdfToData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok saConcatenatePdfToData(SARRAY *sa, l_uint8 **pdata, size_t *pnbytes)
+{
+ return ERROR_INT("function not present", "saConcatenatePdfToData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+/* --------------------------------------------*/
+#endif /* !USE_PDFIO */
+/* --------------------------------------------*/
diff --git a/leptonica/src/pdfio2.c b/leptonica/src/pdfio2.c
new file mode 100644
index 00000000..2f8629d1
--- /dev/null
+++ b/leptonica/src/pdfio2.c
@@ -0,0 +1,2608 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pdfio2.c
+ * <pre>
+ *
+ * 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()
+ *
+ * Convert tiff multipage to pdf file
+ * l_int32 convertTiffMultipageToPdf()
+ *
+ * Low-level CID-based operations
+ *
+ * Without transcoding
+ * l_int32 l_generateCIDataForPdf()
+ * L_COMP_DATA *l_generateFlateDataPdf()
+ * L_COMP_DATA *l_generateJpegData()
+ * L_COMP_DATA *l_generateJpegDataMem()
+ * static L_COMP_DATA *l_generateJp2kData()
+ *
+ * With transcoding
+ * l_int32 l_generateCIData()
+ * l_int32 pixGenerateCIData()
+ * L_COMP_DATA *l_generateFlateData()
+ * static L_COMP_DATA *pixGenerateFlateData()
+ * static L_COMP_DATA *pixGenerateJpegData()
+ * static L_COMP_DATA *pixGenerateJp2kData()
+ * 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 char *generateEscapeString()
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 DefaultInputRes = 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 *pixGenerateJp2kData(PIX *pixs, 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 char *generateEscapeString(const char *str);
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertToPdfData()
+ *
+ * \param[in] pix all depths; cmap OK
+ * \param[in] type L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE,
+ * L_JP2K_ENCODE
+ * \param[in] quality for jpeg: 1-100; 0 for default (75)
+ * for jp2k: 27-45; 0 for default (34)
+ * \param[out] pdata pdf array
+ * \param[out] pnbytes number of bytes in pdf array
+ * \param[in] 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)
+ * \param[in] res override the resolution of the input image, in ppi;
+ * use 0 to respect resolution embedded in the input
+ * \param[in] title [optional] pdf title; can be null
+ * \param[in,out] plpd ptr to lpd; created on the first invocation and
+ * returned until last image is processed
+ * \param[in] position in image sequence: L_FIRST_IMAGE, L_NEXT_IMAGE,
+ * L_LAST_IMAGE
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %res == 0 and the input resolution field is 0,
+ * this will use DefaultInputRes.
+ * (2) This only writes %data if it is the last image to be
+ * written on the page.
+ * (3) See comments in convertToPdf().
+ * </pre>
+ */
+l_ok
+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 (type != L_JPEG_ENCODE && type != L_G4_ENCODE &&
+ type != L_FLATE_ENCODE && type != L_JP2K_ENCODE) {
+ selectDefaultPdfEncoding(pix, &type);
+ }
+ 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 = DefaultInputRes;
+ }
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptraConcatenatePdfToData()
+ *
+ * \param[in] pa_data ptra array of pdf strings, each for a
+ * single-page pdf file
+ * \param[in] sa [optional] string array of pathnames for
+ * input pdf files; can be null
+ * \param[out] pdata concatenated pdf data in memory
+ * \param[out] pnbytes number of bytes in pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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_dnaDiffAdjValues(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
+ lept_stderr("******** object mapper **********");
+ numaaWriteStream(stderr, naa_objs);
+
+ lept_stderr("******** Page object numbers ***********");
+ numaWriteStderr(napage);
+
+ lept_stderr("******** Pages object ***********\n");
+ lept_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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Convert tiff multipage to pdf file *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief convertTiffMultipageToPdf()
+ *
+ * \param[in] filein (tiff)
+ * \param[in] fileout (pdf)
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) A multipage tiff file can also be converted to PS, using
+ * convertTiffMultipageToPS()
+ * </pre>
+ */
+l_ok
+convertTiffMultipageToPdf(const char *filein,
+ const char *fileout)
+{
+l_int32 istiff;
+PIXA *pixa;
+FILE *fp;
+
+ PROCNAME("convertTiffMultipageToPdf");
+
+ if ((fp = fopenReadStream(filein)) == NULL)
+ return ERROR_INT("file not found", procName, 1);
+ istiff = fileFormatIsTiff(fp);
+ fclose(fp);
+ if (!istiff)
+ return ERROR_INT("file not tiff format", procName, 1);
+
+ pixa = pixaReadMultipageTiff(filein);
+ pixaConvertToPdf(pixa, 0, 1.0, 0, 0, "weasel2", fileout);
+ pixaDestroy(&pixa);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Low-level CID-based operations *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_generateCIDataForPdf()
+ *
+ * \param[in] fname [optional] can be null
+ * \param[in] pix [optional] can be null
+ * \param[in] quality for jpeg if transcoded: 1-100; 0 for default (75)
+ * for jp2k if transcoded: 27-45; 0 for default (34)
+ * \param[out] pcid compressed data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) You must set either filename or pix.
+ * (2) Given an image file and optionally a pix raster of that data,
+ * this provides a CID that is compatible with PDF, preferably
+ * without transcoding.
+ * (3) The pix is included for efficiency, in case transcoding
+ * is required and the pix is available to the caller.
+ * (4) We don't try to open files named "stdin" or "-" for Tesseract
+ * compatibility reasons. We may remove this restriction
+ * in the future.
+ * </pre>
+ */
+l_ok
+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 = cid = NULL;
+ if (!fname && !pix)
+ return ERROR_INT("neither fname nor pix are defined", procName, 1);
+
+ /* If a compressed file is given that is not 'stdin', see if we
+ * can generate the pdf output without transcoding. */
+ if (fname && strcmp(fname, "-") != 0 && strcmp(fname, "stdin") != 0) {
+ 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) {
+ cid = l_generateFlateDataPdf(fname, pix);
+ }
+
+ }
+
+ /* Otherwise, use the pix to generate the pdf output */
+ if (!cid) {
+ if (!pix)
+ pixt = pixRead(fname);
+ else
+ pixt = pixClone(pix);
+ if (!pixt)
+ return ERROR_INT("pixt not made", procName, 1);
+ if (selectDefaultPdfEncoding(pixt, &type)) {
+ pixDestroy(&pixt);
+ return 1;
+ }
+ pixGenerateCIData(pixt, type, quality, 0, &cid);
+ pixDestroy(&pixt);
+ }
+ if (!cid) {
+ L_ERROR("totally kerflummoxed\n", procName);
+ return 1;
+ }
+ *pcid = cid;
+ return 0;
+}
+
+
+/*!
+ * \brief l_generateFlateDataPdf()
+ *
+ * \param[in] fname preferably png
+ * \param[in] pixs [optional] can be null
+ * \return cid containing png data, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+ bps = 0; /* initialize to a nonsense value */
+ if (format == IFF_PNG) {
+ isPngInterlaced(fname, &interlaced);
+ if (readHeaderPng(fname, NULL, NULL, &bps, &spp, NULL))
+ return (L_COMP_DATA *)ERROR_PTR("bad png input", procName, NULL);
+ }
+
+ /* PDF is capable of inlining some types of PNG files, but not all
+ of them. We need to transcode anything with interlacing, an
+ alpha channel, or 1 bpp (which would otherwise be photo-inverted).
+
+ Note: 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. Because of the conversion, readHeaderPng() gives
+ spp = 2, whereas pixGetSpp() gives spp = 4 on the converted pix. */
+ if (format != IFF_PNG ||
+ (format == IFF_PNG && (interlaced || bps == 1 || spp == 4 || spp == 2)))
+ { /* lgtm+ analyzer needed the logic expanded */
+ 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) {
+ LEPT_FREE(pngcomp);
+ 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 (n >= nbytespng - i) { /* "n + i" can overflow */
+ LEPT_FREE(pngcomp);
+ LEPT_FREE(datacomp);
+ pixcmapDestroy(&cmap);
+ L_ERROR("invalid png: i = %d, n = %d, nbytes = %zu\n", procName,
+ i, n, nbytespng);
+ return NULL;
+ }
+
+ /* Is it a data chunk? */
+ if (memcmp(pngcomp + i - 4, "IDAT", 4) == 0) {
+ memcpy(datacomp + nbytescomp, pngcomp + i, n);
+ nbytescomp += n;
+ }
+
+ /* Is it a palette chunk? */
+ if (cmapflag && !cmap &&
+ memcmp(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;
+}
+
+
+/*!
+ * \brief l_generateJpegData()
+ *
+ * \param[in] fname of jpeg file
+ * \param[in] ascii85flag 0 for jpeg; 1 for ascii85-encoded jpeg
+ * \return cid containing jpeg data, or NULL on error
+ *
+ * <pre>
+ * 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)
+ * (2) Do not free the data. l_generateJpegDataMem() will free
+ * the data if the data is invalid, or if it does not use
+ * ascii encoding.
+ * </pre>
+ */
+L_COMP_DATA *
+l_generateJpegData(const char *fname,
+ l_int32 ascii85flag)
+{
+l_uint8 *data = NULL;
+size_t nbytes;
+
+ 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 ((data = l_binaryRead(fname, &nbytes)) == NULL)
+ return (L_COMP_DATA *)ERROR_PTR("data not extracted", procName, NULL);
+
+ return l_generateJpegDataMem(data, nbytes, ascii85flag);
+}
+
+
+/*!
+ * \brief l_generateJpegDataMem()
+ *
+ * \param[in] data of jpeg file
+ * \param[in] nbytes of jpeg file
+ * \param[in] ascii85flag 0 for jpeg; 1 for ascii85-encoded jpeg
+ * \return cid containing jpeg data, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See l_generateJpegData().
+ * </pre>
+ */
+L_COMP_DATA *
+l_generateJpegDataMem(l_uint8 *data,
+ size_t nbytes,
+ l_int32 ascii85flag)
+{
+char *data85 = NULL; /* ascii85 encoded jpeg compressed file */
+l_int32 w, h, xres, yres, bps, spp;
+size_t nbytes85;
+L_COMP_DATA *cid;
+
+ PROCNAME("l_generateJpegDataMem");
+
+ if (!data)
+ return (L_COMP_DATA *)ERROR_PTR("data not defined", procName, NULL);
+
+ /* Read the metadata */
+ if (readHeaderMemJpeg(data, nbytes, &w, &h, &spp, NULL, NULL)) {
+ LEPT_FREE(data);
+ return (L_COMP_DATA *)ERROR_PTR("bad jpeg metadata", procName, NULL);
+ }
+ bps = 8;
+ readResolutionMemJpeg(data, nbytes, &xres, &yres);
+
+ /* Optionally, encode the compressed data */
+ if (ascii85flag == 1) {
+ data85 = encodeAscii85(data, nbytes, &nbytes85);
+ LEPT_FREE(data);
+ 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 (ascii85flag == 0) {
+ cid->datacomp = data;
+ } else { /* ascii85 */
+ cid->data85 = data85;
+ cid->nbytes85 = nbytes85;
+ }
+ cid->type = L_JPEG_ENCODE;
+ cid->nbytescomp = nbytes;
+ cid->w = w;
+ cid->h = h;
+ cid->bps = bps;
+ cid->spp = spp;
+ cid->res = xres;
+ return cid;
+}
+
+
+/*!
+ * \brief l_generateJp2kData()
+ *
+ * \param[in] fname of jp2k file
+ * \return cid containing jp2k data, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is only called after the file is verified to be jp2k.
+ * </pre>
+ */
+static L_COMP_DATA *
+l_generateJp2kData(const char *fname)
+{
+l_int32 w, h, bps, spp, xres, yres;
+size_t nbytes;
+L_COMP_DATA *cid;
+FILE *fp;
+
+ PROCNAME("l_generateJp2kData");
+
+ if (!fname)
+ return (L_COMP_DATA *)ERROR_PTR("fname not defined", procName, NULL);
+
+ if (readHeaderJp2k(fname, &w, &h, &bps, &spp))
+ return (L_COMP_DATA *)ERROR_PTR("bad jp2k metadata", 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) {
+ l_CIDataDestroy(&cid);
+ return (L_COMP_DATA *)ERROR_PTR("data not extracted", procName, NULL);
+ }
+
+ xres = yres = 0;
+ if ((fp = fopenReadStream(fname)) != NULL) {
+ fgetJp2kResolution(fp, &xres, &yres);
+ fclose(fp);
+ }
+ cid->type = L_JP2K_ENCODE;
+ cid->nbytescomp = nbytes;
+ cid->w = w;
+ cid->h = h;
+ cid->bps = bps;
+ cid->spp = spp;
+ cid->res = xres;
+ return cid;
+}
+
+
+/*!
+ * \brief l_generateCIData()
+ *
+ * \param[in] fname
+ * \param[in] type L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE,
+ * L_JP2K_ENCODE
+ * \param[in] quality for jpeg if transcoded: 1-100; 0 for default (75)
+ * for jp2k if transcoded: 27-45; 0 for default (34)
+ * \param[in] ascii85 0 for binary; 1 for ascii85-encoded
+ * \param[out] pcid compressed data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 = pixGenerateJp2kData(pix, quality);
+ pixDestroy(&pix);
+ }
+ if (!cid)
+ return ERROR_INT("jp2k 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;
+}
+
+
+/*!
+ * \brief pixGenerateCIData()
+ *
+ * \param[in] pixs 8 or 32 bpp, no colormap
+ * \param[in] type L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE or
+ * L_JP2K_ENCODE
+ * \param[in] quality for jpeg if transcoded: 1-100; 0 for default (75)
+ * for jp2k if transcoded: 27-45; 0 for default (34)
+ * \param[in] ascii85 0 for binary; 1 for ascii85-encoded
+ * \param[out] pcid compressed data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Set ascii85:
+ * ~ 0 for binary data (not permitted in PostScript)
+ * ~ 1 for ascii85 (5 for 4) encoded binary data
+ * </pre>
+ */
+l_ok
+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 && type != L_JP2K_ENCODE) {
+ selectDefaultPdfEncoding(pixs, &type);
+ }
+ if (ascii85 != 0 && ascii85 != 1)
+ return ERROR_INT("invalid ascii85", procName, 1);
+
+ /* Conditionally modify the encoding type if libz is
+ * available and the requested library is missing. */
+#if defined(HAVE_LIBZ)
+# if !defined(HAVE_LIBJPEG)
+ if (type == L_JPEG_ENCODE) {
+ L_WARNING("no libjpeg; using flate encoding\n", procName);
+ type = L_FLATE_ENCODE;
+ }
+# endif /* !defined(HAVE_LIBJPEG) */
+# if !defined(HAVE_LIBJP2K)
+ if (type == L_JP2K_ENCODE) {
+ L_WARNING("no libjp2k; using flate encoding\n", procName);
+ type = L_FLATE_ENCODE;
+ }
+# endif /* !defined(HAVE_LIBJP2K) */
+# if !defined(HAVE_LIBTIFF)
+ if (type == L_G4_ENCODE) {
+ L_WARNING("no libtiff; using flate encoding\n", procName);
+ type = L_FLATE_ENCODE;
+ }
+# endif /* !defined(HAVE_LIBTIFF) */
+#endif /* defined(HAVE_LIBZ) */
+
+ /* 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 || 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 ((*pcid = pixGenerateJpegData(pixs, ascii85, quality)) == NULL)
+ return ERROR_INT("jpeg data not made", procName, 1);
+ } else if (type == L_JP2K_ENCODE) {
+ if ((*pcid = pixGenerateJp2kData(pixs, quality)) == NULL)
+ return ERROR_INT("jp2k 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 { /* type == L_FLATE_ENCODE */
+ if ((*pcid = pixGenerateFlateData(pixs, ascii85)) == NULL)
+ return ERROR_INT("flate data not made", procName, 1);
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief l_generateFlateData()
+ *
+ * \param[in] fname
+ * \param[in] ascii85flag 0 for gzipped; 1 for ascii85-encoded gzipped
+ * \return cid flate compressed image data, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGenerateFlateData()
+ *
+ * \param[in] pixs
+ * \param[in] ascii85flag 0 for gzipped; 1 for ascii85-encoded gzipped
+ * \return cid flate compressed image data, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If called with an RGBA pix (spp == 4), the alpha channel
+ * will be removed, projecting a white backgrouond through
+ * any transparency.
+ * (2) If called with a colormapped pix, any transparency in the
+ * alpha component in the colormap will be ignored, as it is
+ * for all leptonica operations on colormapped pix.
+ * </pre>
+ */
+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;
+size_t ncmapbytes85 = 0;
+size_t 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 if (d == 32 && pixGetSpp(pixs) == 4) { /* remove alpha */
+ pixt = pixAlphaBlendUniform(pixs, 0xffffff00);
+ } else {
+ pixt = pixClone(pixs);
+ }
+ spp = (d == 32) ? 3 : 1;
+ 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) {
+ pixDestroy(&pixt);
+ 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);
+ LEPT_FREE(data);
+ if (!datacomp) {
+ LEPT_FREE(cmapdata85);
+ LEPT_FREE(cmapdatahex);
+ return (L_COMP_DATA *)ERROR_PTR("datacomp not made", procName, NULL);
+ }
+
+ /* Optionally, encode the compressed data */
+ if (ascii85flag == 1) {
+ data85 = encodeAscii85(datacomp, nbytescomp, &nbytes85);
+ LEPT_FREE(datacomp);
+ if (!data85) {
+ LEPT_FREE(cmapdata85);
+ LEPT_FREE(cmapdatahex);
+ 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 (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;
+}
+
+
+/*!
+ * \brief pixGenerateJpegData()
+ *
+ * \param[in] pixs 8 or 32 bpp, no colormap
+ * \param[in] ascii85flag 0 for jpeg; 1 for ascii85-encoded jpeg
+ * \param[in] quality 0 for default, which is 75
+ * \return cid jpeg compressed data, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Set ascii85flag:
+ * ~ 0 for binary data (not permitted in PostScript)
+ * ~ 1 for ascii85 (5 for 4) encoded binary data
+ * </pre>
+ */
+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 */
+ fname = l_makeTempFilename();
+ if (pixWriteJpeg(fname, pixs, quality, 0)) {
+ LEPT_FREE(fname);
+ return NULL;
+ }
+
+ /* Generate the data */
+ cid = l_generateJpegData(fname, ascii85flag);
+ if (lept_rmfile(fname) != 0)
+ L_ERROR("temp file %s was not deleted\n", procName, fname);
+ LEPT_FREE(fname);
+ return cid;
+}
+
+
+/*!
+ * \brief pixGenerateJp2kData()
+ *
+ * \param[in] pixs 8 or 32 bpp, no colormap
+ * \param[in] quality 0 for default, which is 34
+ * \return cid jp2k compressed data, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The quality can be set between 27 (very poor) and 45
+ * (nearly perfect). Use 0 for default (34). Use 100 for lossless,
+ * but this is very expensive and not recommended.
+ * </pre>
+ */
+static L_COMP_DATA *
+pixGenerateJp2kData(PIX *pixs,
+ l_int32 quality)
+{
+l_int32 d;
+char *fname;
+L_COMP_DATA *cid;
+
+ PROCNAME("pixGenerateJp2kData");
+
+ 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 jp2k file */
+ fname = l_makeTempFilename();
+ if (pixWriteJp2k(fname, pixs, quality, 5, 0, 0)) {
+ LEPT_FREE(fname);
+ return NULL;
+ }
+
+ /* Generate the data */
+ cid = l_generateJp2kData(fname);
+ if (lept_rmfile(fname) != 0)
+ L_ERROR("temp file %s was not deleted\n", procName, fname);
+ LEPT_FREE(fname);
+ return cid;
+}
+
+
+/*!
+ * \brief pixGenerateG4Data()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] ascii85flag 0 for gzipped; 1 for ascii85-encoded gzipped
+ * \return cid g4 compressed image data, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Set ascii85flag:
+ * ~ 0 for binary data (not permitted in PostScript)
+ * ~ 1 for ascii85 (5 for 4) encoded binary data
+ * </pre>
+ */
+static L_COMP_DATA *
+pixGenerateG4Data(PIX *pixs,
+ l_int32 ascii85flag)
+{
+char *fname;
+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 */
+ fname = l_makeTempFilename();
+ if (pixWrite(fname, pixs, IFF_TIFF_G4)) {
+ LEPT_FREE(fname);
+ return NULL;
+ }
+
+ cid = l_generateG4Data(fname, ascii85flag);
+ if (lept_rmfile(fname) != 0)
+ L_ERROR("temp file %s was not deleted\n", procName, fname);
+ LEPT_FREE(fname);
+ return cid;
+}
+
+
+/*!
+ * \brief l_generateG4Data()
+ *
+ * \param[in] fname of g4 compressed file
+ * \param[in] ascii85flag 0 for g4 compressed; 1 for ascii85-encoded g4
+ * \return cid g4 compressed image data, or NULL on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+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 */
+size_t nbytes85, nbytescomp;
+L_COMP_DATA *cid;
+FILE *fp;
+
+ PROCNAME("l_generateG4Data");
+
+ if (!fname)
+ return (L_COMP_DATA *)ERROR_PTR("fname not defined", 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);
+
+ /* 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);
+ }
+
+ /* 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 (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;
+}
+
+
+/*!
+ * \brief cidConvertToPdfData()
+ *
+ * \param[in] cid compressed image data
+ * \param[in] title [optional] pdf title; can be NULL
+ * \param[out] pdata output pdf data for image
+ * \param[out] pnbytes size of output pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Caller must not destroy the cid. It is absorbed in the
+ * lpd and destroyed by this function.
+ * </pre>
+ */
+l_ok
+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 = DefaultInputRes;
+ 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;
+}
+
+
+/*!
+ * \brief l_CIDataDestroy()
+ *
+ * \param[in,out] pcid 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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Helper functions for generating the output pdf string *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_generatePdf()
+ *
+ * \param[out] pdata pdf array
+ * \param[out] pnbytes number of bytes in pdf array
+ * \param[in] lpd all the required input image data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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;
+
+ PROCNAME("generateFixedStringsPdf");
+
+ /* 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, "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, 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, buf, L_COPY);
+ if (lpd->title) {
+ char *hexstr;
+ if ((hexstr = generateEscapeString(lpd->title)) != NULL) {
+ snprintf(buf, sizeof(buf), "/Title %s\n", hexstr);
+ sarrayAddString(sa, buf, L_COPY);
+ } else {
+ L_ERROR("title string is not ascii\n", procName);
+ }
+ LEPT_FREE(hexstr);
+ }
+ sarrayAddString(sa, ">>\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");
+}
+
+
+/*!
+ * \brief generateEscapeString()
+ *
+ * \param[in] str input string
+ * \return hex escape string, or null on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If the input string is not ascii, returns null.
+ * (2) This takes an input ascii string and generates a hex
+ * ascii output string with 4 bytes out for each byte in.
+ * The feff code at the beginning tells the pdf interpreter
+ * that the data is to be interpreted as big-endian, 4 bytes
+ * at a time. For ascii, the first two bytes are 0 and the
+ * last two bytes are less than 0x80.
+ * </pre>
+ */
+static char *
+generateEscapeString(const char *str)
+{
+char smallbuf[8];
+char *buffer;
+l_int32 i, nchar, buflen;
+
+ PROCNAME("generateEscapeString");
+
+ if (!str)
+ return (char *)ERROR_PTR("str not defined", procName, NULL);
+ nchar = strlen(str);
+ for (i = 0; i < nchar; i++) {
+ if (str[i] < 0)
+ return (char *)ERROR_PTR("str not all ascii", procName, NULL);
+ }
+
+ buflen = 4 * nchar + 10;
+ buffer = (char *)LEPT_CALLOC(buflen, sizeof(char));
+ stringCat(buffer, buflen, "<feff");
+ for (i = 0; i < nchar; i++) {
+ snprintf(smallbuf, sizeof(smallbuf), "%04x", str[i]);
+ stringCat(buffer, buflen, smallbuf);
+ }
+ stringCat(buffer, buflen, ">");
+ return buffer;
+}
+
+
+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);
+ }
+}
+
+
+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);
+ }
+ xstr = sarrayToString(sa, 0);
+ sarrayDestroy(&sa);
+ if (!xstr) {
+ LEPT_FREE(buf);
+ return ERROR_INT("xstr not made", procName, 1);
+ }
+
+ 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);
+ }
+ cstr = sarrayToString(sa, 0);
+ sarrayDestroy(&sa);
+ if (!cstr) {
+ LEPT_FREE(buf);
+ return ERROR_INT("cstr not made", procName, 1);
+ }
+
+ 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, *photometry;
+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");
+ /* Note: the reversal is deliberate */
+ photometry = (cid->minisblack) ? stringNew("true")
+ : stringNew("false");
+ snprintf(buff, sizeof(buff),
+ "/Filter /CCITTFaxDecode\n"
+ "/DecodeParms\n"
+ "<<\n"
+ "/BlackIs1 %s\n"
+ "/K -1\n"
+ "/Columns %d\n"
+ ">>", photometry, cid->w);
+ fstr = stringNew(buff);
+ LEPT_FREE(photometry);
+ } 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 if (cid->spp == 4) /* pdf supports cmyk */
+ cstr = stringNew("/ColorSpace /DeviceCMYK");
+ else
+ L_ERROR("in jpeg: spp != 1, 3 or 4\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 %zu\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, 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, 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, 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, buf, L_COPY);
+ outstr = sarrayToString(sa, 0);
+ sarrayDestroy(&sa);
+ return outstr;
+}
+
+
+/*!
+ * \brief generateOutputDataPdf()
+ *
+ * \param[out] pdata pdf data array
+ * \param[out] pnbytes size of pdf data array
+ * \param[in] lpd input data used to make pdf
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Only called from l_generatePdf(). On error, no data is returned.
+ * </pre>
+ */
+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(data, lpd->id, sizes[0]);
+ memcpy(data + locs[1], lpd->obj1, sizes[1]);
+ memcpy(data + locs[2], lpd->obj2, sizes[2]);
+ memcpy(data + locs[3], lpd->obj3, sizes[3]);
+ memcpy(data + locs[4], lpd->obj4, sizes[4]);
+ memcpy(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) { /* should not happen */
+ LEPT_FREE(sizes);
+ LEPT_FREE(locs);
+ return ERROR_INT("cid not found", procName, 1);
+ }
+ str = sarrayGetString(lpd->saprex, i, L_NOCOPY);
+ len = strlen(str);
+ memcpy(data + locs[6 + i], str, len);
+ memcpy(data + locs[6 + i] + len,
+ cid->datacomp, cid->nbytescomp);
+ memcpy(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(data + locs[6 + nimages + i], str, strlen(str));
+ }
+
+ /* And finally the trailer */
+ memcpy(data + lpd->xrefloc, lpd->trailer, strlen(lpd->trailer));
+ LEPT_FREE(sizes);
+ LEPT_FREE(locs);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Helper functions for generating multipage pdf output *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief parseTrailerPdf()
+ *
+ * \param[in] bas lba of a pdf file
+ * \param[out] pda 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 (memcmp(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) {
+ sarrayDestroy(&sa);
+ 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
+ lept_stderr("************** Trailer string ************\n");
+ lept_stderr("xrefloc = %d", xrefloc);
+ sarrayWriteStream(stderr, sa);
+
+ lept_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;
+}
+
+
+/*!
+ * \brief substituteObjectNumbers()
+ *
+ * \param[in] bas lba of a pdf object
+ * \param[in] na_objs object number mapping array
+ * \return bad lba of rewritten pdf for the object
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, nobjs, objin, objout, found;
+l_int32 *objs, *matches;
+size_t size;
+L_BYTEA *bad;
+L_DNA *da_match;
+
+ PROCNAME("substituteObjectNumbers");
+ if (!bas)
+ return (L_BYTEA *)ERROR_PTR("bas not defined", procName, NULL);
+ if (!na_objs)
+ return (L_BYTEA *)ERROR_PTR("na_objs not defined", procName, NULL);
+
+ datas = l_byteaGetData(bas, &size);
+ bad = l_byteaCreate(100);
+ objs = numaGetIArray(na_objs); /* object number mapper */
+ nobjs = numaGetCount(na_objs); /* use for sanity checking */
+
+ /* Substitute the object number on the first line */
+ sscanf((char *)datas, "%d", &objin);
+ if (objin < 0 || objin >= nobjs) {
+ L_ERROR("index %d into array of size %d\n", procName, objin, nobjs);
+ LEPT_FREE(objs);
+ return bad;
+ }
+ 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);
+ if (objin < 0 || objin >= nobjs) {
+ L_ERROR("index %d into array of size %d\n", procName, objin, nobjs);
+ LEPT_FREE(objs);
+ LEPT_FREE(matches);
+ l_dnaDestroy(&da_match);
+ return bad;
+ }
+ 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;
+}
+
+
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_pdfSetG4ImageMask()
+ *
+ * \param[in] flag 1 for writing g4 data as fg only through a mask;
+ * 0 for writing fg and bg
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+void
+l_pdfSetG4ImageMask(l_int32 flag)
+{
+ var_WRITE_G4_IMAGE_MASK = flag;
+}
+
+
+/*!
+ * \brief l_pdfSetDateAndVersion()
+ *
+ * \param[in] flag 1 for writing date/time and leptonica version;
+ * 0 for omitting this from the metadata
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) The default is for writing this data. For regression tests
+ * that compare output against golden files, it is useful to omit.
+ * </pre>
+ */
+void
+l_pdfSetDateAndVersion(l_int32 flag)
+{
+ var_WRITE_DATE_AND_VERSION = flag;
+}
+
+/* --------------------------------------------*/
+#endif /* USE_PDFIO */
+/* --------------------------------------------*/
diff --git a/leptonica/src/pdfio2stub.c b/leptonica/src/pdfio2stub.c
new file mode 100644
index 00000000..cb297b12
--- /dev/null
+++ b/leptonica/src/pdfio2stub.c
@@ -0,0 +1,172 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pdfio2stub.c
+ * <pre>
+ *
+ * Stubs for pdfio2.c functions
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if !USE_PDFIO /* defined in environ.h */
+/* --------------------------------------------*/
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok ptraConcatenatePdfToData(L_PTRA *pa_data, SARRAY *sa,
+ l_uint8 **pdata, size_t *pnbytes)
+{
+ return ERROR_INT("function not present", "ptraConcatenatePdfToData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok convertTiffMultipageToPdf(const char *filein, const char *fileout)
+{
+ return ERROR_INT("function not present", "convertTiffMultipageToPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_generateJpegDataMem(l_uint8 *data, size_t nbytes,
+ l_int32 ascii85flag)
+{
+ return (L_COMP_DATA *)ERROR_PTR("function not present",
+ "l_generateJpegDataMem", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok 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_ok 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/leptonica/src/pix.h b/leptonica/src/pix.h
new file mode 100644
index 00000000..f2e5d601
--- /dev/null
+++ b/leptonica/src/pix.h
@@ -0,0 +1,1302 @@
+/*====================================================================*
+ - 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
+
+/*!
+ * \file pix.h
+ *
+ * <pre>
+ * 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.
+ *
+ * Contents:
+ *
+ * (1) 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
+ *
+ * (2) This file has definitions for:
+ * Colors for RGBA
+ * Colors for drawing boxes
+ * 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 and location filter flags
+ * Color component selection flags
+ * Color content 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
+ * Value flags
+ * Statistical measures
+ * Set selection flags
+ * Text orientation flags
+ * Edge orientation flags
+ * Line orientation flags
+ * Image orientation flags
+ * Scan direction flags
+ * Box size adjustment flags
+ * Flags for modifying box boundaries using a second box
+ * Handling overlapping bounding boxes in boxa
+ * Selecting or making a box from two (intersecting) boxes
+ * Flags for replacing invalid boxes
+ * Flags for box corners and center
+ * 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 making simple masks
+ * 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 trailing slash from string
+ *
+ * (3) This file has typedefs for the pix allocator and deallocator functions
+ * alloc_fn()
+ * dealloc_fn().
+ * </pre>
+ */
+
+
+/*-------------------------------------------------------------------------*
+ * 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. */
+
+/*! Basic Pix */
+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;
+
+/*! Colormap of a 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; /*!< blue value */
+ l_uint8 green; /*!< green value */
+ l_uint8 red; /*!< red value */
+ l_uint8 alpha; /*!< alpha value */
+};
+typedef struct RGBA_Quad RGBA_QUAD;
+
+
+/*-------------------------------------------------------------------------*
+ * Colors for 32 RGBA *
+ *-------------------------------------------------------------------------*/
+/* <pre>
+ * 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.
+ * </pre>
+ */
+
+/*! RGBA Color */
+enum {
+ COLOR_RED = 0, /*!< red color index in RGBA_QUAD */
+ COLOR_GREEN = 1, /*!< green color index in RGBA_QUAD */
+ COLOR_BLUE = 2, /*!< blue color index in RGBA_QUAD */
+ L_ALPHA_CHANNEL = 3 /*!< alpha value index in RGBA_QUAD */
+};
+
+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 */
+
+
+/*-------------------------------------------------------------------------*
+ * Colors for drawing boxes *
+ *-------------------------------------------------------------------------*/
+/*! Box Color */
+enum {
+ L_DRAW_RED = 0, /*!< draw in red */
+ L_DRAW_GREEN = 1, /*!< draw in green */
+ L_DRAW_BLUE = 2, /*!< draw in blue */
+ L_DRAW_SPECIFIED = 3, /*!< draw specified color */
+ L_DRAW_RGB = 4, /*!< draw as sequence of r,g,b */
+ L_DRAW_RANDOM = 5 /*!< draw randomly chosen colors */
+};
+
+
+/*-------------------------------------------------------------------------*
+ * Perceptual color weights *
+ *-------------------------------------------------------------------------*/
+/* <pre>
+ * Notes:
+ * (1) These perceptual weighting factors are ad-hoc, but they do
+ * add up to 1. Unlike, for example, the weighting factors for
+ * converting RGB to luminance, or more specifically to Y in the
+ * YUV colorspace. Those numbers come from the
+ * International Telecommunications Union, via ITU-R.
+ * </pre>
+ */
+static const l_float32 L_RED_WEIGHT = 0.3f; /*!< Percept. weight for red */
+static const l_float32 L_GREEN_WEIGHT = 0.5f; /*!< Percept. weight for green */
+static const l_float32 L_BLUE_WEIGHT = 0.2f; /*!< Percept. weight for blue */
+
+
+/*-------------------------------------------------------------------------*
+ * Flags for colormap conversion *
+ *-------------------------------------------------------------------------*/
+/*! Cmap Conversion */
+enum {
+ REMOVE_CMAP_TO_BINARY = 0, /*!< remove colormap for conv to 1 bpp */
+ REMOVE_CMAP_TO_GRAYSCALE = 1, /*!< remove colormap for conv to 8 bpp */
+ REMOVE_CMAP_TO_FULL_COLOR = 2, /*!< remove colormap for conv to 32 bpp */
+ REMOVE_CMAP_WITH_ALPHA = 3, /*!< remove colormap and alpha */
+ REMOVE_CMAP_BASED_ON_SRC = 4 /*!< remove depending on src format */
+};
+
+
+/*------------------------------------------------------------------------*
+ *!
+ * <pre>
+ * 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
+ *
+ * </pre>
+ *-------------------------------------------------------------------------*/
+
+#define PIX_SRC (0xc) /*!< use source pixels */
+#define PIX_DST (0xa) /*!< use destination pixels */
+#define PIX_NOT(op) ((op) ^ 0x0f) /*!< invert operation %op */
+#define PIX_CLR (0x0) /*!< clear pixels */
+#define PIX_SET (0xf) /*!< set pixels */
+
+#define PIX_PAINT (PIX_SRC | PIX_DST) /*!< paint = src | dst */
+#define PIX_MASK (PIX_SRC & PIX_DST) /*!< mask = src & dst */
+#define PIX_SUBTRACT (PIX_DST & PIX_NOT(PIX_SRC)) /*!< subtract = */
+ /*!< src & !dst */
+#define PIX_XOR (PIX_SRC ^ PIX_DST) /*!< xor = src ^ dst */
+
+
+/*-------------------------------------------------------------------------*
+ * <pre>
+ * 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
+ * color samples 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);
+ *
+ * More efficiently, these components can be extracted 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)
+ * The functions extractRGBValues() and extractRGBAValues() are
+ * provided to do this. Likewise, the pixels can be set
+ * directly by shifting, using composeRGBPixel() and
+ * composeRGBAPixel().
+ *
+ * All these operations work properly on both big- and little-endians.
+ *
+ * (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
+ * </pre>
+ *-------------------------------------------------------------------------*/
+
+
+
+/*-------------------------------------------------------------------------*
+ * Array of pix *
+ *-------------------------------------------------------------------------*/
+ /* Serialization for primary data structures */
+#define PIXAA_VERSION_NUMBER 2 /*!< Version for Pixaa serialization */
+#define PIXA_VERSION_NUMBER 2 /*!< Version for Pixa serialization */
+#define BOXA_VERSION_NUMBER 2 /*!< Version for Boxa serialization */
+#define BOXAA_VERSION_NUMBER 3 /*!< Version for Boxaa serialization */
+
+/*! Array of pix */
+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;
+
+/*! Array of arrays of pix */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! Basic rectangle */
+struct Box
+{
+ l_int32 x; /*!< left coordinate */
+ l_int32 y; /*!< top coordinate */
+ l_int32 w; /*!< box width */
+ l_int32 h; /*!< box height */
+ l_uint32 refcount; /*!< reference count (1 if no clones) */
+};
+typedef struct Box BOX;
+
+/*! Array of 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;
+
+/*! Array of 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 /*!< Version for Pta serialization */
+
+/*! Array of points */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! 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 *
+ *-------------------------------------------------------------------------*/
+/*! 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 *
+ *-------------------------------------------------------------------------*/
+/*! 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 /*!< Version for FPix serialization */
+
+/*! Pix with float array */
+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;
+
+/*! Array of 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 /*!< Version for DPix serialization */
+
+/*! Pix with double array */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! 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 /*!< Version for PixaComp serialization */
+
+/*! Array of compressed pix */
+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 *
+ *-------------------------------------------------------------------------*/
+/*
+ * <pre>
+ * 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. OK to use except in two situations:
+ * (a) You change data through one of the handles but don't
+ * want those changes to be seen by the other handle.
+ * (b) The application is multi-threaded. Because the clone
+ * operation is not atomic (e.g., locked with a mutex),
+ * it is possible to end up with an incorrect ref count,
+ * causing either a memory leak or a crash.
+ *
+ * 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., the
+ * string is not owned by the handle). You must not either free the string
+ * or insert it in some other struct that would own it. Specifically,
+ * for an Sarray, the copyflag for retrieval is either:
+ * L_COPY or L_NOCOPY
+ * and for insertion, the copyflag is either:
+ * L_COPY or one of {L_INSERT , L_NOCOPY} (the latter are equivalent
+ * for insertion))
+ * Typical patterns are:
+ * (1) Reference a string in an Sarray with L_NOCOPY and insert a copy
+ * of it in another Sarray with L_COPY.
+ * (2) Copy a string from an Sarray with L_COPY and insert it in
+ * another Sarray with L_INSERT (or L_NOCOPY).
+ * In both cases, a copy is made and both Sarrays own their instance
+ * of that string.
+ * </pre>
+ */
+/*! Object Access */
+enum {
+ L_NOCOPY = 0, /*!< do not copy the object; do not delete the ptr */
+ L_INSERT = L_NOCOPY, /*!< stuff it in; do not copy or 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 array object (e.g., pixa) and fill */
+ /*!< the array with clones (e.g., pix) */
+};
+
+/*----------------------------------------------------------------------------*
+ * Sort flags *
+ *----------------------------------------------------------------------------*/
+/*! Sort Mode */
+enum {
+ L_SHELL_SORT = 1, /*!< use shell sort */
+ L_BIN_SORT = 2 /*!< use bin sort */
+};
+
+/*! Sort Order */
+enum {
+ L_SORT_INCREASING = 1, /*!< sort in increasing order */
+ L_SORT_DECREASING = 2 /*!< sort in decreasing order */
+};
+
+/*! Sort Type */
+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 *
+ *---------------------------------------------------------------------------*/
+/*! Blend Types */
+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 */
+};
+
+/*! Paint Selection */
+enum {
+ L_PAINT_LIGHT = 1, /*!< colorize non-black pixels */
+ L_PAINT_DARK = 2 /*!< colorize non-white pixels */
+};
+
+/*-------------------------------------------------------------------------*
+ * Graphics pixel setting *
+ *-------------------------------------------------------------------------*/
+/*! 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 and location filter flags *
+ *-------------------------------------------------------------------------*/
+/*! Size Comparison */
+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 */
+};
+
+/*! Size Selection */
+enum {
+ L_SELECT_BY_WIDTH = 1, /*!< select by width; 1 bpp */
+ L_SELECT_BY_HEIGHT = 2, /*!< select by height; 1 bpp */
+ L_SELECT_BY_MAX_DIMENSION = 3, /*!< select by max of width and */
+ /*!< height; 1 bpp */
+ L_SELECT_BY_AREA = 4, /*!< select by foreground area; 1 bpp */
+ L_SELECT_BY_PERIMETER = 5 /*!< select by perimeter; 1 bpp */
+};
+
+/*! Location Filter */
+enum {
+ L_SELECT_WIDTH = 1, /*!< width must satisfy constraint */
+ L_SELECT_HEIGHT = 2, /*!< height must satisfy constraint */
+ L_SELECT_XVAL = 3, /*!< x value must satisfy constraint */
+ L_SELECT_YVAL = 4, /*!< y value must satisfy constraint */
+ L_SELECT_IF_EITHER = 5, /*!< either width or height (or xval */
+ /*!< or yval) can satisfy constraint */
+ L_SELECT_IF_BOTH = 6 /*!< both width and height (or xval */
+ /*!< and yval must satisfy constraint */
+};
+
+/*! Boxa Check */
+enum {
+ L_CHECK_WIDTH = 1, /*!< check and possibly modify width */
+ L_CHECK_HEIGHT = 2, /*!< check and possibly modify height */
+ L_CHECK_BOTH = 3 /*!< check and possibly modify both */
+};
+
+/*-------------------------------------------------------------------------*
+ * Color component selection flags *
+ *-------------------------------------------------------------------------*/
+/*! Color Selection */
+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 */
+ L_SELECT_HUE = 7, /*!< use hue value (in HSV color space) */
+ L_SELECT_SATURATION = 8, /*!< use saturation value (in HSV space) */
+ L_SELECT_WEIGHTED = 9 /*!< use weighted average of color comps */
+};
+
+/*-------------------------------------------------------------------------*
+ * Color content flags *
+ *-------------------------------------------------------------------------*/
+/*! Color Content */
+enum {
+ L_INTERMED_DIFF = 1, /*!< intermediate of diff component values */
+ L_AVE_MAX_DIFF_2 = 2, /*!< diff average closest comps to third */
+ L_MAX_DIFF = 3 /*!< maximum diff of component values */
+};
+
+/*-------------------------------------------------------------------------*
+ * 16-bit conversion flags *
+ *-------------------------------------------------------------------------*/
+/*! 16-bit Conversion */
+enum {
+ L_LS_BYTE = 1, /*!< use LSB */
+ L_MS_BYTE = 2, /*!< use MSB */
+ L_AUTO_BYTE = 3, /*!< use LSB if max(val) < 256; else MSB */
+ L_CLIP_TO_FF = 4, /*!< use max(val, 255) */
+ L_LS_TWO_BYTES = 5, /*!< use two LSB */
+ L_MS_TWO_BYTES = 6, /*!< use two MSB */
+ L_CLIP_TO_FFFF = 7 /*!< use max(val, 65535) */
+};
+
+/*-------------------------------------------------------------------------*
+ * Rotate and shear flags *
+ *-------------------------------------------------------------------------*/
+/*! Rotation Type */
+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 */
+};
+
+/*! Background Color */
+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 */
+};
+
+/*! Shear Point */
+enum {
+ L_SHEAR_ABOUT_CORNER = 1, /*!< shear image about UL corner */
+ L_SHEAR_ABOUT_CENTER = 2 /*!< shear image about center */
+};
+
+/*-------------------------------------------------------------------------*
+ * Affine transform order flags *
+ *-------------------------------------------------------------------------*/
+/*! Affine Transform Order */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! Grayscale Fill */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! BlackWhite Set */
+enum {
+ L_SET_WHITE = 1, /*!< set pixels to white */
+ L_SET_BLACK = 2 /*!< set pixels to black */
+};
+
+/*-------------------------------------------------------------------------*
+ * Flags for getting white or black value *
+ *-------------------------------------------------------------------------*/
+/*! BlackWhite Get */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! BlackWhite Sum */
+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. *
+ *-------------------------------------------------------------------------*/
+/*! Dither Distance */
+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 type flags *
+ *-------------------------------------------------------------------------*/
+/*! Distance Type */
+enum {
+ L_MANHATTAN_DISTANCE = 1, /*!< L1 distance (e.g., in color space) */
+ L_EUCLIDEAN_DISTANCE = 2 /*!< L2 distance */
+};
+
+/*-------------------------------------------------------------------------*
+ * Distance Value flags *
+ *-------------------------------------------------------------------------*/
+/*! Distance Value */
+enum {
+ L_NEGATIVE = 1, /*!< values < 0 */
+ L_NON_NEGATIVE = 2, /*!< values >= 0 */
+ L_POSITIVE = 3, /*!< values > 0 */
+ L_NON_POSITIVE = 4, /*!< values <= 0 */
+ L_ZERO = 5, /*!< values = 0 */
+ L_ALL = 6 /*!< all values */
+};
+
+/*-------------------------------------------------------------------------*
+ * Statistical measures *
+ *-------------------------------------------------------------------------*/
+/*! Stats Type */
+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 index selection flags *
+ *-------------------------------------------------------------------------*/
+/*! Index Selection */
+enum {
+ L_CHOOSE_CONSECUTIVE = 1, /*!< select 'n' consecutive */
+ L_CHOOSE_SKIP_BY = 2 /*!< select at intervals of 'n' */
+};
+
+/*-------------------------------------------------------------------------*
+ * Text orientation flags *
+ *-------------------------------------------------------------------------*/
+/*! Text Orientation */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! Edge Orientation */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! Line Orientation */
+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 */
+};
+
+/*-------------------------------------------------------------------------*
+ * Image orientation flags *
+ *-------------------------------------------------------------------------*/
+/*! Image Orientation */
+enum {
+ L_PORTRAIT_MODE = 0, /*!< typical: page is viewed with height > width */
+ L_LANDSCAPE_MODE = 1 /*!< page is viewed at 90 deg to portrait mode */
+};
+
+/*-------------------------------------------------------------------------*
+ * Scan direction flags *
+ *-------------------------------------------------------------------------*/
+/*! Scan Direction */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! Box Adjustment */
+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 modifying box boundaries using a second box *
+ *-------------------------------------------------------------------------*/
+/*! Box Boundary Mod */
+enum {
+ L_USE_MINSIZE = 1, /*!< use boundaries giving min size */
+ L_USE_MAXSIZE = 2, /*!< use boundaries giving max size */
+ L_SUB_ON_LOC_DIFF = 3, /*!< modify boundary if big location diff */
+ L_SUB_ON_SIZE_DIFF = 4, /*!< modify boundary if big size diff */
+ L_USE_CAPPED_MIN = 5, /*!< modify boundary with capped min */
+ L_USE_CAPPED_MAX = 6 /*!< modify boundary with capped max */
+};
+
+/*-------------------------------------------------------------------------*
+ * Handling overlapping bounding boxes in boxa *
+ *-------------------------------------------------------------------------*/
+/*! Box Overlap Mod */
+enum {
+ L_COMBINE = 1, /*!< resize to bounding region; remove smaller */
+ L_REMOVE_SMALL = 2 /*!< only remove smaller */
+};
+
+/*-------------------------------------------------------------------------*
+ * Selecting or making a box from two (intersecting) boxes *
+ *-------------------------------------------------------------------------*/
+/*! Box Combine or Select */
+enum {
+ L_GEOMETRIC_UNION = 1, /*!< use union of two boxes */
+ L_GEOMETRIC_INTERSECTION = 2, /*!< use intersection of two boxes */
+ L_LARGEST_AREA = 3, /*!< use box with largest area */
+ L_SMALLEST_AREA = 4 /*!< use box with smallest area */
+};
+
+/*-------------------------------------------------------------------------*
+ * Flags for replacing invalid boxes *
+ *-------------------------------------------------------------------------*/
+/*! Box Replacement */
+enum {
+ L_USE_ALL_BOXES = 1, /*!< consider all boxes in the sequence */
+ L_USE_SAME_PARITY_BOXES = 2 /*!< consider boxes with the same parity */
+};
+
+/*-------------------------------------------------------------------------*
+ * Flags for box corners and center *
+ *-------------------------------------------------------------------------*/
+/*! Box Corners and Center */
+enum {
+ L_UPPER_LEFT = 1, /*!< UL corner */
+ L_UPPER_RIGHT = 2, /*!< UR corner */
+ L_LOWER_LEFT = 3, /*!< LL corner */
+ L_LOWER_RIGHT = 4, /*!< LR corner */
+ L_BOX_CENTER = 5 /*!< center */
+};
+
+/*-------------------------------------------------------------------------*
+ * Horizontal warp *
+ *-------------------------------------------------------------------------*/
+/*! Horiz Warp Stretch */
+enum {
+ L_WARP_TO_LEFT = 1, /*!< increasing stretch or contraction to left */
+ L_WARP_TO_RIGHT = 2 /*!< increasing stretch or contraction to right */
+};
+
+/*! Horiz Warp Mode */
+enum {
+ L_LINEAR_WARP = 1, /*!< stretch or contraction grows linearly */
+ L_QUADRATIC_WARP = 2 /*!< stretch or contraction grows quadratically */
+};
+
+/*-------------------------------------------------------------------------*
+ * Pixel selection for resampling *
+ *-------------------------------------------------------------------------*/
+/*! Pixel Selection */
+enum {
+ L_INTERPOLATED = 1, /*!< linear interpolation from src pixels */
+ L_SAMPLED = 2 /*!< nearest src pixel sampling only */
+};
+
+/*-------------------------------------------------------------------------*
+ * Thinning flags *
+ *-------------------------------------------------------------------------*/
+/*! Thinning Polarity */
+enum {
+ L_THIN_FG = 1, /*!< thin foreground of 1 bpp image */
+ L_THIN_BG = 2 /*!< thin background of 1 bpp image */
+};
+
+/*-------------------------------------------------------------------------*
+ * Runlength flags *
+ *-------------------------------------------------------------------------*/
+/*! Runlength Direction */
+enum {
+ L_HORIZONTAL_RUNS = 0, /*!< determine runlengths of horizontal runs */
+ L_VERTICAL_RUNS = 1 /*!< determine runlengths of vertical runs */
+};
+
+/*-------------------------------------------------------------------------*
+ * Edge filter flags *
+ *-------------------------------------------------------------------------*/
+/*! Edge Filter */
+enum {
+ L_SOBEL_EDGE = 1, /*!< Sobel edge filter */
+ L_TWO_SIDED_EDGE = 2 /*!< Two-sided edge filter */
+};
+
+/*-------------------------------------------------------------------------*
+ * Subpixel color component ordering in LCD display *
+ *-------------------------------------------------------------------------*/
+/*! Subpixel Color Order */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! HSV Histogram */
+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 */
+};
+
+/*-------------------------------------------------------------------------*
+ * HSV Region flags (inclusion, exclusion) *
+ *-------------------------------------------------------------------------*/
+/*! HSV Region */
+enum {
+ L_INCLUDE_REGION = 1, /*!< Use pixels with specified HSV region */
+ L_EXCLUDE_REGION = 2 /*!< Use pixels outside HSV region */
+};
+
+/*-------------------------------------------------------------------------*
+ * Location flags for adding text to a pix *
+ *-------------------------------------------------------------------------*/
+/*! Add Text Location */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! Pix Plot */
+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 making simple masks *
+ *-------------------------------------------------------------------------*/
+/*! Mask Generation */
+enum {
+ L_USE_INNER = 1, /*!< Select the interior part */
+ L_USE_OUTER = 2 /*!< Select the outer part (e.g., a frame) */
+};
+
+/*-------------------------------------------------------------------------*
+ * Flags for selecting display program *
+ *-------------------------------------------------------------------------*/
+/*! 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 *
+ *-------------------------------------------------------------------------*/
+/*! Flags used in Pix::special */
+enum {
+ L_NO_CHROMA_SAMPLING_JPEG = 1 /*!< Write full resolution chroma */
+};
+
+/*-------------------------------------------------------------------------*
+ * Handling negative values in conversion to unsigned int *
+ *-------------------------------------------------------------------------*/
+/*! Negative Value */
+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 *
+ *-------------------------------------------------------------------------*/
+/*! Relative To Zero */
+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 trailing slash from string *
+ *-------------------------------------------------------------------------*/
+/*! Trailing Slash */
+enum {
+ L_ADD_TRAIL_SLASH = 1, /*!< Add trailing slash to string */
+ L_REMOVE_TRAIL_SLASH = 2 /*!< Remove trailing slash from string */
+};
+
+/*-------------------------------------------------------------------------*
+ * Pix allocator and deallocator function types *
+ *-------------------------------------------------------------------------*/
+/*! Allocator function type */
+typedef void *(*alloc_fn)(size_t);
+
+/*! Deallocator function type */
+typedef void (*dealloc_fn)(void *);
+
+#endif /* LEPTONICA_PIX_H */
diff --git a/leptonica/src/pix1.c b/leptonica/src/pix1.c
new file mode 100644
index 00000000..22589c0e
--- /dev/null
+++ b/leptonica/src/pix1.c
@@ -0,0 +1,2039 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pix1.c
+ * <pre>
+ *
+ * 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 *pixdata_malloc()
+ * static void pixdata_free()
+ * void setPixMemoryManager()
+ *
+ * Pix creation
+ * PIX *pixCreate()
+ * PIX *pixCreateNoInit()
+ * PIX *pixCreateTemplate()
+ * PIX *pixCreateTemplateNoInit()
+ * PIX *pixCreateWithCmap()
+ * 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()
+ * l_uint8 *pixGetTextCompNew()
+ * l_int32 *pixSetTextCompNew()
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 the pix raster *
+ * image data. They have no effect on any other heap allocation, *
+ * including the pix struct itself, which is controlled by the *
+ * #defines in environ.h. *
+ * *
+ * The default functions for allocating pix raster data are malloc and *
+ * free (or leptonica_* custom allocators if LEPTONICA_INTERCEPT_ALLOC *
+ * is defined). Use setPixMemoryManager() to specify other functions *
+ * to use specifically for pix raster image data. *
+ *-------------------------------------------------------------------------*/
+/*! Pix memory manager */
+ /*
+ * <pre>
+ * Notes:
+ * (1) The allocator and deallocator function types,
+ * alloc_fn and dealloc_fn, are defined in pix.h.
+ * </pre>
+ */
+struct PixMemoryManager
+{
+ alloc_fn allocator;
+ dealloc_fn deallocator;
+};
+
+/*! Default Pix memory manager */
+static struct PixMemoryManager pix_mem_manager = {
+#ifdef LEPTONICA_INTERCEPT_ALLOC
+ &leptonica_malloc,
+ &leptonica_free
+#else
+ &malloc,
+ &free
+#endif /* LEPTONICA_INTERCEPT_ALLOC */
+};
+
+static void *
+pixdata_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
+ * pixdata_malloc. Just ignore the custom allocator feature. */
+ return LEPT_MALLOC(size);
+#endif /* _MSC_VER */
+}
+
+static void
+pixdata_free(void *ptr)
+{
+#ifndef _MSC_VER
+ (*pix_mem_manager.deallocator)(ptr);
+#else /* _MSC_VER */
+ /* Under MSVC++, pix_mem_manager is initialized after a call to
+ * pixdata_malloc. Just ignore the custom allocator feature. */
+ LEPT_FREE(ptr);
+#endif /* _MSC_VER */
+}
+
+/*!
+ * \brief setPixMemoryManager()
+ *
+ * \param[in] allocator [optional] use NULL to skip
+ * \param[in] deallocator [optional] use NULL to skip
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+void
+setPixMemoryManager(alloc_fn allocator,
+ dealloc_fn deallocator)
+{
+ if (allocator) pix_mem_manager.allocator = allocator;
+ if (deallocator) pix_mem_manager.deallocator = deallocator;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Pix Creation *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixCreate()
+ *
+ * \param[in] 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, 4LL * pixd->wpl * pixd->h);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixCreateNoInit()
+ *
+ * \param[in] width, height, depth
+ * \return pixd with data allocated but not initialized,
+ * or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Must set pad bits to avoid reading uninitialized data, because
+ * some optimized routines (e.g., pixConnComp()) read from pad bits.
+ * </pre>
+ */
+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 *)pixdata_malloc(4LL * wpl * height)) == NULL) {
+ pixDestroy(&pixd);
+ return (PIX *)ERROR_PTR("pixdata_malloc fail for data",
+ procName, NULL);
+ }
+ pixSetData(pixd, data);
+ pixSetPadBits(pixd, 0);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixCreateTemplate()
+ *
+ * \param[in] pixs
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixCreateTemplate(const 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, 4LL * pixd->wpl * pixd->h);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixCreateTemplateNoInit()
+ *
+ * \param[in] pixs
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixCreateTemplateNoInit(const 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;
+}
+
+
+/*!
+ * \brief pixCreateWithCmap()
+ *
+ * \param[in] width
+ * \param[in] height
+ * \param[in] depth 2, 4 or 8 bpp
+ * \param[in] initcolor L_SET_BLACK, L_SET_WHITE
+ * \return pixd with the initialization color assigned to all pixels,
+ * or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) Creates a pix with a cmap, initialized to value 0.
+ * (2) Initializes the pix black or white by adding that color
+ * to the cmap at index 0.
+ * </pre>
+ */
+PIX *
+pixCreateWithCmap(l_int32 width,
+ l_int32 height,
+ l_int32 depth,
+ l_int32 initcolor)
+{
+PIX *pix;
+PIXCMAP *cmap;
+
+ PROCNAME("pixCreateWithCmap");
+
+ if (depth != 2 && depth != 4 && depth != 8)
+ return (PIX *)ERROR_PTR("depth not 2, 4 or 8 bpp", procName, NULL);
+
+ if ((pix = pixCreate(width, height, depth)) == NULL)
+ return (PIX *)ERROR_PTR("pix not made", procName, NULL);
+ cmap = pixcmapCreate(depth);
+ pixSetColormap(pix, cmap);
+ if (initcolor == L_SET_BLACK)
+ pixcmapAddColor(cmap, 0, 0, 0);
+ else /* L_SET_WHITE */
+ pixcmapAddColor(cmap, 255, 255, 255);
+ return pix;
+}
+
+
+/*!
+ * \brief pixCreateHeader()
+ *
+ * \param[in] width, height, depth
+ * \return pixd with no data allocated, or NULL on error
+ *
+ * <pre>
+ * 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) All pixCreate*() functions call pixCreateHeader().
+ 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, we limit the requested
+ * allocation of image data in a typesafe way.
+ * </pre>
+ */
+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, malicious or otherwise */
+ wpl64 = ((l_uint64)width * (l_uint64)depth + 31) / 32;
+ if (wpl64 > ((1LL << 24) - 1)) {
+ L_ERROR("requested w = %d, h = %d, d = %d\n",
+ procName, width, height, depth);
+ return (PIX *)ERROR_PTR("wpl >= 2^24", procName, NULL);
+ }
+ wpl = (l_int32)wpl64;
+ bignum = 4LL * 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);
+ }
+
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ if (bignum > (1LL << 26)) {
+ L_ERROR("fuzzer requested > 64 MB; refused\n", procName);
+ return NULL;
+ }
+ if (width > 20000) {
+ L_ERROR("fuzzer requested width > 20K; refused\n", procName);
+ return NULL;
+ }
+ if (height > 20000) {
+ L_ERROR("fuzzer requested height > 20K; refused\n", procName);
+ return NULL;
+ }
+#endif /* FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
+
+ pixd = (PIX *)LEPT_CALLOC(1, sizeof(PIX));
+ 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;
+}
+
+
+/*!
+ * \brief pixClone()
+ *
+ * \param[in] pixs
+ * \return same pix ptr, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixClone(PIX *pixs)
+{
+ PROCNAME("pixClone");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+ pixChangeRefcount(pixs, 1);
+
+ return pixs;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Pix Destruction *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixDestroy()
+ *
+ * \param[in,out] ppix will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Decrements the ref count and, if 0, destroys the pix.
+ * (2) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFree()
+ *
+ * \param[in] pix
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Decrements the ref count and, if 0, destroys the pix.
+ * </pre>
+ */
+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)
+ pixdata_free(data);
+ if ((text = pixGetText(pix)) != NULL)
+ LEPT_FREE(text);
+ pixDestroyColormap(pix);
+ LEPT_FREE(pix);
+ }
+ return;
+}
+
+
+/*-------------------------------------------------------------------------*
+ * Pix Copy *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief pixCopy()
+ *
+ * \param[in] pixd [optional] can be null, equal to pixs,
+ * different from pixs
+ * \param[in] pixs
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixCopy(PIX *pixd, /* can be null */
+ const PIX *pixs)
+{
+l_int32 bytes;
+
+ PROCNAME("pixCopy");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+ 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);
+ memcpy(pixd->data, pixs->data, bytes);
+ return pixd;
+ }
+
+ /* Reallocate image data if sizes are different. If this fails,
+ * pixd hasn't been changed. But we want to signal that the copy
+ * failed, so return NULL. This will cause a memory leak if the
+ * return ptr is assigned to pixd, but that is preferred to proceeding
+ * with an incorrect pixd, and in any event this use case of
+ * pixCopy() -- reallocating into an existing pix -- is infrequent. */
+ 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 */
+ memcpy(pixd->data, pixs->data, bytes);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixResizeImageData()
+ *
+ * \param[in] pixd gets new uninitialized buffer for image data
+ * \param[in] pixs determines the size of the buffer; not changed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If the sizes of data in pixs and pixd are unequal, this
+ * frees the existing image data in pixd and allocates
+ * an uninitialized buffer that will hold the required amount
+ * of image data in pixs. The image data from pixs is not
+ * copied into the new buffer.
+ * (2) On failure to allocate, pixd is unchanged.
+ * </pre>
+ */
+l_ok
+pixResizeImageData(PIX *pixd,
+ const 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;
+
+ /* Make sure we can copy the data */
+ pixGetDimensions(pixs, &w, &h, &d);
+ wpl = pixGetWpl(pixs);
+ bytes = 4 * wpl * h;
+ if ((data = (l_uint32 *)pixdata_malloc(bytes)) == NULL)
+ return ERROR_INT("pixdata_malloc fail for data", procName, 1);
+
+ /* OK, do it */
+ pixSetWidth(pixd, w);
+ pixSetHeight(pixd, h);
+ pixSetDepth(pixd, d);
+ pixSetWpl(pixd, wpl);
+ pixFreeData(pixd); /* free any existing image data */
+ pixSetData(pixd, data); /* set the uninitialized memory buffer */
+ pixCopyResolution(pixd, pixs);
+ return 0;
+}
+
+
+/*!
+ * \brief pixCopyColormap()
+ *
+ * \param[in] pixd
+ * \param[in] pixs copies the colormap to %pixd
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This destroys the colormap in pixd, unless the operation is a no-op
+ * </pre>
+ */
+l_ok
+pixCopyColormap(PIX *pixd,
+ const PIX *pixs)
+{
+l_int32 valid;
+const PIXCMAP *cmaps;
+PIXCMAP *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 */
+ if (pixGetDepth(pixs) != pixGetDepth(pixd))
+ return ERROR_INT("depths of pixs and pixd differ", procName, 1);
+
+ pixDestroyColormap(pixd);
+ if ((cmaps = pixs->colormap) == NULL) /* not an error */
+ return 0;
+ pixcmapIsValid(cmaps, NULL, &valid);
+ if (!valid)
+ return ERROR_INT("cmap not valid", procName, 1);
+
+ if ((cmapd = pixcmapCopy(cmaps)) == NULL)
+ return ERROR_INT("cmapd not made", procName, 1);
+ pixSetColormap(pixd, cmapd);
+ return 0;
+}
+
+
+/*!
+ * \brief pixSizesEqual()
+ *
+ * \param[in] pix1, pix2
+ * \return 1 if the two pix have same {h, w, d}; 0 otherwise.
+ */
+l_int32
+pixSizesEqual(const PIX *pix1,
+ const 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;
+}
+
+
+/*!
+ * \brief pixTransferAllData()
+ *
+ * \param[in] pixd must be different from pixs
+ * \param[in,out] ppixs will be nulled if refcount goes to 0
+ * \param[in] copytext 1 to copy the text field; 0 to skip
+ * \param[in] copyformat 1 to copy the informat field; 0 to skip
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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:
+ * \code
+ * void function-inplace(PIX **ppix, ...) {
+ * PIX *pixt = function-makenew(*ppix);
+ * pixDestroy(ppix);
+ * *ppix = pixt;
+ * return;
+ * }
+ * \endcode
+ * 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:
+ * \code
+ * void function-inplace(PIX *pix, ...) {
+ * PIX *pixt = function-makenew(pix);
+ * pixTransferAllData(pix, &pixt, 0, 0);
+ * // pixDestroy() is called on pixt
+ * return;
+ * }
+ * \endcode
+ * 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.
+ * </pre>
+ */
+l_ok
+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 */
+ pixDestroyColormap(pixd); /* free the old one, if it exists */
+ pixd->colormap = pixGetColormap(pixs); /* transfer to pixd */
+ 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(pixGetData(pixd), 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;
+}
+
+
+/*!
+ * \brief pixSwapAndDestroy()
+ *
+ * \param[out] ppixd [optional] input pixd can be null,
+ * and it must be different from pixs
+ * \param[in,out] ppixs will be nulled after the swap
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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:
+ * \code
+ * Pix *pix1 = pixRead("...");
+ * Pix *pix2 = function(pix1, ...);
+ * pixSwapAndDestroy(&pix1, &pix2);
+ * pixDestroy(&pix1); // holds what was in pix2
+ * \endcode
+ * Example with clones ([] shows ref count of image generated
+ * by the function):
+ * \code
+ * 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]
+ * \endcode
+ * </pre>
+ */
+l_ok
+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(const PIX *pix)
+{
+ PROCNAME("pixGetWidth");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 0);
+
+ 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(const PIX *pix)
+{
+ PROCNAME("pixGetHeight");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 0);
+
+ 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(const PIX *pix)
+{
+ PROCNAME("pixGetDepth");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 0);
+
+ 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;
+}
+
+
+/*!
+ * \brief pixGetDimensions()
+ *
+ * \param[in] pix
+ * \param[out] pw, ph, pd [optional] each can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixGetDimensions(const 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;
+}
+
+
+/*!
+ * \brief pixSetDimensions()
+ *
+ * \param[in] pix
+ * \param[in] w, h, d use 0 to skip the setting for any of these
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixCopyDimensions()
+ *
+ * \param[in] pixd
+ * \param[in] pixs
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixCopyDimensions(PIX *pixd,
+ const 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(const PIX *pix)
+{
+ PROCNAME("pixGetSpp");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 0);
+
+ return pix->spp;
+}
+
+
+/*
+ * \brief pixSetSpp()
+ *
+ * \param[in] pix
+ * \param[in] spp 1, 3 or 4 samples
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCopySpp()
+ *
+ * \param[in] pixd
+ * \param[in] pixs
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixCopySpp(PIX *pixd,
+ const 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(const PIX *pix)
+{
+ PROCNAME("pixGetWpl");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 0);
+ 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(const PIX *pix)
+{
+ PROCNAME("pixGetRefcount");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 0);
+ 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(const 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(const 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;
+}
+
+
+/*!
+ * \brief pixGetResolution()
+ *
+ * \param[in] pix
+ * \param[out] pxres, pyres [optional] each can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixGetResolution(const 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;
+}
+
+
+/*!
+ * \brief pixSetResolution()
+ *
+ * \param[in] pix
+ * \param[in] xres, yres use 0 to skip setting a value for either of these
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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,
+ const 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)
+{
+l_float64 xres, yres;
+l_float64 maxres = 100000000.0;
+
+ PROCNAME("pixScaleResolution");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+ if (xscale <= 0 || yscale <= 0)
+ return ERROR_INT("invalid scaling ratio", procName, 1);
+
+ xres = (l_float64)xscale * (l_float32)(pix->xres) + 0.5;
+ yres = (l_float64)yscale * (l_float32)(pix->yres) + 0.5;
+ pix->xres = (l_uint32)L_MIN(xres, maxres);
+ pix->yres = (l_uint32)L_MIN(yres, maxres);
+ return 0;
+}
+
+
+l_int32
+pixGetInputFormat(const PIX *pix)
+{
+ PROCNAME("pixGetInputFormat");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 0);
+ 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,
+ const 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;
+}
+
+
+/*!
+ * \brief pixGetText()
+ *
+ * \param[in] pix
+ * \return ptr to existing text string
+ *
+ * <pre>
+ * Notes:
+ * (1) The text string belongs to the pix:
+ * * the caller must NOT free it
+ * * it must not be used after the pix is destroyed
+ * </pre>
+ */
+char *
+pixGetText(PIX *pix)
+{
+ PROCNAME("pixGetText");
+
+ if (!pix)
+ return (char *)ERROR_PTR("pix not defined", procName, NULL);
+ return pix->text;
+}
+
+
+/*!
+ * \brief pixSetText()
+ *
+ * \param[in] pix
+ * \param[in] textstring can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This removes any existing textstring and puts a copy of
+ * the input textstring there.
+ * </pre>
+ */
+l_ok
+pixSetText(PIX *pix,
+ const char *textstring)
+{
+ PROCNAME("pixSetText");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+
+ stringReplace(&pix->text, textstring);
+ return 0;
+}
+
+
+/*!
+ * \brief pixAddText()
+ *
+ * \param[in] pix
+ * \param[in] textstring can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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,
+ const 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, pixs->text);
+ return 0;
+}
+
+
+/*!
+ * \brief pixGetTextCompNew()
+ *
+ * \param[in] pix
+ * \param[out] psize this number of bytes of returned binary data
+ * \return ptr to binary data derived from the text string in the pix,
+ * after decoding and uncompressing
+ *
+ * <pre>
+ * Notes:
+ * (1) The ascii string in the text field of the input pix was
+ * previously stored there using pixSetTextCompNew().
+ * (2) This retrieves the string and performs ascii85 decoding
+ * followed by decompression on it. The returned binary data
+ * is owned by the caller and must be freed.
+ * </pre>
+ */
+l_uint8 *
+pixGetTextCompNew(PIX *pix,
+ size_t *psize)
+{
+char *str;
+
+ PROCNAME("pixGetTextCompNew");
+
+ if (!pix)
+ return (l_uint8 *)ERROR_PTR("pix not defined", procName, NULL);
+ str = pixGetText(pix);
+ return decodeAscii85WithComp(str, strlen(str), psize);
+}
+
+
+/*!
+ * \brief pixSetTextCompNew()
+ *
+ * \param[in] pix
+ * \param[in] data binary data
+ * \param[in] size number of bytes of binary data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This receives binary data and performs compression and ascii85
+ * encoding on it. The ascii result is stored in the input pix,
+ * replacing any string that may be there.
+ * (2) The input %data can be reconstructed using pixGetTextCompNew().
+ * </pre>
+ */
+l_ok
+pixSetTextCompNew(PIX *pix,
+ const l_uint8 *data,
+ size_t size)
+{
+size_t encodesize; /* ignored */
+
+ PROCNAME("pixSetTextCompNew");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+
+ stringReplace(&pix->text, encodeAscii85WithComp(data, size, &encodesize));
+ return 0;
+}
+
+
+PIXCMAP *
+pixGetColormap(PIX *pix)
+{
+ PROCNAME("pixGetColormap");
+
+ if (!pix)
+ return (PIXCMAP *)ERROR_PTR("pix not defined", procName, NULL);
+ return pix->colormap;
+}
+
+
+/*!
+ * \brief pixSetColormap()
+ *
+ * \param[in] pix
+ * \param[in] colormap optional; can be null.
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) If %colormap is not defined, this is a no-op.
+ * (2) This destroys any existing colormap before assigning the
+ * new %colormap to %pix.
+ * (3) If the colormap is not valid, this returns 1. The caller
+ * should check if there is a possibility that the pix and
+ * colormap depths differ.
+ * (4) This does not do the work of checking pixs for a pixel value
+ * that is out of bounds for the colormap -- that only needs to
+ * be done when reading and writing with an I/O library like
+ * png and gif.
+ * (5) Because colormaps are not ref counted, the new colormap
+ * must not belong to any other pix.
+ * </pre>
+ */
+l_ok
+pixSetColormap(PIX *pix,
+ PIXCMAP *colormap)
+{
+l_int32 valid;
+
+ PROCNAME("pixSetColormap");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+ if (!colormap) return 0;
+
+ /* Make sure the colormap doesn't get lost */
+ pixDestroyColormap(pix);
+ pix->colormap = colormap;
+
+ pixcmapIsValid(colormap, NULL, &valid);
+ if (!valid)
+ return ERROR_INT("colormap is not valid", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixDestroyColormap()
+ *
+ * \param[in] pix
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixGetData()
+ *
+ * \param[in] pix
+ * \return ptr to image data
+ *
+ * <pre>
+ * 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.
+ * (2) This cannot guarantee that the pix data returned will not
+ * be changed, so %pix cannot be declared const. And because
+ * most imaging operations call this for access to the data,
+ * this prevents them from declaring %pix to be const, even if
+ * they only use the data for inspection.
+ * </pre>
+ */
+l_uint32 *
+pixGetData(PIX *pix)
+{
+ PROCNAME("pixGetData");
+
+ if (!pix)
+ return (l_uint32 *)ERROR_PTR("pix not defined", procName, NULL);
+ return pix->data;
+}
+
+
+/*!
+ * \brief pixSetData()
+ *
+ * \param[in] pix
+ * \param[in] data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This does not free any existing data. To free existing
+ * data, use pixFreeData() before pixSetData().
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixExtractData()
+ *
+ * \param[in] pix
+ * \return ptr to data, or null on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *)pixdata_malloc(bytes)) == NULL)
+ return (l_uint32 *)ERROR_PTR("data not made", procName, NULL);
+ memcpy(data, datas, bytes);
+ }
+
+ return data;
+}
+
+
+/*!
+ * \brief pixFreeData()
+ *
+ * \param[in] pix
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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) {
+ pixdata_free(data);
+ pix->data = NULL;
+ }
+ return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Pix line ptrs *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixGetLinePtrs()
+ *
+ * \param[in] pix
+ * \param[out] psize [optional] array size, which is the pix height
+ * \return array of line ptrs, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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[];
+
+/*!
+ * \brief pixPrintStreamInfo()
+ *
+ * \param[in] fp file stream
+ * \param[in] pix
+ * \param[in] text [optional] identifying string; can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixPrintStreamInfo(FILE *fp,
+ const PIX *pix,
+ const char *text)
+{
+l_int32 informat;
+const 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), pix->data, pixGetRefcount(pix));
+ fprintf(fp, " xres = %d, yres = %d\n", pixGetXRes(pix), pixGetYRes(pix));
+ if ((cmap = pix->colormap) != NULL)
+ pixcmapWriteStream(fp, cmap);
+ else
+ fprintf(fp, " no colormap\n");
+ informat = pixGetInputFormat(pix);
+ fprintf(fp, " input format: %d (%s)\n", informat,
+ ImageFileFormatExtensions[informat]);
+ if (pix->text != NULL)
+ fprintf(fp, " text: %s\n", pix->text);
+
+ return 0;
+}
diff --git a/leptonica/src/pix2.c b/leptonica/src/pix2.c
new file mode 100644
index 00000000..89f39e56
--- /dev/null
+++ b/leptonica/src/pix2.c
@@ -0,0 +1,3573 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pix2.c
+ * <pre>
+ *
+ * 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 pixSetCmapPixel()
+ * 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()
+ *
+ * Raster line pixel setter
+ * l_int32 setLineDataVal()
+ *
+ * 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()
+ *
+ * Infer resolution from image size
+ * l_int32 pixInferResolution()
+ *
+ * Setup helpers for 8 bpp byte processing
+ * l_uint8 **pixSetupByteProcessing()
+ * l_int32 pixCleanupByteProcessing()
+ *
+ * Setting parameters for antialias masking with alpha transforms
+ * void l_setAlphaMaskBorder()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixGetPixel()
+ *
+ * \param[in] pix
+ * \param[in] x,y pixel coords
+ * \param[out] pval pixel value
+ * \return 0 if OK; 1 or 2 on error
+ *
+ * <pre>
+ * 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.
+ * (3) If the point is outside the image, this returns an error (2),
+ * with 0 in %pval. To avoid spamming output, it fails silently.
+ * </pre>
+ */
+l_ok
+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 || y < 0 || y >= h)
+ return 2;
+
+ 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;
+}
+
+
+/*!
+ * \brief pixSetPixel()
+ *
+ * \param[in] pix
+ * \param[in] x,y pixel coords
+ * \param[in] val value to be inserted
+ * \return 0 if OK; 1 or 2 on error
+ *
+ * <pre>
+ * 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.
+ * (3) If the point is outside the image, this returns an error (2),
+ * with 0 in %pval. To avoid spamming output, it fails silently.
+ * </pre>
+ */
+l_ok
+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 || y < 0 || y >= h)
+ return 2;
+
+ 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;
+}
+
+
+/*!
+ * \brief pixGetRGBPixel()
+ *
+ * \param[in] pix 32 bpp rgb, not colormapped
+ * \param[in] x,y pixel coords
+ * \param[out] prval [optional] red component
+ * \param[out] pgval [optional] green component
+ * \param[out] pbval [optional] blue component
+ * \return 0 if OK; 1 or 2 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If the point is outside the image, this returns an error (2),
+ * with 0 in %pval. To avoid spamming output, it fails silently.
+ * </pre>
+ */
+l_ok
+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 || y < 0 || y >= h)
+ return 2;
+
+ 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;
+}
+
+
+/*!
+ * \brief pixSetRGBPixel()
+ *
+ * \param[in] pix 32 bpp rgb
+ * \param[in] x,y pixel coords
+ * \param[in] rval red component
+ * \param[in] gval green component
+ * \param[in] bval blue component
+ * \return 0 if OK; 1 or 2 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If the point is outside the image, this returns an error (2),
+ * and to avoid spamming output, it fails silently.
+ * </pre>
+ */
+l_ok
+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 || y < 0 || y >= h)
+ return 2;
+
+ wpl = pixGetWpl(pix);
+ data = pixGetData(pix);
+ line = data + y * wpl;
+ composeRGBPixel(rval, gval, bval, &pixel);
+ *(line + x) = pixel;
+ return 0;
+}
+
+
+/*!
+ * \brief pixSetCmapPixel()
+ *
+ * \param[in] pix 2, 4 or 8 bpp, colormapped
+ * \param[in] x,y pixel coords
+ * \param[in] rval red component
+ * \param[in] gval green component
+ * \param[in] bval blue component
+ * \return 0 if OK; 1 or 2 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If the point is outside the image, this returns an error (2),
+ * and to avoid spamming output, it fails silently.
+ * (2) - If the color already exists, use it.
+ * - If the color does not exist in the colormap, it is added
+ * if possible.
+ * - If there is not room in the colormap for the new color:
+ * * if d < 8, return 2 with a warning.
+ * * if d == 8, find and use the nearest color.
+ * (3) Note that this operation scales with the number of colors
+ * in the colormap, and therefore can be very expensive if an
+ * attempt is made to set many pixels. (In that case, it should
+ * be implemented with a map:rgb-->index for efficiency.)
+ * This is best used with very small images.
+ * </pre>
+ */
+l_ok
+pixSetCmapPixel(PIX *pix,
+ l_int32 x,
+ l_int32 y,
+ l_int32 rval,
+ l_int32 gval,
+ l_int32 bval)
+{
+l_int32 w, h, d, index;
+PIXCMAP *cmap;
+
+ PROCNAME("pixSetCmapPixel");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+ if ((cmap = pixGetColormap(pix)) == NULL)
+ return ERROR_INT("pix is not colormapped", procName, 1);
+ pixGetDimensions(pix, &w, &h, &d);
+ if (d != 2 && d != 4 && d != 8)
+ return ERROR_INT("pix depth not 2, 4 or 8", procName, 1);
+ if (x < 0 || x >= w || y < 0 || y >= h)
+ return 2;
+
+ if (d == 8) { /* always add */
+ pixcmapAddNearestColor(cmap, rval, gval, bval, &index);
+ } else { /* d < 8 */
+ if (pixcmapAddNewColor(cmap, rval, gval, bval, &index) == 2)
+ return ERROR_INT("colormap is full", procName, 2);
+ }
+ pixSetPixel(pix, x, y, index);
+ return 0;
+}
+
+
+/*!
+ * \brief pixGetRandomPixel()
+ *
+ * \param[in] pix any depth; can be colormapped
+ * \param[out] pval [optional] pixel value
+ * \param[out] px [optional] x coordinate chosen; can be null
+ * \param[out] py [optional] y coordinate chosen; can be null
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If the pix is colormapped, it returns the rgb value.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixClearPixel()
+ *
+ * \param[in] pix any depth; warning if colormapped
+ * \param[in] x,y pixel coords
+ * \return 0 if OK; 1 or 2 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) If the point is outside the image, this returns an error (2),
+ * with 0 in %pval. To avoid spamming output, it fails silently.
+ * </pre>
+ */
+l_ok
+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);
+ if (pixGetColormap(pix))
+ L_WARNING("cmapped: setting to 0 may not be intended\n", procName);
+ pixGetDimensions(pix, &w, &h, &d);
+ if (x < 0 || x >= w || y < 0 || y >= h)
+ return 2;
+
+ 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;
+}
+
+
+/*!
+ * \brief pixFlipPixel()
+ *
+ * \param[in] pix any depth, warning if colormapped
+ * \param[in] x,y pixel coords
+ * \return 0 if OK; 1 or 2 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If the point is outside the image, this returns an error (2),
+ * with 0 in %pval. To avoid spamming output, it fails silently.
+ * </pre>
+ */
+l_ok
+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);
+ if (pixGetColormap(pix))
+ L_WARNING("cmapped: setting to 0 may not be intended\n", procName);
+ pixGetDimensions(pix, &w, &h, &d);
+ if (x < 0 || x >= w || y < 0 || y >= h)
+ return 2;
+
+ 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;
+}
+
+
+/*!
+ * \brief setPixelLow()
+ *
+ * \param[in] line ptr to beginning of line,
+ * \param[in] x pixel location in line
+ * \param[in] depth bpp
+ * \param[in] val to be inserted
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Caution: input variables are not checked!
+ * </pre>
+ */
+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:
+ lept_stderr("illegal depth in setPixelLow()\n");
+ }
+}
+
+
+/*-------------------------------------------------------------*
+ * Find black or white value *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixGetBlackOrWhiteVal()
+ *
+ * \param[in] pixs all depths; cmap ok
+ * \param[in] op L_GET_BLACK_VAL, L_GET_WHITE_VAL
+ * \param[out] pval pixel value
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixClearAll()
+ *
+ * \param[in] pix all depths; use cmapped with caution
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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!
+ * </pre>
+ */
+l_ok
+pixClearAll(PIX *pix)
+{
+ PROCNAME("pixClearAll");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+
+ memset(pix->data, 0, 4LL * pix->wpl * pix->h);
+ return 0;
+}
+
+
+/*!
+ * \brief pixSetAll()
+ *
+ * \param[in] pix all depths; use cmapped with caution
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+ }
+
+ memset(pix->data, 0xff, 4LL * pix->wpl * pix->h);
+ return 0;
+}
+
+
+/*!
+ * \brief pixSetAllGray()
+ *
+ * \param[in] pix all depths, cmap ok
+ * \param[in] grayval in range 0 ... 255
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSetAllArbitrary()
+ *
+ * \param[in] pix all depths; use cmapped with caution
+ * \param[in] val value to set all pixels
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Caution 1! 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.
+ * (2) Caution 2! For 32 bpp pix, the interpretation of the LSB
+ * of %val depends on whether spp == 3 (RGB) or spp == 4 (RGBA).
+ * For RGB, the LSB is ignored in image transformations.
+ * For RGBA, the LSB is interpreted as the alpha (transparency)
+ * component; full transparency has alpha == 0x0, whereas
+ * full opacity has alpha = 0xff. An RGBA image with full
+ * opacity behaves like an RGB image.
+ * (3) As an example of (2), suppose you want to initialize a 32 bpp
+ * pix with partial opacity, say 0xee337788. If the pix is 3 spp,
+ * the 0x88 alpha component will be ignored and may be changed
+ * in subsequent processing. However, if the pix is 4 spp, the
+ * alpha component will be retained and used. The function
+ * pixCreate(w, h, 32) makes an RGB image by default, and
+ * pixSetSpp(pix, 4) can be used to promote an RGB image to RGBA.
+ * </pre>
+ */
+l_ok
+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);
+ if (d < 32) {
+ maxval = (1 << d) - 1;
+ if (val > maxval) {
+ L_WARNING("val = %d too large for depth; using maxval = %d\n",
+ procName, val, maxval);
+ 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;
+}
+
+
+/*!
+ * \brief pixSetBlackOrWhite()
+ *
+ * \param[in] pixs all depths; cmap ok
+ * \param[in] op L_SET_BLACK, L_SET_WHITE
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSetComponentArbitrary()
+ *
+ * \param[in] pix 32 bpp
+ * \param[in] comp COLOR_RED, COLOR_GREEN, COLOR_BLUE, L_ALPHA_CHANNEL
+ * \param[in] val value to set this component
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For example, this can be used to set the alpha component to opaque:
+ * pixSetComponentArbitrary(pix, L_ALPHA_CHANNEL, 255)
+ * </pre>
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixClearInRect()
+ *
+ * \param[in] pix all depths; can be cmapped
+ * \param[in] box in which all pixels will be cleared
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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!
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSetInRect()
+ *
+ * \param[in] pix all depths, can be cmapped
+ * \param[in] box in which all pixels will be set
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSetInRectArbitrary()
+ *
+ * \param[in] pix all depths; can be cmapped
+ * \param[in] box in which all pixels will be set to val
+ * \param[in] val value to set all pixels
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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!
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixBlendInRect()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] box [optional] in which all pixels will be blended
+ * \param[in] val blend value; 0xrrggbb00
+ * \param[in] fract fraction of color to be blended with each pixel in pixs
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixSetPadBits()
+ *
+ * \param[in] pix 1, 2, 4, 8, 16, 32 bpp
+ * \param[in] val 0 or 1
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * (4) When writing formatted output, such as tiff, png or jpeg,
+ * the pad bits have no effect on the raster image that is
+ * generated by reading back from the file. However, in some
+ * cases, the compressed file itself will depend on the pad
+ * bits. This is seen, for example, in Windows with 2 and 4 bpp
+ * tiff-compressed images that have pad bits on each scanline.
+ * It is sometimes convenient to use a golden file with a
+ * byte-by-byte check to verify invariance. Consequently,
+ * and because setting the pad bits is cheap, the pad bits are
+ * set to 0 before writing these compressed files.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSetPadBitsBand()
+ *
+ * \param[in] pix 1, 2, 4, 8, 16, 32 bpp
+ * \param[in] by starting y value of band
+ * \param[in] bh height of band
+ * \param[in] val 0 or 1
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixSetOrClearBorder()
+ *
+ * \param[in] pixs all depths
+ * \param[in] left, right, top, bot amount to set or clear
+ * \param[in] op operation PIX_SET or PIX_CLR
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSetBorderVal()
+ *
+ * \param[in] pixs 8, 16 or 32 bpp
+ * \param[in] left, right, top, bot amount to set
+ * \param[in] val value to set at each border pixel
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSetBorderRingVal()
+ *
+ * \param[in] pixs any depth; cmap OK
+ * \param[in] dist distance from outside; must be > 0; first ring is 1
+ * \param[in] val value to set at each border pixel
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSetMirroredBorder()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] left, right, top, bot number of pixels to set
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixCopyBorder()
+ *
+ * \param[in] pixd all depths; colormap ok; can be NULL
+ * \param[in] pixs same depth and size as pixd
+ * \param[in] left, right, top, bot number of pixels to copy
+ * \return pixd, or NULL on error if pixd is not defined
+ *
+ * <pre>
+ * Notes:
+ * (1) pixd can be null, but otherwise it must be the same size
+ * and depth as pixs. Always returns pixd.
+ * (2) 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.
+ * Nevertheless, for safety, if making a new pixd, all the
+ * non-border pixels are initialized to 0.
+ * </pre>
+ */
+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 = pixCreateTemplate(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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixAddBorder()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] npix number of pixels to be added to each side
+ * \param[in] val value of added border pixels
+ * \return pixd with the added exterior pixels, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixGetBlackOrWhiteVal() for values of black and white pixels.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixAddBlackOrWhiteBorder()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] left, right, top, bot number of pixels added
+ * \param[in] op L_GET_BLACK_VAL, L_GET_WHITE_VAL
+ * \return pixd with the added exterior pixels, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixAddBorderGeneral()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] left, right, top, bot number of pixels added
+ * \param[in] val value of added border pixels
+ * \return pixd with the added exterior pixels, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 = pixCreate(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;
+}
+
+
+/*!
+ * \brief pixRemoveBorder()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief pixRemoveBorderGeneral()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] 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 = pixCreate(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;
+}
+
+
+/*!
+ * \brief pixRemoveBorderToSize()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] wd target width; use 0 if only removing from height
+ * \param[in] hd target height; use 0 if only removing from width
+ * \return pixd with pixels removed around border, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixAddMirroredBorder()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] left, right, top, bot number of pixels added
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixAddRepeatedBorder()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] left, right, top, bot number of pixels added
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixAddMixedBorder()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] left, right, top, bot number of pixels added
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixAddContinuedBorder()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] left, right, top, bot pixels on each side to be added
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This adds pixels on each side whose values are equal to
+ * the value on the closest boundary pixel.
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------------*/
+/*!
+ * \brief pixShiftAndTransferAlpha()
+ *
+ * \param[in] pixd 32 bpp
+ * \param[in] pixs 32 bpp
+ * \param[in] shiftx, shifty
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixDisplayLayersRGBA()
+ *
+ * \param[in] pixs cmap or 32 bpp rgba
+ * \param[in] val 32 bit unsigned color to use as background
+ * \param[in] maxw max output image width; 0 for no scaling
+ * \return pixd showing various image views, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, (l_float32)(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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixCreateRGBImage()
+ *
+ * \param[in] pixr 8 bpp red pix
+ * \param[in] pixg 8 bpp green pix
+ * \param[in] pixb 8 bpp blue pix
+ * \return 32 bpp pix, interleaved with 4 samples/pixel,
+ * or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGetRGBComponent()
+ *
+ * \param[in] pixs 32 bpp, or colormapped
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixSetRGBComponent()
+ *
+ * \param[in] pixd 32 bpp
+ * \param[in] pixs 8 bpp
+ * \param[in] comp one of the set: {COLOR_RED, COLOR_GREEN,
+ * COLOR_BLUE, L_ALPHA_CHANNEL}
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixGetRGBComponentCmap()
+ *
+ * \param[in] pixs colormapped
+ * \param[in] 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
+ *
+ * <pre>
+ * Notes:
+ * (1) In leptonica, we do not support alpha in colormaps.
+ * </pre>
+ */
+PIX *
+pixGetRGBComponentCmap(PIX *pixs,
+ l_int32 comp)
+{
+l_int32 i, j, w, h, val, index, valid;
+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);
+ pixcmapIsValid(cmap, pixc, &valid);
+ if (!valid) {
+ pixDestroy(&pixc);
+ return (PIX *)ERROR_PTR("invalid colormap", procName, NULL);
+ }
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ if ((pixd = pixCreateNoInit(w, h, 8)) == NULL) {
+ pixDestroy(&pixc);
+ 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;
+}
+
+
+/*!
+ * \brief pixCopyRGBComponent()
+ *
+ * \param[in] pixd 32 bpp
+ * \param[in] pixs 32 bpp
+ * \param[in] comp one of the set: {COLOR_RED, COLOR_GREEN,
+ * COLOR_BLUE, L_ALPHA_CHANNEL}
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief composeRGBPixel()
+ *
+ * \param[in] rval, gval, bval
+ * \param[out] ppixel 32-bit pixel
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+l_ok
+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 = ((l_uint32)rval << L_RED_SHIFT) |
+ ((l_uint32)gval << L_GREEN_SHIFT) |
+ ((l_uint32)bval << L_BLUE_SHIFT);
+ return 0;
+}
+
+
+/*!
+ * \brief composeRGBAPixel()
+ *
+ * \param[in] rval, gval, bval, aval
+ * \param[out] ppixel 32-bit pixel
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 = ((l_uint32)rval << L_RED_SHIFT) |
+ ((l_uint32)gval << L_GREEN_SHIFT) |
+ ((l_uint32)bval << L_BLUE_SHIFT) |
+ aval;
+ return 0;
+}
+
+
+/*!
+ * \brief extractRGBValues()
+ *
+ * \param[in] pixel 32 bit
+ * \param[out] prval [optional] red component
+ * \param[out] pgval [optional] green component
+ * \param[out] pbval [optional] blue component
+ * \return void
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief extractRGBAValues()
+ *
+ * \param[in] pixel 32 bit
+ * \param[out] prval [optional] red component
+ * \param[out] pgval [optional] green component
+ * \param[out] pbval [optional] blue component
+ * \param[out] paval [optional] 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;
+}
+
+
+/*!
+ * \brief extractMinMaxComponent()
+ *
+ * \param[in] pixel 32 bpp RGB
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixGetRGBLine()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] row
+ * \param[in] bufr array of red samples; size w bytes
+ * \param[in] bufg array of green samples; size w bytes
+ * \param[in] bufb array of blue samples; size w bytes
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This puts rgb components from the input line in pixs
+ * into the given buffers.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*-------------------------------------------------------------*
+ * Raster line pixel setter *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief setLineDataVal()
+ *
+ * \param[in] line ptr to first word in raster line data
+ * \param[in] j index of pixels into the raster line
+ * \param[in] d depth of the pixel
+ * \param[in] val pixel value to be set
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a convenience function to set a pixel value in a
+ * raster line where the depth of the image can have different
+ * values (1, 2, 4, 8, 16 or 32).
+ * </pre>
+ */
+l_ok
+setLineDataVal(l_uint32 *line,
+ l_int32 j,
+ l_int32 d,
+ l_uint32 val)
+{
+ PROCNAME("setLineDataVal");
+
+ if (!line)
+ return ERROR_INT("line not defined", procName, 1);
+ if (j < 0)
+ return ERROR_INT("j must be >= 0", procName, 1);
+ if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+ return ERROR_INT("invalid d", procName, 1);
+
+ if (d == 1)
+ SET_DATA_BIT_VAL(line, j, val);
+ else if (d == 2)
+ SET_DATA_DIBIT(line, j, val);
+ else if (d == 4)
+ SET_DATA_QBIT(line, j, val);
+ else if (d == 8)
+ SET_DATA_BYTE(line, j, val);
+ else if (d == 16)
+ SET_DATA_TWO_BYTES(line, j, val);
+ else /* d == 32 */
+ *(line + j) = val;
+ return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ * Pixel endian conversion *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixEndianByteSwapNew()
+ *
+ * \param[in] pixs
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if ((pixd = pixCreateTemplate(pixs)) == NULL)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ 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 */
+
+}
+
+
+/*!
+ * \brief pixEndianByteSwap()
+ *
+ * \param[in] pixs
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 */
+
+}
+
+
+/*!
+ * \brief lineEndianByteSwap()
+ *
+ * \param[in] datad dest byte array data, reordered on little-endians
+ * \param[in] datas a src line of pix data)
+ * \param[in] wpl number of 32 bit words in the line
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 datad
+ * is addressed by bytes, the pixels are chosen sequentially
+ * from left to right in the image.
+ * </pre>
+ */
+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(datad, 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 */
+
+}
+
+
+/*!
+ * \brief pixEndianTwoByteSwapNew()
+ *
+ * \param[in] pixs
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if ((pixd = pixCreateTemplate(pixs)) == NULL)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ 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 */
+
+}
+
+
+/*!
+ * \brief pixEndianTwoByteSwap()
+ *
+ * \param[in] pixs
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixGetRasterData()
+ *
+ * \param[in] pixs 1, 8, 32 bpp
+ * \param[out] pdata raster data in memory
+ * \param[out] pnbytes number of bytes in data string
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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((size_t)databpl * h, sizeof(l_uint8)))
+ == NULL)
+ return ERROR_INT("data not allocated", procName, 1);
+ *pdata = data;
+ *pnbytes = (size_t)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;
+}
+
+
+/*-------------------------------------------------------------*
+ * Infer resolution from image size *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixInferResolution()
+ *
+ * \param[in] pix
+ * \param[in] longside assumed max dimension, in inches
+ * \param[out] pres resolution (ppi)
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds the resolution, assuming that the longest side
+ * of the image is %longside. On error, returns 300 ppi.
+ * (2) This is useful for computing resolution for generating pdfs,
+ * when the images are scanned from pages of known size.
+ * There, %longside is typically about 11.0.
+ * </pre>
+ */
+l_ok
+pixInferResolution(PIX *pix,
+ l_float32 longside,
+ l_int32 *pres)
+{
+l_int32 w, h, maxdim, res;
+
+ PROCNAME("pixInferResolution");
+
+ if (!pres)
+ return ERROR_INT("&res not defined", procName, 1);
+ *pres = 300;
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+ if (longside <= 0.0)
+ return ERROR_INT("longside not > 0", procName, 1);
+
+ pixGetDimensions(pix, &w, &h, NULL);
+ maxdim = L_MAX(w, h);
+ res = (l_int32)(maxdim / longside + 0.5);
+ res = L_MAX(res, 1); /* don't let it be 0 */
+ if (res < 10)
+ L_WARNING("low inferred resolution: %d ppi\n", procName, res);
+ if (res > 10000)
+ L_WARNING("high inferred resolution: %d ppi\n", procName, res);
+ *pres = res;
+ return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ * Test alpha component opaqueness *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixAlphaIsOpaque()
+ *
+ * \param[in] pix 32 bpp, spp == 4
+ * \param[out] popaque 1 if spp == 4 and all alpha component
+ * values are 255 (opaque); 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * 1) On error, opaque is returned as 0 (FALSE).
+ * </pre>
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixSetupByteProcessing()
+ *
+ * \param[in] pix 8 bpp, no colormap
+ * \param[out] pw [optional] width
+ * \param[out] ph [optional] height
+ * \return line ptr array, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixCleanupByteProcessing()
+ *
+ * \param[in] pix 8 bpp, no colormap
+ * \param[in] lineptrs ptrs to the beginning of each raster line of data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This must be called after processing that was initiated
+ * by pixSetupByteProcessing() has finished.
+ * </pre>
+ */
+l_ok
+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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief l_setAlphaMaskBorder()
+ *
+ * \param[in] val1, val2 in [0.0 ... 1.0]
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/pix3.c b/leptonica/src/pix3.c
new file mode 100644
index 00000000..67f6dea4
--- /dev/null
+++ b/leptonica/src/pix3.c
@@ -0,0 +1,3716 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pix3.c
+ * <pre>
+ *
+ * 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()
+ * l_int32 pixCopyWithBoxa() -- this is boxa-directed
+ * PIX *pixPaintSelfThroughMask()
+ * PIX *pixMakeMaskFromVal()
+ * PIX *pixMakeMaskFromLUT()
+ * PIX *pixMakeArbMaskFromRGB()
+ * PIX *pixSetUnderTransparency()
+ * PIX *pixMakeAlphaFromMask()
+ * l_int32 pixGetColorNearMaskBoundary()
+ * PIX *pixDisplaySelectedPixels() -- for debugging
+ *
+ * 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()
+ * l_int32 pixCountPixelsInRect()
+ * 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()
+ *
+ * Average of pixel values in RGB images
+ * l_int32 pixAverageInRectRGB()
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixSetMasked()
+ *
+ * \param[in] pixd 1, 2, 4, 8, 16 or 32 bpp; or colormapped
+ * \param[in] pixm [optional] 1 bpp mask; no operation if NULL
+ * \param[in] val value to set at each masked pixel
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSetMaskedGeneral()
+ *
+ * \param[in] pixd 8, 16 or 32 bpp
+ * \param[in] pixm [optional] 1 bpp mask; no operation if null
+ * \param[in] val value to set at each masked pixel
+ * \param[in] x, y location of UL corner of pixm relative to pixd;
+ * can be negative
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) {
+ pixDestroy(&pixmu);
+ 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;
+}
+
+
+/*!
+ * \brief pixCombineMasked()
+ *
+ * \param[in] pixd 1 bpp, 8 bpp gray or 32 bpp rgb; no cmap
+ * \param[in] pixs 1 bpp, 8 bpp gray or 32 bpp rgb; no cmap
+ * \param[in] pixm [optional] 1 bpp mask; no operation if NULL
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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!
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixCombineMaskedGeneral()
+ *
+ * \param[in] pixd 1 bpp, 8 bpp gray or 32 bpp rgb
+ * \param[in] pixs 1 bpp, 8 bpp gray or 32 bpp rgb
+ * \param[in] pixm [optional] 1 bpp mask
+ * \param[in] x, y origin of pixs and pixm relative to pixd; can be negative
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixPaintThroughMask()
+ *
+ * \param[in] pixd 1, 2, 4, 8, 16 or 32 bpp; or colormapped
+ * \param[in] pixm [optional] 1 bpp mask
+ * \param[in] x, y origin of pixm relative to pixd; can be negative
+ * \param[in] val pixel value to set at each masked pixel
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixCopyWithBoxa()
+ *
+ * \param[in] pixs all depths; cmap ok
+ * \param[in] boxa e.g., from components of a photomask
+ * \param[in] background L_SET_WHITE or L_SET_BLACK
+ * \return pixd or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Pixels from pixs are copied ("blitted") through each box into pixd.
+ * (2) Pixels not copied are preset to either white or black.
+ * (3) This fast and simple implementation can use rasterop because
+ * each region to be copied is rectangular.
+ * (4) A much slower implemention that doesn't use rasterop would make
+ * a 1 bpp mask from the boxa and then copy, pixel by pixel,
+ * through the mask:
+ * pixGetDimensions(pixs, &w, &h, NULL);
+ * pixm = pixCreate(w, h, 1);
+ * pixm = pixMaskBoxa(pixm, pixm, boxa);
+ * pixd = pixCreateTemplate(pixs);
+ * pixSetBlackOrWhite(pixd, background);
+ * pixCombineMasked(pixd, pixs, pixm);
+ * pixDestroy(&pixm);
+ * </pre>
+ */
+PIX *
+pixCopyWithBoxa(PIX *pixs,
+ BOXA *boxa,
+ l_int32 background)
+{
+l_int32 i, n, x, y, w, h;
+PIX *pixd;
+
+ PROCNAME("pixCopyWithBoxa");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+ if (!boxa)
+ return (PIX *)ERROR_PTR("boxa not defined", procName, NULL);
+ if (background != L_SET_WHITE && background != L_SET_BLACK)
+ return (PIX *)ERROR_PTR("invalid background", procName, NULL);
+
+ pixd = pixCreateTemplate(pixs);
+ pixSetBlackOrWhite(pixd, background);
+ n = boxaGetCount(boxa);
+ for (i = 0; i < n; i++) {
+ boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h);
+ pixRasterop(pixd, x, y, w, h, PIX_SRC, pixs, x, y);
+ }
+ return pixd;
+}
+
+
+/*!
+ * \brief pixPaintSelfThroughMask()
+ *
+ * \param[in] pixd 8 bpp gray or 32 bpp rgb; not colormapped
+ * \param[in] pixm 1 bpp mask
+ * \param[in] x, y origin of pixm relative to pixd; must not be negative
+ * \param[in] searchdir L_HORIZ, L_VERT or L_BOTH_DIRECTIONS
+ * \param[in] mindist min distance of nearest tile edge to box; >= 0
+ * \param[in] tilesize requested size for tiling; may be reduced
+ * \param[in] ntiles number of tiles tested in each row/column
+ * \param[in] distblend distance outside the fg used for blending with pixs
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixMakeMaskFromVal()
+ *
+ * \param[in] pixs 2, 4 or 8 bpp; can be colormapped
+ * \param[in] val pixel value
+ * \return pixd 1 bpp mask, or NULL on error
+ *
+ * <pre>
+ * 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 %val.
+ * (2) If no pixels have the value, an empty mask is generated.
+ * </pre>
+ */
+PIX *
+pixMakeMaskFromVal(PIX *pixs,
+ l_int32 val)
+{
+l_int32 w, h, d, i, j, sval, wpls, wpld;
+l_uint32 *datas, *datad, *lines, *lined;
+PIX *pixd;
+
+ PROCNAME("pixMakeMaskFromVal");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs 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)
+ sval = GET_DATA_DIBIT(lines, j);
+ else if (d == 4)
+ sval = GET_DATA_QBIT(lines, j);
+ else /* d == 8 */
+ sval = GET_DATA_BYTE(lines, j);
+ if (sval == val)
+ SET_DATA_BIT(lined, j);
+ }
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixMakeMaskFromLUT()
+ *
+ * \param[in] pixs 2, 4 or 8 bpp; can be colormapped
+ * \param[in] tab 256-entry LUT; 1 means to write to mask
+ * \return pixd 1 bpp mask, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixMakeArbMaskFromRGB()
+ *
+ * \param[in] pixs 32 bpp RGB
+ * \param[in] rc, gc, bc arithmetic factors; can be negative
+ * \param[in] thresh lower threshold on weighted sum of components
+ * \return pixd 1 bpp mask, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates a 1 bpp mask image, where a 1 is written in
+ * the mask for each pixel in pixs that satisfies
+ * rc * rval + gc * gval + bc * bval > thresh
+ * where rval is the red component, etc.
+ * (2) Unlike with pixConvertToGray(), there are no constraints
+ * on the color coefficients, which can be negative. For
+ * example, a mask that discriminates against red and in favor
+ * of blue will have rc < 0.0 and bc > 0.0.
+ * (3) To make the result independent of intensity (the 'V' in HSV),
+ * select coefficients so that %thresh = 0. Then the result
+ * is not changed when all components are multiplied by the
+ * same constant (as long as nothing saturates). This can be
+ * useful if, for example, the illumination is not uniform.
+ * </pre>
+ */
+PIX *
+pixMakeArbMaskFromRGB(PIX *pixs,
+ l_float32 rc,
+ l_float32 gc,
+ l_float32 bc,
+ l_float32 thresh)
+{
+PIX *pix1, *pix2;
+
+ PROCNAME("pixMakeArbMaskFromRGB");
+
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+ if (thresh >= 255.0) thresh = 254.0; /* avoid 8 bit overflow */
+
+ if ((pix1 = pixConvertRGBToGrayArb(pixs, rc, gc, bc)) == NULL)
+ return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+ pix2 = pixThresholdToBinary(pix1, thresh + 1);
+ pixInvert(pix2, pix2);
+ pixDestroy(&pix1);
+ return pix2;
+}
+
+
+/*!
+ * \brief pixSetUnderTransparency()
+ *
+ * \param[in] pixs 32 bpp rgba
+ * \param[in] val 32 bit unsigned color to use where alpha == 0
+ * \param[in] debug displays layers of pixs
+ * \return pixd 32 bpp rgba, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixMakeAlphaFromMask()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] dist blending distance; typically 10 - 30
+ * \param[out] pbox [optional] use NULL to get the full size
+ * \return pixd (8 bpp gray, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGetColorNearMaskBoundary()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] pixm 1 bpp mask, full image
+ * \param[in] box region of mask; typically b.b. of a component
+ * \param[in] dist distance into BG from mask boundary to use
+ * \param[out] pval average pixel value
+ * \param[in] debug 1 to output mask images
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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");
+ pixWriteDebug("/tmp/masknear/input.png", pix1, IFF_PNG);
+ pixWriteDebug("/tmp/masknear/adjusted.png", pix2, IFF_PNG);
+ pixWriteDebug("/tmp/masknear/outerfive.png", pix3, IFF_PNG);
+ lept_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;
+}
+
+
+/*!
+ * \brief pixDisplaySelectedPixels()
+ *
+ * \param[in] pixs [optional] any depth
+ * \param[in] pixm 1 bpp mask, aligned UL corner with %pixs
+ * \param[in] sel [optional] pattern to paint at each pixel in pixm
+ * \param[in] val rgb rendering of pattern
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For every fg pixel in %pixm, this paints the pattern in %sel
+ * in color %val on a copy of %pixs.
+ * (2) The implementation is to dilate %pixm by %sel, and then
+ * paint through the dilated mask onto %pixs.
+ * (3) If %pixs == NULL, it paints on a white image.
+ * (4) If %sel == NULL, it paints only the pixels in the input %pixm.
+ * (5) This visualization would typically be used in debugging.
+ * </pre>
+ */
+PIX *
+pixDisplaySelectedPixels(PIX *pixs,
+ PIX *pixm,
+ SEL *sel,
+ l_uint32 val)
+{
+l_int32 w, h;
+PIX *pix1, *pix2;
+
+ PROCNAME("pixDisplaySelectedPixels");
+
+ if (!pixm || pixGetDepth(pixm) != 1)
+ return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", procName, NULL);
+
+ if (pixs) {
+ pix1 = pixConvertTo32(pixs);
+ } else {
+ pixGetDimensions(pixm, &w, &h, NULL);
+ pix1 = pixCreate(w, h, 32);
+ pixSetAll(pix1);
+ }
+
+ if (sel)
+ pix2 = pixDilate(NULL, pixm, sel);
+ else
+ pix2 = pixClone(pixm);
+ pixSetMasked(pix1, pix2, val);
+ pixDestroy(&pix2);
+ return pix1;
+}
+
+
+/*-------------------------------------------------------------*
+ * One and two-image boolean ops on arbitrary depth images *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixInvert()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixOr()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs1,
+ * different from pixs1
+ * \param[in] pixs1 can be == pixd
+ * \param[in] pixs2 must be != pixd
+ * \return pixd always
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixAnd()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs1,
+ * different from pixs1
+ * \param[in] pixs1 can be == pixd
+ * \param[in] pixs2 must be != pixd
+ * \return pixd always
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixXor()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs1,
+ * different from pixs1
+ * \param[in] pixs1 can be == pixd
+ * \param[in] pixs2 must be != pixd
+ * \return pixd always
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixSubtract()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs1,
+ * equal to pixs2, or different from both pixs1 and pixs2
+ * \param[in] pixs1 can be == pixd
+ * \param[in] pixs2 can be == pixd
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixZero()
+ *
+ * \param[in] pix all depths; colormap OK
+ * \param[out] pempty 1 if all bits in image data field are 0; 0 otherwise
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixForegroundFraction()
+ *
+ * \param[in] pix 1 bpp
+ * \param[out] pfract fraction of ON pixels
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaCountPixels()
+ *
+ * \param[in] 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);
+
+ if ((na = numaCreate(n)) == NULL)
+ return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+ tab = makePixelSumTab8();
+ 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;
+}
+
+
+/*!
+ * \brief pixCountPixels()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[out] pcount count of ON pixels
+ * \param[in] tab8 [optional] 8-bit pixel lookup table
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+pixCountPixels(PIX *pixs,
+ 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 (!pixs || pixGetDepth(pixs) != 1)
+ return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+ tab = (tab8) ? tab8 : makePixelSumTab8();
+ pixGetDimensions(pixs, &w, &h, NULL);
+ wpl = pixGetWpl(pixs);
+ data = pixGetData(pixs);
+ 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;
+}
+
+
+/*!
+ * \brief pixCountPixelsInRect()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] box (can be null)
+ * \param[out] pcount count of ON pixels
+ * \param[in] tab8 [optional] 8-bit pixel lookup table
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+pixCountPixelsInRect(PIX *pixs,
+ BOX *box,
+ l_int32 *pcount,
+ l_int32 *tab8)
+{
+l_int32 bx, by, bw, bh;
+PIX *pix1;
+
+ PROCNAME("pixCountPixelsInRect");
+
+ if (!pcount)
+ return ERROR_INT("&count not defined", procName, 1);
+ *pcount = 0;
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+ if (box) {
+ boxGetGeometry(box, &bx, &by, &bw, &bh);
+ pix1 = pixCreate(bw, bh, 1);
+ pixRasterop(pix1, 0, 0, bw, bh, PIX_SRC, pixs, bx, by);
+ pixCountPixels(pix1, pcount, tab8);
+ pixDestroy(&pix1);
+ } else {
+ pixCountPixels(pixs, pcount, tab8);
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixCountByRow()
+ *
+ * \param[in] pix 1 bpp
+ * \param[in] box [optional] clipping box for count; can be null
+ * \return na of number of ON pixels by row, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) To resample for a bin size different from 1, use
+ * numaUniformSampling() on the result of this function.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCountByColumn()
+ *
+ * \param[in] pix 1 bpp
+ * \param[in] box [optional] clipping box for count; can be null
+ * \return na of number of ON pixels by column, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) To resample for a bin size different from 1, use
+ * numaUniformSampling() on the result of this function.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCountPixelsByRow()
+ *
+ * \param[in] pix 1 bpp
+ * \param[in] 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);
+
+ h = pixGetHeight(pix);
+ if ((na = numaCreate(h)) == NULL)
+ return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+
+ tab = (tab8) ? tab8 : makePixelSumTab8();
+ for (i = 0; i < h; i++) {
+ pixCountPixelsInRow(pix, i, &count, tab);
+ numaAddNumber(na, count);
+ }
+
+ if (!tab8) LEPT_FREE(tab);
+ return na;
+}
+
+
+/*!
+ * \brief pixCountPixelsByColumn()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixCountPixelsInRow()
+ *
+ * \param[in] pix 1 bpp
+ * \param[in] row number
+ * \param[out] pcount sum of ON pixels in raster line
+ * \param[in] tab8 [optional] 8-bit pixel lookup table
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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));
+
+ tab = (tab8) ? tab8 : makePixelSumTab8();
+ 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;
+}
+
+
+/*!
+ * \brief pixGetMomentByColumn()
+ *
+ * \param[in] pix 1 bpp
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixThresholdPixelSum()
+ *
+ * \param[in] pix 1 bpp
+ * \param[in] thresh threshold
+ * \param[out] pabove 1 if above threshold;
+ * 0 if equal to or less than threshold
+ * \param[in] tab8 [optional] 8-bit pixel lookup table
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+
+ tab = (tab8) ? tab8 : makePixelSumTab8();
+ 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;
+}
+
+
+/*!
+ * \brief makePixelSumTab8()
+ *
+ * \return table of 256 l_int32.
+ *
+ * <pre>
+ * Notes:
+ * (1) This table of integers gives the number of 1 bits
+ * in the 8 bit index.
+ * </pre>
+ */
+l_int32 *
+makePixelSumTab8(void)
+{
+l_uint8 byte;
+l_int32 i;
+l_int32 *tab;
+
+ tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ 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;
+}
+
+
+/*!
+ * \brief makePixelCentroidTab8()
+ *
+ * \return table of 256 l_int32.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_int32 *
+makePixelCentroidTab8(void)
+{
+l_int32 i;
+l_int32 *tab;
+
+ tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ 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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixAverageByRow()
+ *
+ * \param[in] pix 8 or 16 bpp; no colormap
+ * \param[in] box [optional] clipping box for sum; can be null
+ * \param[in] type L_WHITE_IS_MAX, L_BLACK_IS_MAX
+ * \return na of pixel averages by row, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixAverageByColumn()
+ *
+ * \param[in] pix 8 or 16 bpp; no colormap
+ * \param[in] box [optional] clipping box for sum; can be null
+ * \param[in] type L_WHITE_IS_MAX, L_BLACK_IS_MAX
+ * \return na of pixel averages by column, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixAverageInRect()
+ *
+ * \param[in] pixs 1, 2, 4, 8 bpp; not cmapped
+ * \param[in] pixm [optional] 1 bpp mask; if null, use all pixels
+ * \param[in] box [optional] if null, use entire image
+ * \param[in] minval ignore values less than this
+ * \param[in] maxval ignore values greater than this
+ * \param[in] subsamp subsample factor: integer; use 1 for all pixels
+ * \param[out] pave average of pixel values under consideration
+ * \return 0 if OK; 1 on error; 2 if all pixels are filtered out
+ *
+ * <pre>
+ * Notes:
+ * (1) The average is computed with 4 optional filters: a rectangle,
+ * a mask, a contiguous set of range values, and subsampling.
+ * In practice you might use only one or two of these.
+ * (2) The mask %pixm is a blocking mask: only count pixels in the bg.
+ * If it exists, alignment is assumed at UL corner and computation
+ * is over the minimum intersection of %pixs and %pixm.
+ * If you want the average of pixels under the mask fg, invert it.
+ * (3) Set the range limits %minval = 0 and %maxval = 255 to use
+ * all non-masked pixels (regardless of value) in the average.
+ * (4) If no pixels are used in the averaging, the returned average
+ * value is 0 and the function returns 2. This is not an error,
+ * but it says to disregard the returned average value.
+ * (5) For example, to average all pixels in a given clipping rect %box,
+ * pixAverageInRect(pixs, NULL, box, 0, 255, 1, &aveval);
+ * </pre>
+ */
+l_ok
+pixAverageInRect(PIX *pixs,
+ PIX *pixm,
+ BOX *box,
+ l_int32 minval,
+ l_int32 maxval,
+ l_int32 subsamp,
+ l_float32 *pave)
+{
+l_int32 w, h, d, wpls, wm, hm, dm, wplm, val, count;
+l_int32 i, j, xstart, xend, ystart, yend;
+l_uint32 *datas, *datam, *lines, *linem;
+l_float64 sum;
+
+ PROCNAME("pixAverageInRect");
+
+ if (!pave)
+ return ERROR_INT("&ave not defined", procName, 1);
+ *pave = 0;
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+ if (pixGetColormap(pixs) != NULL)
+ return ERROR_INT("pixs is colormapped", procName, 1);
+ pixGetDimensions(pixs, &w, &h, &d);
+ if (d != 1 && d != 2 && d != 4 && d != 8)
+ return ERROR_INT("pixs not 1, 2, 4 or 8 bpp", procName, 1);
+ if (pixm) {
+ pixGetDimensions(pixm, &wm, &hm, &dm);
+ if (dm != 1)
+ return ERROR_INT("pixm not 1 bpp", procName, 1);
+ w = L_MIN(w, wm);
+ h = L_MIN(h, hm);
+ }
+ if (subsamp < 1)
+ return ERROR_INT("subsamp must be >= 1", procName, 1);
+
+ if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+ NULL, NULL) == 1)
+ return ERROR_INT("invalid clipping box", procName, 1);
+
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+ if (pixm) {
+ datam = pixGetData(pixm);
+ wplm = pixGetWpl(pixm);
+ }
+ sum = 0.0;
+ count = 0;
+ for (i = ystart; i < yend; i += subsamp) {
+ lines = datas + i * wpls;
+ if (pixm)
+ linem = datam + i * wplm;
+ for (j = xstart; j < xend; j += subsamp) {
+ if (pixm && (GET_DATA_BIT(linem, j) == 1))
+ continue;
+ if (d == 1)
+ val = GET_DATA_BIT(lines, j);
+ else 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 (val >= minval && val <= maxval) {
+ sum += val;
+ count++;
+ }
+ }
+ }
+
+ if (count == 0)
+ return 2; /* not an error; don't use the average value (0.0) */
+ *pave = sum / (l_float32)count;
+ return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ * Average of pixel values in RGB images *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixAverageInRectRGB()
+ *
+ * \param[in] pixs rgb; not cmapped
+ * \param[in] pixm [optional] 1 bpp mask; if null, use all pixels
+ * \param[in] box [optional] if null, use entire image
+ * \param[in] subsamp subsample factor: integer; use 1 for all pixels
+ * \param[out] pave average color of pixel values under consideration,
+ * in format 0xrrggbb00.
+ * \return 0 if OK; 1 on error; 2 if all pixels are filtered out
+ *
+ * <pre>
+ * Notes:
+ * (1) The average is computed with 3 optional filters: a rectangle,
+ * a mask, and subsampling.
+ * In practice you might use only one or two of these.
+ * (2) The mask %pixm is a blocking mask: only count pixels in the bg.
+ * If it exists, alignment is assumed at UL corner and computation
+ * is over the minimum intersection of %pixs and %pixm.
+ * If you want the average of pixels under the mask fg, invert it.
+ * (3) If no pixels are used in the averaging, the returned average
+ * value is 0 and the function returns 2. This is not an error,
+ * but it says to disregard the returned average value.
+ * (4) For example, to average all pixels in a given clipping rect %box,
+ * pixAverageInRectRGB(pixs, NULL, box, 1, &aveval);
+ * </pre>
+ */
+l_ok
+pixAverageInRectRGB(PIX *pixs,
+ PIX *pixm,
+ BOX *box,
+ l_int32 subsamp,
+ l_uint32 *pave)
+{
+l_int32 w, h, wpls, wm, hm, dm, wplm, i, j, xstart, xend, ystart, yend;
+l_int32 rval, gval, bval, rave, gave, bave, count;
+l_uint32 *datas, *datam, *lines, *linem;
+l_uint32 pixel;
+l_float64 rsum, gsum, bsum;
+
+ PROCNAME("pixAverageInRectRGB");
+
+ if (!pave)
+ return ERROR_INT("&ave not defined", procName, 1);
+ *pave = 0;
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return ERROR_INT("pixs undefined or not 32 bpp", procName, 1);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ if (pixm) {
+ pixGetDimensions(pixm, &wm, &hm, &dm);
+ if (dm != 1)
+ return ERROR_INT("pixm not 1 bpp", procName, 1);
+ w = L_MIN(w, wm);
+ h = L_MIN(h, hm);
+ }
+ if (subsamp < 1)
+ return ERROR_INT("subsamp must be >= 1", procName, 1);
+
+ if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+ NULL, NULL) == 1)
+ return ERROR_INT("invalid clipping box", procName, 1);
+
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+ if (pixm) {
+ datam = pixGetData(pixm);
+ wplm = pixGetWpl(pixm);
+ }
+ rsum = gsum = bsum = 0.0;
+ count = 0;
+ for (i = ystart; i < yend; i += subsamp) {
+ lines = datas + i * wpls;
+ if (pixm)
+ linem = datam + i * wplm;
+ for (j = xstart; j < xend; j += subsamp) {
+ if (pixm && (GET_DATA_BIT(linem, j) == 1))
+ continue;
+ pixel = *(lines + j);
+ extractRGBValues(pixel, &rval, &gval, &bval);
+ rsum += rval;
+ gsum += gval;
+ bsum += bval;
+ count++;
+ }
+ }
+
+ if (count == 0)
+ return 2; /* not an error */
+ rave = (l_uint32)(rsum / (l_float64)count);
+ gave = (l_uint32)(gsum / (l_float64)count);
+ bave = (l_uint32)(bsum / (l_float64)count);
+ composeRGBPixel(rave, gave, bave, pave);
+ return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ * Variance of pixel values in gray images *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixVarianceByRow()
+ *
+ * \param[in] pix 8 or 16 bpp; no colormap
+ * \param[in] box [optional] clipping box for variance; can be null
+ * \return na of rmsdev by row, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 += (l_float64)(val) * val;
+ }
+ ave = norm * sum1;
+ var = norm * sum2 - ave * ave;
+ rootvar = sqrt(var);
+ numaAddNumber(na, (l_float32)rootvar);
+ }
+
+ return na;
+}
+
+
+/*!
+ * \brief pixVarianceByColumn()
+ *
+ * \param[in] pix 8 or 16 bpp; no colormap
+ * \param[in] box [optional] clipping box for variance; can be null
+ * \return na of rmsdev by column, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 += (l_float64)(val) * val;
+ }
+ ave = norm * sum1;
+ var = norm * sum2 - ave * ave;
+ rootvar = sqrt(var);
+ numaAddNumber(na, (l_float32)rootvar);
+ }
+
+ return na;
+}
+
+
+/*!
+ * \brief pixVarianceInRect()
+ *
+ * \param[in] pix 1, 2, 4, 8 bpp; not cmapped
+ * \param[in] box [optional] if null, use entire image
+ * \param[out] prootvar sqrt variance of pixel values in region
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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 += (l_float64)(val) * val;
+ } else if (d == 2) {
+ val = GET_DATA_DIBIT(line, j);
+ sum1 += val;
+ sum2 += (l_float64)(val) * val;
+ } else if (d == 4) {
+ val = GET_DATA_QBIT(line, j);
+ sum1 += val;
+ sum2 += (l_float64)(val) * val;
+ } else { /* d == 8 */
+ val = GET_DATA_BYTE(line, j);
+ sum1 += val;
+ sum2 += (l_float64)(val) * val;
+ }
+ }
+ }
+ norm = 1.0 / ((l_float64)(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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixAbsDiffByRow()
+ *
+ * \param[in] pix 8 bpp; no colormap
+ * \param[in] box [optional] clipping box for region; can be null
+ * \return na of abs val pixel difference averages by row, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixAbsDiffByColumn()
+ *
+ * \param[in] pix 8 bpp; no colormap
+ * \param[in] box [optional] clipping box for region; can be null
+ * \return na of abs val pixel difference averages by column,
+ * or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixAbsDiffInRect()
+ *
+ * \param[in] pix 8 bpp; not cmapped
+ * \param[in] box [optional] if null, use entire image
+ * \param[in] dir differences along L_HORIZONTAL_LINE or L_VERTICAL_LINE
+ * \param[out] pabsdiff average of abs diff pixel values in region
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixAbsDiffOnLine()
+ *
+ * \param[in] pix 8 bpp; not cmapped
+ * \param[in] x1, y1 first point; x1 <= x2, y1 <= y2
+ * \param[in] x2, y2 first point
+ * \param[out] pabsdiff average of abs diff pixel values on line
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixCountArbInRect()
+ *
+ * \param[in] pixs 8 bpp, or colormapped
+ * \param[in] box [optional] over which count is made;
+ * use entire image if NULL
+ * \param[in] val pixel value to count
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[out] pcount count; estimate it if factor > 1
+ * \return na histogram, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixMirroredTiling()
+ *
+ * \param[in] pixs 8 or 32 bpp, small tile; to be replicated
+ * \param[in] w, h dimensions of output pix
+ * \return pixd usually larger pix, mirror-tiled with pixs,
+ * or NULL on error
+ *
+ * <pre>
+ * 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 |
+ * -----------------
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFindRepCloseTile()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] box region of pixs to search around
+ * \param[in] searchdir L_HORIZ or L_VERT; direction to search
+ * \param[in] mindist min distance of selected tile edge from box; >= 0
+ * \param[in] tsize tile size; > 1; even; typically ~50
+ * \param[in] ntiles number of tiles tested in each row/column
+ * \param[out] pboxtile region of best tile
+ * \param[in] debug 1 for debug output
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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..
+ * </pre>
+ */
+l_ok
+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.33f, 0.34f, 0.33f);
+ 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) {
+ lept_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;
+}
+
+
+/*!
+ * \brief findTileRegionsForSearch()
+ *
+ * \param[in] box region of Pix to search around
+ * \param[in] w, h dimensions of Pix
+ * \param[in] searchdir L_HORIZ or L_VERT; direction to search
+ * \param[in] mindist min distance of selected tile edge from box; >= 0
+ * \param[in] tsize tile size; > 1; even; typically ~50
+ * \param[in] ntiles number of tiles tested in each row/column
+ * \return boxa if OK, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See calling function pixfindRepCloseTile().
+ * </pre>
+ */
+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/leptonica/src/pix4.c b/leptonica/src/pix4.c
new file mode 100644
index 00000000..1fcb4699
--- /dev/null
+++ b/leptonica/src/pix4.c
@@ -0,0 +1,3568 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pix4.c
+ * <pre>
+ *
+ * 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 pixCountRGBColorsByHash()
+ * l_int32 pixCountRGBColors()
+ * L_AMAP *pixGetColorAmapHistogram()
+ * l_int32 amapGetCountForColor()
+ * l_int32 pixGetRankValue()
+ * l_int32 pixGetRankValueMaskedRGB()
+ * l_int32 pixGetRankValueMasked()
+ * l_int32 pixGetPixelAverage()
+ * l_int32 pixGetPixelStats()
+ * l_int32 pixGetAverageMaskedRGB()
+ * l_int32 pixGetAverageMasked()
+ * l_int32 pixGetAverageTiledRGB()
+ * PIX *pixGetAverageTiled()
+ * NUMA *pixRowStats()
+ * NUMA *pixColumnStats()
+ * l_int32 pixGetRangeValues()
+ * l_int32 pixGetExtremeValue()
+ * l_int32 pixGetMaxValueInRect()
+ * l_int32 pixGetMaxColorIndex()
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+
+/*------------------------------------------------------------------*
+ * Pixel histogram and averaging *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixGetGrayHistogram()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16 bpp; can be colormapped
+ * \param[in] factor subsampling factor; integer >= 1
+ * \return na histogram, or NULL on error
+ *
+ * <pre>
+ * 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) Set the subsampling factor > 1 to reduce the amount of computation.
+ * </pre>
+ */
+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) {
+ pixDestroy(&pixg);
+ 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;
+ if (d == 2) {
+ for (j = 0; j < w; j += factor) {
+ val = GET_DATA_DIBIT(line, j);
+ array[val] += 1.0;
+ }
+ } else if (d == 4) {
+ for (j = 0; j < w; j += factor) {
+ val = GET_DATA_QBIT(line, j);
+ array[val] += 1.0;
+ }
+ } else if (d == 8) {
+ for (j = 0; j < w; j += factor) {
+ val = GET_DATA_BYTE(line, j);
+ array[val] += 1.0;
+ }
+ } else { /* d == 16 */
+ for (j = 0; j < w; j += factor) {
+ val = GET_DATA_TWO_BYTES(line, j);
+ array[val] += 1.0;
+ }
+ }
+ }
+
+ pixDestroy(&pixg);
+ return na;
+}
+
+
+/*!
+ * \brief pixGetGrayHistogramMasked()
+ *
+ * \param[in] pixs 8 bpp, or colormapped
+ * \param[in] pixm [optional] 1 bpp mask over which histogram is
+ * to be computed; use all pixels if null
+ * \param[in] x, y UL corner of pixm relative to the UL corner of pixs;
+ * can be < 0; these values are ignored if pixm is null
+ * \param[in] factor subsampling factor; integer >= 1
+ * \return na histogram, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGetGrayHistogramInRect()
+ *
+ * \param[in] pixs 8 bpp, or colormapped
+ * \param[in] box [optional] over which histogram is to be computed;
+ * use full image if NULL
+ * \param[in] factor subsampling factor; integer >= 1
+ * \return na histogram, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGetGrayHistogramTiled()
+ *
+ * \param[in] pixs any depth, colormap OK
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[in] nx, ny tiling; >= 1; typically small
+ * \return naa set of histograms, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGetColorHistogram()
+ *
+ * \param[in] pixs rgb or colormapped
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[out] pnar red histogram
+ * \param[out] pnag green histogram
+ * \param[out] pnab blue histogram
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixGetColorHistogramMasked()
+ *
+ * \param[in] pixs 32 bpp rgb, or colormapped
+ * \param[in] pixm [optional] 1 bpp mask over which histogram is
+ * to be computed; use all pixels if null
+ * \param[in] x, y UL corner of pixm relative to the UL corner of pixs;
+ * can be < 0; these values are ignored if pixm is null
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[out] pnar red histogram
+ * \param[out] pnag green histogram
+ * \param[out] pnab blue histogram
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixGetCmapHistogram()
+ *
+ * \param[in] pixs colormapped: d = 2, 4 or 8
+ * \param[in] factor subsampling factor; integer >= 1
+ * \return na histogram of cmap indices, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGetCmapHistogramMasked()
+ *
+ * \param[in] pixs colormapped: d = 2, 4 or 8
+ * \param[in] pixm [optional] 1 bpp mask over which histogram is
+ * to be computed; use all pixels if null
+ * \param[in] x, y UL corner of pixm relative to the UL corner of pixs;
+ * can be < 0; these values are ignored if pixm is null
+ * \param[in] factor subsampling factor; integer >= 1
+ * \return na histogram, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGetCmapHistogramInRect()
+ *
+ * \param[in] pixs colormapped: d = 2, 4 or 8
+ * \param[in] box [optional] over which histogram is to be computed;
+ * use full image if NULL
+ * \param[in] factor subsampling factor; integer >= 1
+ * \return na histogram, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCountRGBColorsByHash()
+ *
+ * \param[in] pixs rgb or rgba
+ * \param[out] pncolors number of colors found
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is about 3x faster than pixCountRGBColors(),
+ * which uses an ordered map.
+ * </pre>
+ */
+l_ok
+pixCountRGBColorsByHash(PIX *pixs,
+ l_int32 *pncolors)
+{
+L_DNA *da1, *da2;
+
+ PROCNAME("pixCountRGBColorsByHash");
+
+ if (!pncolors)
+ return ERROR_INT("&ncolors not defined", procName, 1);
+ *pncolors = 0;
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return ERROR_INT("pixs not defined or not 32 bpp", procName, 1);
+ da1 = pixConvertDataToDna(pixs);
+ l_dnaRemoveDupsByHash(da1, &da2, NULL);
+ *pncolors = l_dnaGetCount(da2);
+ l_dnaDestroy(&da1);
+ l_dnaDestroy(&da2);
+ return 0;
+}
+
+
+/*!
+ * \brief pixCountRGBColors()
+ *
+ * \param[in] pixs rgb or rgba
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[out] pncolors number of colors found
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %factor == 1, this gives the exact number of colors.
+ * (2) This is about 3x slower than pixCountRGBColorsByHash().
+ * </pre>
+ */
+l_ok
+pixCountRGBColors(PIX *pixs,
+ l_int32 factor,
+ l_int32 *pncolors)
+{
+L_AMAP *amap;
+
+ PROCNAME("pixCountRGBColors");
+
+ if (!pncolors)
+ return ERROR_INT("&ncolors not defined", procName, 1);
+ *pncolors = 0;
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return ERROR_INT("pixs not defined or not 32 bpp", procName, 1);
+ if (factor <= 0)
+ return ERROR_INT("factor must be > 0", procName, 1);
+ amap = pixGetColorAmapHistogram(pixs, factor);
+ *pncolors = l_amapSize(amap);
+ l_amapDestroy(&amap);
+ return 0;
+}
+
+
+/*!
+ * \brief pixGetColorAmapHistogram()
+ *
+ * \param[in] pixs rgb or rgba
+ * \param[in] factor subsampling factor; integer >= 1
+ * \return amap, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates an ordered map from pixel value to histogram count.
+ * (2) Use amapGetCountForColor() to use the map to look up a count.
+ * </pre>
+ */
+L_AMAP *
+pixGetColorAmapHistogram(PIX *pixs,
+ l_int32 factor)
+{
+l_int32 i, j, w, h, wpl;
+l_uint32 *data, *line;
+L_AMAP *amap;
+RB_TYPE key, value;
+RB_TYPE *pval;
+
+ PROCNAME("pixGetColorAmapHistogram");
+
+ if (!pixs)
+ return (L_AMAP *)ERROR_PTR("pixs not defined", procName, NULL);
+ if (pixGetDepth(pixs) != 32)
+ return (L_AMAP *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+ if (factor <= 0)
+ return (L_AMAP *)ERROR_PTR("factor must be > 0", procName, NULL);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ data = pixGetData(pixs);
+ wpl = pixGetWpl(pixs);
+ amap = l_amapCreate(L_UINT_TYPE);
+ for (i = 0; i < h; i += factor) {
+ line = data + i * wpl;
+ for (j = 0; j < w; j += factor) {
+ key.utype = line[j];
+ pval = l_amapFind(amap, key);
+ if (!pval)
+ value.itype = 1;
+ else
+ value.itype = 1 + pval->itype;
+ l_amapInsert(amap, key, value);
+ }
+ }
+
+ return amap;
+}
+
+
+/*!
+ * \brief amapGetCountForColor()
+ *
+ * \param[in] amap map from pixel value to count
+ * \param[in] val rgb or rgba pixel value
+ * \return count, or -1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The ordered map is made by pixGetColorAmapHistogram().
+ * </pre>
+ */
+l_int32
+amapGetCountForColor(L_AMAP *amap,
+ l_uint32 val)
+{
+RB_TYPE key;
+RB_TYPE *pval;
+
+ PROCNAME("amapGetCountForColor");
+
+ if (!amap)
+ return ERROR_INT("amap not defined", procName, -1);
+
+ key.utype = val;
+ pval = l_amapFind(amap, key);
+ return (pval) ? pval->itype : 0;
+}
+
+
+/*!
+ * \brief pixGetRankValue()
+ *
+ * \param[in] pixs 8 bpp, 32 bpp or colormapped
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[in] rank between 0.0 and 1.0; 1.0 is brightest, 0.0 is darkest
+ * \param[out] pvalue pixel value corresponding to input rank
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Simple function to get a rank value (color) 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().
+ * (2) For RGB, this treats each color component independently.
+ * It calls pixGetGrayHistogramMasked() on each component, and
+ * uses the returned gray histogram to get the rank value.
+ * It then combines the 3 rank values into a color pixel.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixGetRankValueMaskedRGB()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] pixm [optional] 1 bpp mask over which rank val is to be taken;
+ * use all pixels if null
+ * \param[in] x, y UL corner of pixm relative to the UL corner of pixs;
+ * can be < 0; these values are ignored if pixm is null
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[in] rank between 0.0 and 1.0; 1.0 is brightest, 0.0 is darkest
+ * \param[out] prval [optional] red component val for input rank
+ * \param[out] pgval [optional] green component val for input rank
+ * \param[out] pbval [optional] blue component val for input rank
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixGetRankValueMasked()
+ *
+ * \param[in] pixs 8 bpp, or colormapped
+ * \param[in] pixm [optional] 1 bpp mask, over which the rank val
+ * is to be taken; use all pixels if null
+ * \param[in] x, y UL corner of pixm relative to the UL corner of pixs;
+ * can be < 0; these values are ignored if pixm is null
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[in] rank between 0.0 and 1.0; 1.0 is brightest, 0.0 is darkest
+ * \param[out] pval pixel value corresponding to input rank
+ * \param[out] pna [optional] of histogram
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixGetPixelAverage()
+ *
+ * \param[in] pixs 8 or 32 bpp, or colormapped
+ * \param[in] pixm [optional] 1 bpp mask over which average is
+ * to be taken; use all pixels if null
+ * \param[in] x, y UL corner of pixm relative to the UL corner of pixs;
+ * can be < 0
+ * \param[in] factor subsampling factor; >= 1
+ * \param[out] pval average pixel value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For rgb pix, this is a more direct computation of the
+ * average value of the pixels in %pixs that are under the
+ * mask %pixm. It is faster than pixGetPixelStats(), which
+ * calls pixGetAverageMaskedRGB() and has the overhead of
+ * generating a temporary pix of each of the three components;
+ * this can take most of the time if %factor > 1.
+ * (2) If %pixm is null, this gives the average value of all
+ * pixels in %pixs. The returned value is an integer.
+ * (3) For color %pixs, the returned pixel value is in the standard
+ * uint32 RGBA packing.
+ * (4) Clipping of pixm (if it exists) to pixs is done in the inner loop.
+ * (5) Input x,y are ignored if %pixm does not exist.
+ * (6) For general averaging of 1, 2, 4 or 8 bpp grayscale, use
+ * pixAverageInRect().
+ * </pre>
+ */
+l_ok
+pixGetPixelAverage(PIX *pixs,
+ PIX *pixm,
+ l_int32 x,
+ l_int32 y,
+ l_int32 factor,
+ l_uint32 *pval)
+{
+l_int32 i, j, w, h, d, wm, hm, wpl1, wplm, val, rval, gval, bval, count;
+l_uint32 *data1, *datam, *line1, *linem;
+l_float64 sum, rsum, gsum, bsum;
+PIX *pix1;
+
+ PROCNAME("pixGetPixelAverage");
+
+ if (!pval)
+ return ERROR_INT("&val not defined", procName, 1);
+ *pval = 0;
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+ d = pixGetDepth(pixs);
+ if (d != 32 && !pixGetColormap(pixs))
+ return ERROR_INT("pixs not rgb 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 (pixGetColormap(pixs))
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+ else
+ pix1 = pixClone(pixs);
+ pixGetDimensions(pix1, &w, &h, &d);
+ if (d == 1) {
+ pixDestroy(&pix1);
+ return ERROR_INT("pix1 is just 1 bpp", procName, 1);
+ }
+ data1 = pixGetData(pix1);
+ wpl1 = pixGetWpl(pix1);
+
+ sum = rsum = gsum = bsum = 0.0;
+ count = 0;
+ if (!pixm) {
+ for (i = 0; i < h; i += factor) {
+ line1 = data1 + i * wpl1;
+ for (j = 0; j < w; j += factor) {
+ if (d == 8) {
+ val = GET_DATA_BYTE(line1, j);
+ sum += val;
+ } else { /* rgb */
+ extractRGBValues(*(line1 + j), &rval, &gval, &bval);
+ rsum += rval;
+ gsum += gval;
+ bsum += bval;
+ }
+ count++;
+ }
+ }
+ } else { /* masked */
+ 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;
+ line1 = data1 + (y + i) * wpl1;
+ 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(line1, x + j);
+ sum += val;
+ } else { /* rgb */
+ extractRGBValues(*(line1 + x + j), &rval, &gval, &bval);
+ rsum += rval;
+ gsum += gval;
+ bsum += bval;
+ }
+ count++;
+ }
+ }
+ }
+ }
+
+ pixDestroy(&pix1);
+ if (count == 0)
+ return ERROR_INT("no pixels sampled", procName, 1);
+ if (d == 8) {
+ *pval = (l_uint32)(sum / (l_float64)count);
+ } else { /* d == 32 */
+ rval = (l_uint32)(rsum / (l_float64)count);
+ gval = (l_uint32)(gsum / (l_float64)count);
+ bval = (l_uint32)(bsum / (l_float64)count);
+ composeRGBPixel(rval, gval, bval, pval);
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixGetPixelStats()
+ *
+ * \param[in] pixs 8 bpp, 32 bpp or colormapped
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[in] type L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE,
+ * L_STANDARD_DEVIATION, L_VARIANCE
+ * \param[out] pvalue pixel value corresponding to input type
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Simple function to get one of four statistical values of an image.
+ * (2) It does not take a mask: it uses the entire image.
+ * (3) To get the average pixel value of an RGB image, suggest using
+ * pixGetPixelAverage(), which is considerably faster.
+ * </pre>
+ */
+l_ok
+pixGetPixelStats(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("pixGetPixelStats");
+
+ 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;
+}
+
+
+/*!
+ * \brief pixGetAverageMaskedRGB()
+ *
+ * \param[in] pixs 32 bpp, or colormapped
+ * \param[in] pixm [optional] 1 bpp mask over which average is
+ * to be taken; use all pixels if null
+ * \param[in] x, y UL corner of pixm relative to the UL corner of pixs;
+ * can be < 0
+ * \param[in] factor subsampling factor; >= 1
+ * \param[in] type L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE,
+ * L_STANDARD_DEVIATION, L_VARIANCE
+ * \param[out] prval [optional] measured red value of given 'type'
+ * \param[out] pgval [optional] measured green value of given 'type'
+ * \param[out] pbval [optional] measured blue value of given 'type'
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For usage, see pixGetAverageMasked().
+ * (2) If there is a colormap, it is removed before the 8 bpp
+ * component images are extracted.
+ * (3) A better name for this would be: pixGetPixelStatsRGB()
+ * </pre>
+ */
+l_ok
+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)
+{
+l_int32 empty;
+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 (pixm) {
+ pixZero(pixm, &empty);
+ if (empty)
+ return ERROR_INT("empty mask", 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;
+}
+
+
+/*!
+ * \brief pixGetAverageMasked()
+ *
+ * \param[in] pixs 8 or 16 bpp, or colormapped
+ * \param[in] pixm [optional] 1 bpp mask over which average is
+ * to be taken; use all pixels if null
+ * \param[in] x, y UL corner of pixm relative to the UL corner of pixs;
+ * can be < 0
+ * \param[in] factor subsampling factor; >= 1
+ * \param[in] type L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE,
+ * L_STANDARD_DEVIATION, L_VARIANCE
+ * \param[out] pval measured value of given 'type'
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * (6) A better name for this would be: pixGetPixelStatsGray()
+ * </pre>
+ */
+l_ok
+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, empty;
+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 (pixm) {
+ pixZero(pixm, &empty);
+ if (empty)
+ return ERROR_INT("empty mask", 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 += (l_float64)(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 += (l_float64)(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;
+}
+
+
+/*!
+ * \brief pixGetAverageTiledRGB()
+ *
+ * \param[in] pixs 32 bpp, or colormapped
+ * \param[in] sx, sy tile size; must be at least 2 x 2
+ * \param[in] type L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE, L_STANDARD_DEVIATION
+ * \param[out] ppixr [optional] tiled 'average' of red component
+ * \param[out] ppixg [optional] tiled 'average' of green component
+ * \param[out] ppixb [optional] tiled 'average' of blue component
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For usage, see pixGetAverageTiled().
+ * (2) If there is a colormap, it is removed before the 8 bpp
+ * component images are extracted.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixGetAverageTiled()
+ *
+ * \param[in] pixs 8 bpp, or colormapped
+ * \param[in] sx, sy tile size; must be at least 2 x 2
+ * \param[in] type L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE, L_STANDARD_DEVIATION
+ * \return pixd average values in each tile, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 += (l_float64)(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;
+}
+
+
+/*!
+ * \brief pixRowStats()
+ *
+ * \param[in] pixs 8 bpp; not cmapped
+ * \param[in] box [optional] clipping box; can be null
+ * \param[out] pnamean [optional] numa of mean values
+ * \param[out] pnamedian [optional] numa of median values
+ * \param[out] pnamode [optional] numa of mode intensity values
+ * \param[out] pnamodecount [optional] numa of mode counts
+ * \param[out] pnavar [optional] numa of variance
+ * \param[out] pnarootvar [optional] numa of square root of variance
+ * \return na numa of requested statistic for each row, or NULL on error
+ *
+ * <pre>
+ * 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()
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixColumnStats()
+ *
+ * \param[in] pixs 8 bpp; not cmapped
+ * \param[in] box [optional] clipping box; can be null
+ * \param[out] pnamean [optional] numa of mean values
+ * \param[out] pnamedian [optional] numa of median values
+ * \param[out] pnamode [optional] numa of mode intensity values
+ * \param[out] pnamodecount [optional] numa of mode counts
+ * \param[out] pnavar [optional] numa of variance
+ * \param[out] pnarootvar [optional] numa of square root of variance
+ * \return na numa of requested statistic for each column,
+ * or NULL on error
+ *
+ * <pre>
+ * 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()
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGetRangeValues()
+ *
+ * \param[in] pixs 8 bpp grayscale, 32 bpp rgb, or colormapped
+ * \param[in] factor subsampling factor; >= 1; ignored if colormapped
+ * \param[in] color L_SELECT_RED, L_SELECT_GREEN or L_SELECT_BLUE
+ * \param[out] pminval [optional] minimum value of component
+ * \param[out] pmaxval [optional] maximum value of component
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If pixs is 8 bpp grayscale, the color selection type is ignored.
+ * </pre>
+ */
+l_ok
+pixGetRangeValues(PIX *pixs,
+ l_int32 factor,
+ l_int32 color,
+ l_int32 *pminval,
+ l_int32 *pmaxval)
+{
+l_int32 d;
+PIXCMAP *cmap;
+
+ PROCNAME("pixGetRangeValues");
+
+ 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 pixcmapGetRangeValues(cmap, color, pminval, pmaxval,
+ NULL, NULL);
+
+ 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;
+}
+
+
+/*!
+ * \brief pixGetExtremeValue()
+ *
+ * \param[in] pixs 8 bpp grayscale, 32 bpp rgb, or colormapped
+ * \param[in] factor subsampling factor; >= 1; ignored if colormapped
+ * \param[in] type L_SELECT_MIN or L_SELECT_MAX
+ * \param[out] prval [optional] red component
+ * \param[out] pgval [optional] green component
+ * \param[out] pbval [optional] blue component
+ * \param[out] pgrayval [optional] min or max gray value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 = -1;
+ if (pgval) *pgval = -1;
+ if (pbval) *pbval = -1;
+ if (pgrayval) *pgrayval = -1;
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+ if (type != L_SELECT_MIN && type != L_SELECT_MAX)
+ return ERROR_INT("invalid type", procName, 1);
+
+ cmap = pixGetColormap(pixs);
+ if (cmap) {
+ if (type == L_SELECT_MIN) {
+ if (prval) pixcmapGetRangeValues(cmap, L_SELECT_RED, prval, NULL,
+ NULL, NULL);
+ if (pgval) pixcmapGetRangeValues(cmap, L_SELECT_GREEN, pgval, NULL,
+ NULL, NULL);
+ if (pbval) pixcmapGetRangeValues(cmap, L_SELECT_BLUE, pbval, NULL,
+ NULL, NULL);
+ } else { /* type == L_SELECT_MAX */
+ if (prval) pixcmapGetRangeValues(cmap, L_SELECT_RED, NULL, prval,
+ NULL, NULL);
+ if (pgval) pixcmapGetRangeValues(cmap, L_SELECT_GREEN, NULL, pgval,
+ NULL, NULL);
+ if (pbval) pixcmapGetRangeValues(cmap, L_SELECT_BLUE, NULL, pbval,
+ NULL, NULL);
+ }
+ return 0;
+ }
+
+ pixGetDimensions(pixs, &w, &h, &d);
+ 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 = -1;
+
+ 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 = -1;
+ extgval = -1;
+ extbval = -1;
+ }
+ 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;
+}
+
+
+/*!
+ * \brief pixGetMaxValueInRect()
+ *
+ * \param[in] pixs 8, 16 or 32 bpp grayscale; no color space components
+ * \param[in] box [optional] region; set box = NULL to use entire pixs
+ * \param[out] pmaxval [optional] max value in region
+ * \param[out] pxmax [optional] x location of max value
+ * \param[out] pymax [optional] y location of max value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixGetMaxColorIndex()
+ *
+ * \param[in] pixs 1, 2, 4 or 8 bpp colormapped
+ * \param[out] pmaxindex max colormap index value
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixGetMaxColorIndex(PIX *pixs,
+ l_int32 *pmaxindex)
+{
+l_int32 i, j, w, h, d, wpl, val, max, maxval, empty;
+l_uint32 *data, *line;
+
+ PROCNAME("pixGetMaxColorIndex");
+
+ if (!pmaxindex)
+ return ERROR_INT("&maxindex not defined", procName, 1);
+ *pmaxindex = 0;
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+ pixGetDimensions(pixs, &w, &h, &d);
+ if (d != 1 && d != 2 && d != 4 && d != 8)
+ return ERROR_INT("invalid pixs depth; not in (1,2,4,8}", procName, 1);
+
+ wpl = pixGetWpl(pixs);
+ data = pixGetData(pixs);
+ max = 0;
+ maxval = (1 << d) - 1;
+ if (d == 1) {
+ pixZero(pixs, &empty);
+ if (!empty) max = 1;
+ *pmaxindex = max;
+ return 0;
+ }
+ for (i = 0; i < h; i++) {
+ line = data + i * wpl;
+ if (d == 2) {
+ for (j = 0; j < w; j++) {
+ val = GET_DATA_DIBIT(line, j);
+ if (val > max) max = val;
+ }
+ } else if (d == 4) {
+ for (j = 0; j < w; j++) {
+ val = GET_DATA_QBIT(line, j);
+ if (val > max) max = val;
+ }
+ } else if (d == 8) {
+ for (j = 0; j < w; j++) {
+ val = GET_DATA_BYTE(line, j);
+ if (val > max) max = val;
+ }
+ }
+ if (max == maxval) break;
+ }
+ *pmaxindex = max;
+ return 0;
+}
+
+
+/*!
+ * \brief pixGetBinnedComponentRange()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] nbins number of equal population bins; must be > 1
+ * \param[in] factor subsampling factor; >= 1
+ * \param[in] color L_SELECT_RED, L_SELECT_GREEN or L_SELECT_BLUE
+ * \param[out] pminval [optional] minimum value of component
+ * \param[out] pmaxval [optional] maximum value of component
+ * \param[out] pcarray [optional] color array of bins
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, NULL, 0);
+ if (!carray)
+ return ERROR_INT("carray not made", procName, 1);
+
+ 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;
+}
+
+
+/*!
+ * \brief pixGetRankColorArray()
+ *
+ * \param[in] pixs 32 bpp or cmapped
+ * \param[in] nbins number of equal population bins; must be > 1
+ * \param[in] type color selection flag
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[out] pcarray array of colors, ranked by intensity
+ * \param[in] pixadb [optional] debug: caller passes this in.
+ * Use to display color squares and to
+ * capture plots of color components
+ * \param[in] fontsize [optional] debug: only used if pixadb exists.
+ * Valid set is {4,6,8,10,12,14,16,18,20}.
+ * fontsize == 6 is typical.
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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,
+ * L_SELECT_HUE, L_SELECT_SATURATION.
+ * (2) The pixels are ordered by the value of the selected color
+ value, and an equal number are placed in %nbins. The average
+ * color in each bin is returned in a color array with %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. Must be at least 10 samples/bin.
+ * (4) A crude "rank color" as a function of rank can 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].
+ * </pre>
+ */
+l_ok
+pixGetRankColorArray(PIX *pixs,
+ l_int32 nbins,
+ l_int32 type,
+ l_int32 factor,
+ l_uint32 **pcarray,
+ PIXA *pixadb,
+ l_int32 fontsize)
+{
+l_int32 ret, w, h, samplesperbin;
+l_uint32 *array;
+PIX *pix1, *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 &&
+ type != L_SELECT_HUE && type != L_SELECT_SATURATION)
+ return ERROR_INT("invalid type", procName, 1);
+ if (pixadb) {
+ if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2) {
+ L_WARNING("invalid fontsize %d; setting to 6\n", procName,
+ fontsize);
+ fontsize = 6;
+ }
+ }
+ pixGetDimensions(pixs, &w, &h, NULL);
+ samplesperbin = (w * h) / (factor * factor * nbins);
+ if (samplesperbin < 10) {
+ L_ERROR("samplesperbin = %d < 10\n", procName, samplesperbin);
+ return 1;
+ }
+
+ /* Downscale by factor and remove colormap if it exists */
+ pix1 = pixScaleByIntSampling(pixs, factor);
+ if (cmap)
+ pixc = pixRemoveColormap(pix1, REMOVE_CMAP_TO_FULL_COLOR);
+ else
+ pixc = pixClone(pix1);
+ pixDestroy(&pix1);
+
+ /* Convert to an 8 bit version for ordering the pixels */
+ pixg = pixConvertRGBToGrayGeneral(pixc, type, 0.0, 0.0, 0.0);
+
+ /* Get the average color in each bin for pixels whose grayscale
+ * values are in the range for that bin. */
+ pixGetBinnedColor(pixc, pixg, 1, nbins, pcarray, pixadb);
+ ret = 0;
+ if ((array = *pcarray) == NULL) {
+ L_ERROR("color array not returned\n", procName);
+ ret = 1;
+ }
+ if (array && pixadb) {
+ pixd = pixDisplayColorArray(array, nbins, 200, 5, fontsize);
+ pixWriteDebug("/tmp/lept/regout/rankhisto.png", pixd, IFF_PNG);
+ pixDestroy(&pixd);
+ }
+
+ pixDestroy(&pixc);
+ pixDestroy(&pixg);
+ return ret;
+}
+
+
+/*!
+ * \brief pixGetBinnedColor()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] pixg 8 bpp grayscale version of pixs
+ * \param[in] factor sampling factor along pixel counting direction
+ * \param[in] nbins number of bins based on grayscale value {1,...,100}
+ * \param[out] pcarray array of average color values in each bin
+ * \param[in] pixadb [optional] debug: caller passes this in.
+ * Use to display output color squares and plots of
+ * color components.
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This takes a color image, a grayscale version, and the number
+ * of requested bins. The pixels are ordered by the corresponding
+ * gray value and an equal number of pixels are put in each bin.
+ * The average color for each bin is returned as an array
+ * of l_uint32 colors in our standard RGBA ordering. We require
+ * at least 5 pixels in each bin.
+ * (2) This is used by pixGetRankColorArray(), which generates the
+ * grayscale image %pixg from the color image %pixs.
+ * (3) Arrays of float64 are used for intermediate storage, without
+ * loss of precision, of the sampled uint32 pixel values.
+ * </pre>
+ */
+l_ok
+pixGetBinnedColor(PIX *pixs,
+ PIX *pixg,
+ l_int32 factor,
+ l_int32 nbins,
+ l_uint32 **pcarray,
+ PIXA *pixadb)
+{
+l_int32 i, j, k, w, h, wpls, wplg;
+l_int32 count, bincount, binindex, binsize, npts, avepts, ntot;
+l_int32 rval, gval, bval, grayval, rave, gave, bave;
+l_uint32 *datas, *datag, *lines, *lineg, *carray;
+l_float64 val64, rsum, gsum, bsum;
+L_DNAA *daa;
+NUMA *naeach;
+PIX *pix1;
+
+ PROCNAME("pixGetBinnedColor");
+
+ if (!pcarray)
+ return ERROR_INT("&carray not defined", procName, 1);
+ *pcarray = NULL;
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return ERROR_INT("pixs undefined or not 32 bpp", procName, 1);
+ if (!pixg || pixGetDepth(pixg) != 8)
+ return ERROR_INT("pixg undefined or not 8 bpp", procName, 1);
+ if (factor < 1) {
+ L_WARNING("sampling factor less than 1; setting to 1\n", procName);
+ factor = 1;
+ }
+ if (nbins < 1 || nbins > 100)
+ return ERROR_INT("nbins not in [1,100]", procName, 1);
+
+ /* Require that each bin has at least 5 pixels. */
+ 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 */
+ if (avepts < 5) {
+ L_ERROR("avepts = %d; must be >= 5\n", procName, avepts);
+ return 1;
+ }
+
+ /* ------------------------------------------------------------ *
+ * Find the average color for each bin. The colors are ordered *
+ * by the gray value in the corresponding pixel in %pixg. *
+ * The bins have equal numbers of pixels (within 1). *
+ * ------------------------------------------------------------ */
+
+ /* Generate a dnaa, where each dna has the colors corresponding
+ * to the grayscale value given by the index of the dna in the dnaa */
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+ datag = pixGetData(pixg);
+ wplg = pixGetWpl(pixg);
+ daa = l_dnaaCreateFull(256, 0);
+ 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);
+ l_dnaaAddNumber(daa, grayval, lines[j]);
+ }
+ }
+
+ if (pixadb) {
+ NUMA *na, *nabinval, *narank;
+ na = numaCreate(256); /* grayscale histogram */
+ for (i = 0; i < 256; i++)
+ numaAddNumber(na, l_dnaaGetDnaCount(daa, i));
+
+ /* Plot the gray bin value and the rank(gray) values */
+ numaDiscretizeHistoInBins(na, nbins, &nabinval, &narank);
+ pix1 = gplotSimplePix1(nabinval, "Gray value in each bin");
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ pix1 = gplotSimplePix1(narank, "rank as function of gray value");
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ numaDestroy(&na);
+ numaDestroy(&nabinval);
+ numaDestroy(&narank);
+ }
+
+ /* Get the number of items in each bin */
+ ntot = l_dnaaGetNumberCount(daa);
+ if ((naeach = numaGetUniformBinSizes(ntot, nbins)) == NULL) {
+ l_dnaaDestroy(&daa);
+ return ERROR_INT("naeach not made", procName, 1);
+ }
+
+ /* Get the average color in each bin. This algorithm is
+ * esssentially the same as in numaDiscretizeHistoInBins() */
+ carray = (l_uint32 *)LEPT_CALLOC(nbins, sizeof(l_uint32));
+ rsum = gsum = bsum = 0.0;
+ bincount = 0;
+ binindex = 0;
+ numaGetIValue(naeach, 0, &binsize);
+ k = 0; /* count up to ntot */
+ for (i = 0; i < 256; i++) {
+ count = l_dnaaGetDnaCount(daa, i);
+ for (j = 0; j < count; j++) {
+ k++;
+ bincount++;
+ l_dnaaGetValue(daa, i, j, &val64);
+ extractRGBValues((l_uint32)val64, &rval, &gval, &bval);
+ rsum += rval;
+ gsum += gval;
+ bsum += bval;
+ if (bincount == binsize) { /* add bin entry */
+ rave = (l_int32)(rsum / binsize + 0.5);
+ gave = (l_int32)(gsum / binsize + 0.5);
+ bave = (l_int32)(bsum / binsize + 0.5);
+ composeRGBPixel(rave, gave, bave, carray + binindex);
+ rsum = gsum = bsum = 0.0;
+ bincount = 0;
+ binindex++;
+ if (binindex == nbins) break;
+ numaGetIValue(naeach, binindex, &binsize);
+ }
+ }
+ if (binindex == nbins) break;
+ }
+ if (binindex != nbins)
+ L_ERROR("binindex = %d != nbins = %d\n", procName, binindex, nbins);
+
+ if (pixadb) {
+ NUMA *nared, *nagreen, *nablue;
+ nared = numaCreate(nbins);
+ nagreen = numaCreate(nbins);
+ nablue = numaCreate(nbins);
+ for (i = 0; i < nbins; i++) {
+ extractRGBValues(carray[i], &rval, &gval, &bval);
+ numaAddNumber(nared, rval);
+ numaAddNumber(nagreen, gval);
+ numaAddNumber(nablue, bval);
+ }
+ lept_mkdir("lept/regout");
+ pix1 = gplotSimplePix1(nared, "Average red val vs. rank bin");
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ pix1 = gplotSimplePix1(nagreen, "Average green val vs. rank bin");
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ pix1 = gplotSimplePix1(nablue, "Average blue val vs. rank bin");
+ pixaAddPix(pixadb, pix1, L_INSERT);
+ numaDestroy(&nared);
+ numaDestroy(&nagreen);
+ numaDestroy(&nablue);
+ }
+
+ *pcarray = carray;
+ numaDestroy(&naeach);
+ l_dnaaDestroy(&daa);
+ return 0;
+}
+
+
+/*!
+ * \brief pixDisplayColorArray()
+ *
+ * \param[in] carray array of colors: 0xrrggbb00
+ * \param[in] ncolors size of array
+ * \param[in] side size of each color square; suggest 200
+ * \param[in] ncols number of columns in output color matrix
+ * \param[in] fontsize to label each square with text.
+ * Valid set is {4,6,8,10,12,14,16,18,20}.
+ * Suggest 6 for 200x200 square. Use 0 to disable.
+ * \return pixd color array, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates an array of labeled color squares from an
+ * array of color values.
+ * (2) To make a single color square, use pixMakeColorSquare().
+ * </pre>
+ */
+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 *pix1, *pix2, *pix3, *pix4;
+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++) {
+ pix1 = pixCreate(side, side, 32);
+ pixSetAllArbitrary(pix1, carray[i]);
+ pix2 = pixAddBorder(pix1, 2, 1);
+ if (bmf) {
+ extractRGBValues(carray[i], &rval, &gval, &bval);
+ snprintf(textstr, sizeof(textstr),
+ "%d: (%d %d %d)", i, rval, gval, bval);
+ pix3 = pixAddSingleTextblock(pix2, bmf, textstr, 0xff000000,
+ L_ADD_BELOW, NULL);
+ } else {
+ pix3 = pixClone(pix2);
+ }
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ pix4 = pixaDisplayTiledInColumns(pixa, ncols, 1.0, 20, 2);
+ pixaDestroy(&pixa);
+ bmfDestroy(&bmf);
+ return pix4;
+}
+
+
+/*!
+ * \brief pixRankBinByStrip()
+ *
+ * \param[in] pixs 32 bpp or cmapped
+ * \param[in] direction L_SCAN_HORIZONTAL or L_SCAN_VERTICAL
+ * \param[in] size of strips in scan direction
+ * \param[in] nbins number of equal population bins; must be > 1
+ * \param[in] type color selection flag
+ * \return pixd result, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates a pix of height %nbins, where each column
+ * represents a horizontal or vertical strip of the input image.
+ * If %direction == L_SCAN_HORIZONTAL, the input image is
+ * tiled into vertical strips of width %size, where %size is
+ * chosen as 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,
+ * L_SELECT_HUE, L_SELECT_SATURATION.
+ * It determines how the rank ordering is done.
+ * (4) Typical input values might be %size = 5, %nbins = 10.
+ * </pre>
+ */
+PIX *
+pixRankBinByStrip(PIX *pixs,
+ l_int32 direction,
+ l_int32 size,
+ l_int32 nbins,
+ l_int32 type)
+{
+l_int32 i, j, w, h, mindim, 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 &&
+ type != L_SELECT_HUE && type != L_SELECT_SATURATION)
+ return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ mindim = L_MIN(w, h);
+ if (mindim < 20 || nbins > mindim)
+ return (PIX *)ERROR_PTR("pix too small and/or too many bins",
+ procName, NULL);
+
+ /* 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, NULL, 0);
+ if (array) {
+ 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, NULL, 0);
+ if (array) {
+ 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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixaGetAlignedStats()
+ *
+ * \param[in] pixa of identically sized, 8 bpp pix; not cmapped
+ * \param[in] type L_MEAN_ABSVAL, L_MEDIAN_VAL, L_MODE_VAL, L_MODE_COUNT
+ * \param[in] nbins of histogram for median and mode; ignored for mean
+ * \param[in] thresh on histogram for mode val; ignored for all other types
+ * \return pix with pixelwise aligned stats, or NULL on error.
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaExtractColumnFromEachPix()
+ *
+ * \param[in] pixa of identically sized, 8 bpp; not cmapped
+ * \param[in] col column index
+ * \param[in] pixd pix into which each column is inserted
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixGetRowStats()
+ *
+ * \param[in] pixs 8 bpp; not cmapped
+ * \param[in] type L_MEAN_ABSVAL, L_MEDIAN_VAL, L_MODE_VAL, L_MODE_COUNT
+ * \param[in] nbins of histogram for median and mode; ignored for mean
+ * \param[in] thresh on histogram for mode; ignored for mean and median
+ * \param[in] colvect vector of results gathered across the rows of pixs
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+ for (k = 0; k < nbins; k++) {
+ if (histo[k] > max)
+ max = histo[k];
+ }
+ colvect[i] = max;
+ }
+ }
+
+ LEPT_FREE(histo);
+ LEPT_FREE(gray2bin);
+ LEPT_FREE(bin2gray);
+ return 0;
+}
+
+
+/*!
+ * \brief pixGetColumnStats()
+ *
+ * \param[in] pixs 8 bpp; not cmapped
+ * \param[in] type L_MEAN_ABSVAL, L_MEDIAN_VAL, L_MODE_VAL, L_MODE_COUNT
+ * \param[in] nbins of histogram for median and mode; ignored for mean
+ * \param[in] thresh on histogram for mode val; ignored for all other types
+ * \param[in] rowvect vector of results gathered down the columns of pixs
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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;
+ for (k = 0; k < nbins; k++) {
+ if (histo[k] > max)
+ max = histo[k];
+ }
+ rowvect[j] = max;
+ }
+ for (k = 0; k < nbins; k++)
+ histo[k] = 0;
+ }
+
+ LEPT_FREE(histo);
+ LEPT_FREE(gray2bin);
+ LEPT_FREE(bin2gray);
+ return 0;
+}
+
+
+/*!
+ * \brief pixSetPixelColumn()
+ *
+ * \param[in] pix 8 bpp; not cmapped
+ * \param[in] col column index
+ * \param[in] colvect vector of floats
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixThresholdForFgBg()
+ *
+ * \param[in] pixs any depth; cmapped ok
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[in] thresh threshold for generating foreground mask
+ * \param[out] pfgval [optional] average foreground value
+ * \param[out] pbgval [optional] average background value
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSplitDistributionFgBg()
+ *
+ * \param[in] pixs any depth; cmapped ok
+ * \param[in] scorefract fraction of the max score, used to determine
+ * the range over which the histogram min is searched
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[out] pthresh [optional] best threshold for separating
+ * \param[out] pfgval [optional] average foreground value
+ * \param[out] pbgval [optional] average background value
+ * \param[out] ppixdb [optional] plot of distribution and split point
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See numaSplitDistribution() for details on the underlying
+ * method of choosing a threshold.
+ * </pre>
+ */
+l_ok
+pixSplitDistributionFgBg(PIX *pixs,
+ l_float32 scorefract,
+ l_int32 factor,
+ l_int32 *pthresh,
+ l_int32 *pfgval,
+ l_int32 *pbgval,
+ PIX **ppixdb)
+{
+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 (ppixdb) *ppixdb = NULL;
+ 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 (ppixdb) {
+ 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 (ppixdb) {
+ lept_mkdir("lept/redout");
+ gplot = gplotCreate("/tmp/lept/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);
+ *ppixdb = gplotMakeOutputPix(gplot);
+ gplotDestroy(&gplot);
+ numaDestroy(&nax);
+ numaDestroy(&nay);
+ }
+
+ pixDestroy(&pixg);
+ numaDestroy(&na);
+ return 0;
+}
diff --git a/leptonica/src/pix5.c b/leptonica/src/pix5.c
new file mode 100644
index 00000000..fc8f2501
--- /dev/null
+++ b/leptonica/src/pix5.c
@@ -0,0 +1,3221 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pix5.c
+ * <pre>
+ *
+ * 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 *pixClipRectangleWithBorder()
+ * PIX *pixClipMasked()
+ * l_int32 pixCropToMatch()
+ * PIX *pixCropToSize()
+ * PIX *pixResizeToMatch()
+ *
+ * Select a connected component by size
+ * PIX *pixSelectComponentBySize()
+ * PIX *pixFilterComponentBySize()
+ *
+ * Make special masks
+ * PIX *pixMakeSymmetricMask()
+ * PIX *pixMakeFrameMask()
+ *
+ * Generate a covering of rectangles over connected components
+ * PIX * pixMakeCoveringOfRectangles()
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixaFindDimensions()
+ *
+ * \param[in] pixa
+ * \param[out] pnaw [optional] numa of pix widths
+ * \param[out] pnah [optional] numa of pix heights
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixFindAreaPerimRatio()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] tab [optional] pixel sum table, can be NULL
+ * \param[out] pfract area/perimeter ratio
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaFindPerimToAreaRatio()
+ *
+ * \param[in] pixa of 1 bpp pix
+ * \return na of perimeter/arear ratio for each pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is typically used for a pixa consisting of
+ * 1 bpp connected components.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFindPerimToAreaRatio()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] tab [optional] pixel sum table, can be NULL
+ * \param[out] pfract perimeter/area ratio
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaFindPerimSizeRatio()
+ *
+ * \param[in] pixa of 1 bpp pix
+ * \return na of fg perimeter/(2*(w+h)) ratio for each pix,
+ * or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFindPerimSizeRatio()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] tab [optional] pixel sum table, can be NULL
+ * \param[out] pratio perimeter/size ratio
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaFindAreaFraction()
+ *
+ * \param[in] pixa of 1 bpp pix
+ * \return na of area fractions for each pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is typically used for a pixa consisting of
+ * 1 bpp connected components.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFindAreaFraction()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] tab [optional] pixel sum table, can be NULL
+ * \param[out] pfract fg area/size ratio
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaFindAreaFractionMasked()
+ *
+ * \param[in] pixa of 1 bpp pix
+ * \param[in] pixm mask image
+ * \param[in] debug 1 for output, 0 to suppress
+ * \return na of ratio masked/total fractions for each pix,
+ * or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFindAreaFractionMasked()
+ *
+ * \param[in] pixs 1 bpp, typically a single component
+ * \param[in] box [optional] for pixs relative to pixm
+ * \param[in] pixm 1 bpp mask, typically over the entire image from
+ * which the component pixs was extracted
+ * \param[in] tab [optional] pixel sum table, can be NULL
+ * \param[out] pfract fg area/size ratio
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaFindWidthHeightRatio()
+ *
+ * \param[in] pixa of 1 bpp pix
+ * \return na of width/height ratios for each pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is typically used for a pixa consisting of
+ * 1 bpp connected components.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaFindWidthHeightProduct()
+ *
+ * \param[in] pixa of 1 bpp pix
+ * \return na of width*height products for each pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is typically used for a pixa consisting of
+ * 1 bpp connected components.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFindOverlapFraction()
+ *
+ * \param[in] pixs1, pixs2 1 bpp
+ * \param[in] x2, y2 location in pixs1 of UL corner of pixs2
+ * \param[in] tab [optional] pixel sum table, can be null
+ * \param[out] pratio ratio fg intersection to fg union
+ * \param[out] pnoverlap [optional] number of overlapping pixels
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The UL corner of pixs2 is placed at (x2, y2) in pixs1.
+ * (2) This measure is similar to the correlation.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixFindRectangleComps()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] dist max distance allowed between bounding box
+ * and nearest foreground pixel within it
+ * \param[in] minw, minh minimum size in each direction as a requirement
+ * for a conforming rectangle
+ * \return boxa of components that conform, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConformsToRectangle()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] box [optional] if null, use the entire pixs
+ * \param[in] dist max distance allowed between bounding box and
+ * nearest foreground pixel within it
+ * \param[out] pconforms 0 (false) if not conforming;
+ * 1 (true) if conforming
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixClipRectangles()
+ *
+ * \param[in] pixs
+ * \param[in] boxa requested clipping regions
+ * \return pixa consisting of requested regions, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The returned pixa includes the actual regions clipped out from
+ * the input pixs.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixClipRectangle()
+ *
+ * \param[in] pixs
+ * \param[in] box requested clipping region; const
+ * \param[out] pboxc [optional] actual box of clipped region
+ * \return clipped pix, or NULL on error or if rectangle
+ * doesn't intersect pixs
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ boxDestroy(&boxc);
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ }
+ pixCopyResolution(pixd, pixs);
+ pixCopyColormap(pixd, pixs);
+ pixCopyText(pixd, pixs);
+ pixRasterop(pixd, 0, 0, bw, bh, PIX_SRC, pixs, bx, by);
+
+ if (pboxc)
+ *pboxc = boxc;
+ else
+ boxDestroy(&boxc);
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixClipRectangleWithBorder()
+ *
+ * \param[in] pixs
+ * \param[in] box requested clipping region; const
+ * \param[in] maxbord maximum amount of border to include
+ * \param[out] pboxn box in coordinates of returned pix
+ * \return under-clipped pix, or NULL on error or if rectangle
+ * doesn't intersect pixs
+ *
+ * <pre>
+ * Notes:
+ * (1) This underclips by an amount determined by the minimum of
+ * %maxbord and the amount of border that can be included
+ * equally on all 4 sides.
+ * (2) If part of the rectangle lies outside the pix, no border
+ * is included on any side.
+ * </pre>
+ */
+PIX *
+pixClipRectangleWithBorder(PIX *pixs,
+ BOX *box,
+ l_int32 maxbord,
+ BOX **pboxn)
+{
+l_int32 w, h, bx, by, bw, bh, bord;
+BOX *box1;
+PIX *pix1;
+
+ PROCNAME("pixClipRectangleWithBorder");
+
+ if (!pboxn)
+ return (PIX *)ERROR_PTR("&boxn not defined", procName, NULL);
+ *pboxn = NULL;
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+ if (!box)
+ return (PIX *)ERROR_PTR("box not defined", procName, NULL);
+
+ /* Determine the border width */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ boxGetGeometry(box, &bx, &by, &bw, &bh);
+ bord = L_MIN(bx, by);
+ bord = L_MIN(bord, w - bx - bw);
+ bord = L_MIN(bord, h - by - bh);
+ bord = L_MIN(bord, maxbord);
+
+ if (bord <= 0) { /* standard clipping */
+ pix1 = pixClipRectangle(pixs, box, NULL);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ *pboxn = boxCreate(0, 0, w, h);
+ return pix1;
+ }
+
+ /* There is a positive border */
+ box1 = boxAdjustSides(NULL, box, -bord, bord, -bord, bord);
+ pix1 = pixClipRectangle(pixs, box1, NULL);
+ boxDestroy(&box1);
+ *pboxn = boxCreate(bord, bord, bw, bh);
+ return pix1;
+}
+
+
+/*!
+ * \brief pixClipMasked()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp; colormap ok
+ * \param[in] pixm clipping mask, 1 bpp
+ * \param[in] x, y origin of clipping mask relative to pixs
+ * \param[in] 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
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCropToMatch()
+ *
+ * \param[in] pixs1 any depth, colormap OK
+ * \param[in] pixs2 any depth, colormap OK
+ * \param[out] ppixd1 may be a clone
+ * \param[out] ppixd2 may be a clone
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixCropToSize()
+ *
+ * \param[in] pixs any depth, colormap OK
+ * \param[in] w, h max dimensions of cropped image
+ * \return pixd cropped if necessary or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixResizeToMatch()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp; colormap ok
+ * \param[in] pixt can be null; we use only the size
+ * \param[in] w, h ignored if pixt is defined
+ * \return pixd resized to match or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Select a connected component by size *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixSelectComponentBySize()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] rankorder in decreasing size: 0 for largest.
+ * \param[in] type L_SELECT_BY_WIDTH, L_SELECT_BY_HEIGHT,
+ * L_SELECT_BY_MAX_DIMENSION,
+ * L_SELECT_BY_AREA, L_SELECT_BY_PERIMETER
+ * \param[in] connectivity 4 or 8
+ * \param[out] pbox [optional] location of returned component
+ * \return pix of rank order connected component, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This selects the Nth largest connected component, based on
+ * the selection type and connectivity.
+ * (2) Note that %rankorder is an integer. Use %rankorder = 0 for
+ * the largest component and %rankorder = -1 for the smallest.
+ * If %rankorder >= number of components, select the smallest.
+ */
+PIX *
+pixSelectComponentBySize(PIX *pixs,
+ l_int32 rankorder,
+ l_int32 type,
+ l_int32 connectivity,
+ BOX **pbox)
+{
+l_int32 n, empty, sorttype, index;
+BOXA *boxa1;
+NUMA *naindex;
+PIX *pixd;
+PIXA *pixa1, *pixa2;
+
+ PROCNAME("pixSelectComponentBySize");
+
+ if (pbox) *pbox = NULL;
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+ if (type == L_SELECT_BY_WIDTH)
+ sorttype = L_SORT_BY_WIDTH;
+ else if (type == L_SELECT_BY_HEIGHT)
+ sorttype = L_SORT_BY_HEIGHT;
+ else if (type == L_SELECT_BY_MAX_DIMENSION)
+ sorttype = L_SORT_BY_MAX_DIMENSION;
+ else if (type == L_SELECT_BY_AREA)
+ sorttype = L_SORT_BY_AREA;
+ else if (type == L_SELECT_BY_PERIMETER)
+ sorttype = L_SORT_BY_PERIMETER;
+ else
+ return (PIX *)ERROR_PTR("invalid selection type", procName, NULL);
+ if (connectivity != 4 && connectivity != 8)
+ return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+ pixZero(pixs, &empty);
+ if (empty)
+ return (PIX *)ERROR_PTR("no foreground pixels", procName, NULL);
+
+ boxa1 = pixConnComp(pixs, &pixa1, connectivity);
+ n = boxaGetCount(boxa1);
+ if (rankorder < 0 || rankorder >= n)
+ rankorder = n - 1; /* smallest */
+ pixa2 = pixaSort(pixa1, sorttype, L_SORT_DECREASING, &naindex, L_CLONE);
+ pixd = pixaGetPix(pixa2, rankorder, L_COPY);
+ if (pbox) {
+ numaGetIValue(naindex, rankorder, &index);
+ *pbox = boxaGetBox(boxa1, index, L_COPY);
+ }
+
+ numaDestroy(&naindex);
+ boxaDestroy(&boxa1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixFilterComponentBySize()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] rankorder in decreasing size: 0 for largest.
+ * \param[in] type L_SELECT_BY_WIDTH, L_SELECT_BY_HEIGHT,
+ * L_SELECT_BY_MAX_DIMENSION,
+ * L_SELECT_BY_AREA, L_SELECT_BY_PERIMETER
+ * \param[in] connectivity 4 or 8
+ * \param[out] pbox [optional] location of returned component
+ * \return pix with all other components removed, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes in pixSelectComponentBySize().
+ * (2) This returns a copy of %pixs, with all components removed
+ * except for the selected one.
+ */
+PIX *
+pixFilterComponentBySize(PIX *pixs,
+ l_int32 rankorder,
+ l_int32 type,
+ l_int32 connectivity,
+ BOX **pbox)
+{
+l_int32 x, y, w, h;
+BOX *box;
+PIX *pix1, *pix2;
+
+ PROCNAME("pixFilterComponentBySize");
+
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+
+ pix1 = pixSelectComponentBySize(pixs, rankorder, type, connectivity, &box);
+ if (!pix1) {
+ boxDestroy(&box);
+ return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+ }
+
+ /* Put the selected component in a new pix at the same
+ * location as it had in %pixs */
+ boxGetGeometry(box, &x, &y, &w, &h);
+ pix2 = pixCreateTemplate(pixs);
+ pixRasterop(pix2, x, y, w, h, PIX_SRC, pix1, 0, 0);
+ if (pbox)
+ *pbox = box;
+ else
+ boxDestroy(&box);
+ pixDestroy(&pix1);
+ return pix2;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Make special masks *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixMakeSymmetricMask()
+ *
+ * \param[in] w, h dimensions of output 1 bpp pix
+ * \param[in] hf horizontal fraction of half-width
+ * \param[in] vf vertical fraction of half-height
+ * \param[in] type L_USE_INNER, L_USE_OUTER
+ * \return pixd 1 bpp, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a convenience function for generating masks with
+ * horizontal and vertical reflection symmetry, over either
+ * the inner or outer parts of an image.
+ * (2) Using L_USE_INNER to generate a mask over the inner part
+ * of the image, the mask is a solid rectangle, and the fractions
+ * describe the distance between the boundary of the image and
+ * the rectangle boundary. For example, with hf == vf == 0.0,
+ * the mask covers the full image.
+ * (3) Using L_USE_OUTER to generate a mask over an outer frame
+ * of the image, the mask touches the boundary of the image,
+ * and the fractions describe the location of the inner
+ * boundary of the frame. For example, with hf == vf == 1.0,
+ * the inner boundary is at the center of the image, so the
+ * mask covers the full image.
+ * (4) More examples:
+ * * mask covering the inner 70%: hf = vf = 0.3, type = L_USE_INNER
+ * * frame covering the outer 30%: hf = vf = 0.3, type = L_USE_OUTER
+ * </pre>
+ */
+PIX *
+pixMakeSymmetricMask(l_int32 w,
+ l_int32 h,
+ l_float32 hf,
+ l_float32 vf,
+ l_int32 type)
+{
+ PROCNAME("pixMakeSymmetricMask");
+
+ if (w <= 0 || h <= 0)
+ return (PIX *)ERROR_PTR("mask size 0", procName, NULL);
+ if (hf < 0.0 || hf > 1.0)
+ return (PIX *)ERROR_PTR("invalid horiz fractions", procName, NULL);
+ if (vf < 0.0 || vf > 1.0)
+ return (PIX *)ERROR_PTR("invalid vert fractions", procName, NULL);
+
+ if (type == L_USE_INNER)
+ return pixMakeFrameMask(w, h, hf, 1.0, vf, 1.0);
+ else if (type == L_USE_OUTER)
+ return pixMakeFrameMask(w, h, 0.0, hf, 0.0, vf);
+ else
+ return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+}
+
+
+/*!
+ * \brief pixMakeFrameMask()
+ *
+ * \param[in] w, h dimensions of output 1 bpp pix
+ * \param[in] hf1 horizontal fraction of half-width at outer frame bdry
+ * \param[in] hf2 horizontal fraction of half-width at inner frame bdry
+ * \param[in] vf1 vertical fraction of half-width at outer frame bdry
+ * \param[in] vf2 vertical fraction of half-width at inner frame bdry
+ * \return pixd 1 bpp, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This makes an arbitrary 1-component mask with a centered fg
+ * frame, which can have both an inner and an outer boundary.
+ * All input fractional distances are measured from the image
+ * border to the frame boundary, in units of the image half-width
+ * for hf1 and hf2 and the image half-height for vf1 and vf2.
+ * The distances to the outer frame boundary are given by hf1
+ * and vf1; to the inner frame boundary, by hf2 and vf2.
+ * Input fractions are thus in [0.0 ... 1.0], with hf1 <= hf2
+ * and vf1 <= vf2. Horizontal and vertical frame widths are
+ * thus independently specified.
+ * (2) Special cases:
+ * * full fg mask: hf1 = vf1 = 0.0, hf2 = vf2 = 1.0.
+ * * empty fg (zero width) mask: set hf1 = hf2 and vf1 = vf2.
+ * * fg rectangle with no hole: set hf2 = vf2 = 1.0.
+ * * frame touching outer boundary: set hf1 = vf1 = 0.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.
+ * </pre>
+ */
+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 && vf1 == 0.0 && hf2 == 1.0 && vf2 == 1.0) { /* full */
+ pixSetAll(pixd);
+ return pixd;
+ }
+ if (hf1 == hf2 && vf1 == vf2) { /* 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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Generate a covering of rectangles over connected components *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixMakeCoveringOfRectangles()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] maxiters max iterations: use 0 to iterate to completion
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This iteratively finds the bounding boxes of the connected
+ * components and generates a mask from them. Two iterations
+ * should suffice for most situations.
+ * (2) Returns an empty pix if %pixs is empty.
+ * (3) If there are many small components in proximity, it may
+ * be useful to merge them with a morphological closing before
+ * calling this one.
+ * </pre>
+ */
+PIX *
+pixMakeCoveringOfRectangles(PIX *pixs,
+ l_int32 maxiters)
+{
+l_int32 empty, same, niters;
+BOXA *boxa;
+PIX *pix1, *pix2;
+
+ PROCNAME("pixMakeCoveringOfRectangles");
+
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+ if (maxiters < 0)
+ return (PIX *)ERROR_PTR("maxiters must be >= 0", procName, NULL);
+ if (maxiters == 0) maxiters = 50; /* ridiculously large number */
+
+ pixZero(pixs, &empty);
+ pix1 = pixCreateTemplate(pixs);
+ if (empty) return pix1;
+
+ /* Do first iteration */
+ boxa = pixConnCompBB(pixs, 8);
+ pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS);
+ boxaDestroy(&boxa);
+ if (maxiters == 1) return pix1;
+
+ niters = 1;
+ while (niters < maxiters) { /* continue to add pixels to pix1 */
+ niters++;
+ boxa = pixConnCompBB(pix1, 8);
+ pix2 = pixCopy(NULL, pix1);
+ pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS);
+ boxaDestroy(&boxa);
+ pixEqual(pix1, pix2, &same);
+ pixDestroy(&pix2);
+ if (same) {
+ L_INFO("%d iterations\n", procName, niters - 1);
+ return pix1;
+ }
+ }
+ L_INFO("maxiters = %d reached\n", procName, niters);
+ return pix1;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Fraction of Fg pixels under a mask *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixFractionFgInMask()
+ *
+ * \param[in] pix1 1 bpp
+ * \param[in] pix2 1 bpp
+ * \param[out] pfract fraction of fg pixels in 1 that are
+ * aligned with the fg of 2
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixClipToForeground()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[out] ppixd [optional] clipped pix returned
+ * \param[out] pbox [optional] bounding box
+ * \return 0 if OK; 1 on error or if there are no fg pixels
+ *
+ * <pre>
+ * Notes:
+ * (1) At least one of {&pixd, &box} must be specified.
+ * (2) If there are no fg pixels, the returned ptrs are null.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixTestClipToForeground()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[out] pcanclip 1 if fg does not extend to all four edges
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+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 */
+}
+
+
+/*!
+ * \brief pixClipBoxToForeground()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] boxs [optional] use full image if null
+ * \param[out] ppixd [optional] clipped pix returned
+ * \param[out] pboxd [optional] bounding box
+ * \return 0 if OK; 1 on error or if there are no fg pixels
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixScanForForeground()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] box [optional] within which the search is conducted
+ * \param[in] scanflag direction of scan; e.g., L_FROM_LEFT
+ * \param[out] ploc location in scan direction of first black pixel
+ * \return 0 if OK; 1 on error or if no fg pixels are found
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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 */
+}
+
+
+/*!
+ * \brief pixClipBoxToEdges()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] boxs [optional] ; use full image if null
+ * \param[in] lowthresh threshold to choose clipping location
+ * \param[in] highthresh threshold required to find an edge
+ * \param[in] maxwidth max allowed width between low and high thresh locs
+ * \param[in] factor sampling factor along pixel counting direction
+ * \param[out] ppixd [optional] clipped pix returned
+ * \param[out] pboxd [optional] bounding box
+ * \return 0 if OK; 1 on error or if a fg edge is not found from
+ * all four sides.
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+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
+ lept_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;
+}
+
+
+/*!
+ * \brief pixScanForEdge()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] box [optional] within which the search is conducted
+ * \param[in] lowthresh threshold to choose clipping location
+ * \param[in] highthresh threshold required to find an edge
+ * \param[in] maxwidth max allowed width between low and high thresh locs
+ * \param[in] factor sampling factor along pixel counting direction
+ * \param[in] scanflag direction of scan; e.g., L_FROM_LEFT
+ * \param[out] ploc location in scan direction of first black pixel
+ * \return 0 if OK; 1 on error or if the edge is not found
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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
+ lept_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
+ lept_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
+ lept_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
+ lept_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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixExtractOnLine()
+ *
+ * \param[in] pixs 1 bpp or 8 bpp; no colormap
+ * \param[in] x1, y1 one end point for line
+ * \param[in] x2, y2 another end pt for line
+ * \param[in] factor sampling; >= 1
+ * \return na of pixel values along line, or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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_PNG, NULL);
+#endif
+
+ ptaDestroy(&pta);
+ }
+
+ return na;
+}
+
+
+/*!
+ * \brief pixAverageOnLine()
+ *
+ * \param[in] pixs 1 bpp or 8 bpp; no colormap
+ * \param[in] x1, y1 starting pt for line
+ * \param[in] x2, y2 end pt for line
+ * \param[in] factor sampling; >= 1
+ * \return average of pixel values along line, or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+ count = 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;
+}
+
+
+/*!
+ * \brief pixAverageIntensityProfile()
+ *
+ * \param[in] pixs any depth; colormap OK
+ * \param[in] fract fraction of image width or height to be used
+ * \param[in] dir averaging direction: L_HORIZONTAL_LINE or
+ * L_VERTICAL_LINE
+ * \param[in] first, last span of rows or columns to measure
+ * \param[in] factor1 sampling along fast scan direction; >= 1
+ * \param[in] factor2 sampling along slow scan direction; >= 1
+ * \return na of reversal profile, or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixReversalProfile()
+ *
+ * \param[in] pixs any depth; colormap OK
+ * \param[in] fract fraction of image width or height to be used
+ * \param[in] dir profile direction: L_HORIZONTAL_LINE or
+ * L_VERTICAL_LINE
+ * \param[in] first, last span of rows or columns to measure
+ * \param[in] minreversal minimum change in intensity to trigger a reversal
+ * \param[in] factor1 sampling along raster line (fast scan); >= 1
+ * \param[in] factor2 sampling of raster lines (slow scan); >= 1
+ * \return na of reversal profile, or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixWindowedVarianceOnLine()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] dir L_HORIZONTAL_LINE or L_VERTICAL_LINE
+ * \param[in] loc location of the constant coordinate for the line
+ * \param[in] c1, c2 end point coordinates for the line
+ * \param[in] size window size; must be > 1
+ * \param[out] pnad windowed square root of variance
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 += (l_float64)(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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixMinMaxNearLine()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] x1, y1 starting pt for line
+ * \param[in] x2, y2 end pt for line
+ * \param[in] dist distance to search from line in each direction
+ * \param[in] direction L_SCAN_NEGATIVE, L_SCAN_POSITIVE, L_SCAN_BOTH
+ * \param[out] pnamin [optional] minimum values
+ * \param[out] pnamax [optional] maximum values
+ * \param[out] pminave [optional] average of minimum values
+ * \param[out] pmaxave [optional] average of maximum values
+ * \return 0 if OK; 1 on error or if there are no sampled points
+ * within the image.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixRankRowTransform()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \return pixd with pixels sorted in each row, from
+ * min to max value
+ *
+ * <pre>
+ * Notes:
+ * (1) The time is O(n) in the number of pixels and runs about
+ * 100 Mpixels/sec on a 3 GHz machine.
+ * </pre>
+ */
+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 = pixCreateTemplate(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;
+}
+
+
+/*!
+ * \brief pixRankColumnTransform()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \return pixd with pixels sorted in each column, from
+ * min to max value
+ *
+ * <pre>
+ * Notes:
+ * (1) The time is O(n) in the number of pixels and runs about
+ * 50 Mpixels/sec on a 3 GHz machine.
+ * </pre>
+ */
+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 = pixCreateTemplate(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/leptonica/src/pixabasic.c b/leptonica/src/pixabasic.c
new file mode 100644
index 00000000..c8c7d7c0
--- /dev/null
+++ b/leptonica/src/pixabasic.c
@@ -0,0 +1,3283 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pixabasic.c
+ * <pre>
+ *
+ * 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 pixaVerifyDimensions()
+ * l_int32 pixaIsFull()
+ * l_int32 pixaCountText()
+ * l_int32 pixaSetText()
+ * 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 pixaRemoveSelected()
+ * l_int32 pixaInitFull()
+ * l_int32 pixaClear()
+ *
+ * Pixa and Pixaa combination
+ * l_int32 pixaJoin()
+ * PIXA *pixaInterleave()
+ * l_int32 pixaaJoin()
+ *
+ * Pixaa creation, destruction
+ * PIXAA *pixaaCreate()
+ * PIXAA *pixaaCreateFromPixa()
+ * void pixaaDestroy()
+ *
+ * Pixaa addition
+ * l_int32 pixaaAddPixa()
+ * static 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 pixaaVerifyDimensions()
+ * 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()
+ * PIXA *pixaReadMem()
+ * l_int32 pixaWriteDebug()
+ * l_int32 pixaWrite()
+ * l_int32 pixaWriteStream()
+ * l_int32 pixaWriteMem()
+ * PIXA *pixaReadBoth()
+ *
+ * Pixaa serialized I/O (requires png support)
+ * PIXAA *pixaaReadFromFiles()
+ * PIXAA *pixaaRead()
+ * PIXAA *pixaaReadStream()
+ * PIXAA *pixaaReadMem()
+ * l_int32 pixaaWrite()
+ * l_int32 pixaaWriteStream()
+ * l_int32 pixaaWriteMem()
+ *
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Bounds on array sizes */
+static const size_t MaxInitPtrArraySize = 100000;
+static const size_t MaxPixaPtrArraySize = 5000000;
+static const size_t MaxPixaaPtrArraySize = 1000000;
+static const size_t InitialPtrArraySize = 20; /*!< n'importe quoi */
+
+ /* Static functions */
+static l_int32 pixaExtendArray(PIXA *pixa);
+static l_int32 pixaaExtendArray(PIXAA *paa);
+
+/*---------------------------------------------------------------------*
+ * Pixa creation, destruction, copy *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaCreate()
+ *
+ * \param[in] n initial number of ptrs
+ * \return pixa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This creates an empty boxa.
+ * </pre>
+ */
+PIXA *
+pixaCreate(l_int32 n)
+{
+PIXA *pixa;
+
+ PROCNAME("pixaCreate");
+
+ if (n <= 0 || n > MaxInitPtrArraySize)
+ n = InitialPtrArraySize;
+
+ pixa = (PIXA *)LEPT_CALLOC(1, sizeof(PIXA));
+ pixa->n = 0;
+ pixa->nalloc = n;
+ pixa->refcount = 1;
+ pixa->pix = (PIX **)LEPT_CALLOC(n, sizeof(PIX *));
+ pixa->boxa = boxaCreate(n);
+ if (!pixa->pix || !pixa->boxa) {
+ pixaDestroy(&pixa);
+ return (PIXA *)ERROR_PTR("pix or boxa not made", procName, NULL);
+ }
+ return pixa;
+}
+
+
+/*!
+ * \brief pixaCreateFromPix()
+ *
+ * \param[in] pixs with individual components on a lattice
+ * \param[in] n number of components
+ * \param[in] cellw width of each cell
+ * \param[in] cellh height of each cell
+ * \return pixa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For bpp = 1, we truncate each retrieved pix to the ON
+ * pixels, which we assume for now start at (0,0)
+ * </pre>
+ */
+PIXA *
+pixaCreateFromPix(PIX *pixs,
+ l_int32 n,
+ l_int32 cellw,
+ l_int32 cellh)
+{
+l_int32 w, h, d, nw, nh, i, j, index;
+PIX *pix1, *pix2;
+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 ((pix1 = pixCreate(cellw, cellh, d)) == NULL) {
+ pixaDestroy(&pixa);
+ return (PIXA *)ERROR_PTR("pix1 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(pix1, 0, 0, cellw, cellh, PIX_SRC, pixs,
+ j * cellw, i * cellh);
+ if (d == 1 && !pixClipToForeground(pix1, &pix2, NULL))
+ pixaAddPix(pixa, pix2, L_INSERT);
+ else
+ pixaAddPix(pixa, pix1, L_COPY);
+ }
+ }
+
+ pixDestroy(&pix1);
+ return pixa;
+}
+
+
+/*!
+ * \brief pixaCreateFromBoxa()
+ *
+ * \param[in] pixs
+ * \param[in] boxa
+ * \param[in] start first box to use
+ * \param[in] num number of boxes; use 0 to go to the end
+ * \param[out] pcropwarn [optional] TRUE if the boxa extent
+ * is larger than pixs.
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This simply extracts from pixs the region corresponding to each
+ * box in the boxa. To extract all the regions, set both %start
+ * and %num to 0.
+ * (2) The 5th 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.
+ * </pre>
+ */
+PIXA *
+pixaCreateFromBoxa(PIX *pixs,
+ BOXA *boxa,
+ l_int32 start,
+ l_int32 num,
+ l_int32 *pcropwarn)
+{
+l_int32 i, n, end, 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);
+ if (num < 0)
+ return (PIXA *)ERROR_PTR("num must be >= 0", procName, NULL);
+
+ n = boxaGetCount(boxa);
+ end = (num == 0) ? n - 1 : L_MIN(start + num - 1, n - 1);
+ if ((pixad = pixaCreate(end - start + 1)) == 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 = start; i <= end; 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;
+}
+
+
+/*!
+ * \brief pixaSplitPix()
+ *
+ * \param[in] pixs with individual components on a lattice
+ * \param[in] nx number of mosaic cells horizontally
+ * \param[in] ny number of mosaic cells vertically
+ * \param[in] borderwidth of added border on all sides
+ * \param[in] bordercolor in our RGBA format: 0xrrggbbaa
+ * \return pixa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *pix1;
+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 ((pix1 = pixCreate(cellw + 2 * borderwidth,
+ cellh + 2 * borderwidth, d)) == NULL) {
+ pixaDestroy(&pixa);
+ return (PIXA *)ERROR_PTR("pix1 not made", procName, NULL);
+ }
+ pixCopyColormap(pix1, pixs);
+ if (borderwidth == 0) { /* initialize full image to white */
+ if (d == 1)
+ pixClearAll(pix1);
+ else
+ pixSetAll(pix1);
+ } else {
+ pixSetAllArbitrary(pix1, bordercolor);
+ }
+ pixRasterop(pix1, borderwidth, borderwidth, cellw, cellh,
+ PIX_SRC, pixs, j * cellw, i * cellh);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ }
+ }
+
+ return pixa;
+}
+
+
+/*!
+ * \brief pixaDestroy()
+ *
+ * \param[in,out] ppixa use ptr address so it will be nulled
+ *
+ * <pre>
+ * Notes:
+ * (1) Decrements the ref count and, if 0, destroys the pixa.
+ * (2) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaCopy()
+ *
+ * \param[in] pixa
+ * \param[in] 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, nb;
+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);
+ nb = pixaGetBoxaCount(pixa);
+ for (i = 0; i < pixa->n; i++) {
+ if (copyflag == L_COPY) {
+ pixc = pixaGetPix(pixa, i, L_COPY);
+ if (i < nb) boxc = pixaGetBox(pixa, i, L_COPY);
+ } else { /* copy-clone */
+ pixc = pixaGetPix(pixa, i, L_CLONE);
+ if (i < nb) boxc = pixaGetBox(pixa, i, L_CLONE);
+ }
+ pixaAddPix(pixac, pixc, L_INSERT);
+ if (i < nb) pixaAddBox(pixac, boxc, L_INSERT);
+ }
+
+ return pixac;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ * Pixa addition *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaAddPix()
+ *
+ * \param[in] pixa
+ * \param[in] pix to be added
+ * \param[in] copyflag L_INSERT, L_COPY, L_CLONE
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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) {
+ if (pixaExtendArray(pixa)) {
+ if (copyflag != L_INSERT)
+ pixDestroy(&pixc);
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ }
+
+ pixa->pix[n] = pixc;
+ pixa->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief pixaAddBox()
+ *
+ * \param[in] pixa
+ * \param[in] box
+ * \param[in] copyflag L_INSERT, L_COPY, L_CLONE
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaExtendArray()
+ *
+ * \param[in] pixa
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Doubles the size of the pixa and boxa ptr arrays.
+ * (2) The max number of pix in the array is 5 million.
+ * </pre>
+ */
+static l_int32
+pixaExtendArray(PIXA *pixa)
+{
+ PROCNAME("pixaExtendArray");
+
+ if (!pixa)
+ return ERROR_INT("pixa not defined", procName, 1);
+
+ return pixaExtendArrayToSize(pixa, 2 * pixa->nalloc);
+}
+
+
+/*!
+ * \brief pixaExtendArrayToSize()
+ *
+ * \param[in] pixa
+ * \param[in] size number of pix ptrs in new array
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * (2) The max number of pix ptrs is 5M.
+ * </pre>
+ */
+l_ok
+pixaExtendArrayToSize(PIXA *pixa,
+ size_t size)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("pixaExtendArrayToSize");
+
+ if (!pixa)
+ return ERROR_INT("pixa not defined", procName, 1);
+ if (pixa->nalloc > MaxPixaPtrArraySize) /* belt & suspenders */
+ return ERROR_INT("pixa has too many ptrs", procName, 1);
+ if (size > MaxPixaPtrArraySize)
+ return ERROR_INT("size > 5M ptrs; too large", procName, 1);
+ if (size <= pixa->nalloc) {
+ L_INFO("size too small; no extension\n", procName);
+ return 0;
+ }
+
+ oldsize = pixa->nalloc * sizeof(PIX *);
+ newsize = size * sizeof(PIX *);
+ if ((pixa->pix = (PIX **)reallocNew((void **)&pixa->pix,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+ pixa->nalloc = size;
+ return boxaExtendArrayToSize(pixa->boxa, size);
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pixa accessors *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixaChangeRefcount()
+ *
+ * \param[in] pixa
+ * \param[in] delta
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixaChangeRefcount(PIXA *pixa,
+ l_int32 delta)
+{
+ PROCNAME("pixaChangeRefcount");
+
+ if (!pixa)
+ return ERROR_INT("pixa not defined", procName, 1);
+
+ pixa->refcount += delta;
+ return 0;
+}
+
+
+/*!
+ * \brief pixaGetPix()
+ *
+ * \param[in] pixa
+ * \param[in] index to the index-th pix
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief pixaGetPixDimensions()
+ *
+ * \param[in] pixa
+ * \param[in] index to the index-th box
+ * \param[out] pw, ph, pd [optional] each can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaGetBoxa()
+ *
+ * \param[in] pixa
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief pixaGetBoxaCount()
+ *
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief pixaGetBox()
+ *
+ * \param[in] pixa
+ * \param[in] index to the index-th pix
+ * \param[in] accesstype L_COPY or L_CLONE
+ * \return box if null, not automatically an error, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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;
+ }
+}
+
+
+/*!
+ * \brief pixaGetBoxGeometry()
+ *
+ * \param[in] pixa
+ * \param[in] index to the index-th box
+ * \param[out] px, py, pw, ph [optional] each can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaSetBoxa()
+ *
+ * \param[in] pixa
+ * \param[in] boxa
+ * \param[in] accesstype L_INSERT, L_COPY, L_CLONE
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This destroys the existing boxa in the pixa.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaGetPixArray()
+ *
+ * \param[in] pixa
+ * \return pix array, or NULL on error
+ *
+ * <pre>
+ * 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!
+ * </pre>
+ */
+PIX **
+pixaGetPixArray(PIXA *pixa)
+{
+ PROCNAME("pixaGetPixArray");
+
+ if (!pixa)
+ return (PIX **)ERROR_PTR("pixa not defined", procName, NULL);
+
+ return pixa->pix;
+}
+
+
+/*!
+ * \brief pixaVerifyDepth()
+ *
+ * \param[in] pixa
+ * \param[out] psame 1 if depth is the same for all pix; 0 otherwise
+ * \param[out] pmaxd [optional] max depth of all pix
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is considered to be an error if there are no pix.
+ * </pre>
+ */
+l_ok
+pixaVerifyDepth(PIXA *pixa,
+ l_int32 *psame,
+ l_int32 *pmaxd)
+{
+l_int32 i, n, d, maxd, same;
+
+ PROCNAME("pixaVerifyDepth");
+
+ if (pmaxd) *pmaxd = 0;
+ if (!psame)
+ return ERROR_INT("psame not defined", procName, 1);
+ if (!pixa)
+ return ERROR_INT("pixa not defined", procName, 1);
+ if ((n = pixaGetCount(pixa)) == 0)
+ return ERROR_INT("no pix in pixa", procName, 1);
+
+ same = 1;
+ pixaGetPixDimensions(pixa, 0, NULL, NULL, &maxd);
+ for (i = 1; i < n; i++) {
+ if (pixaGetPixDimensions(pixa, i, NULL, NULL, &d))
+ return ERROR_INT("pix depth not found", procName, 1);
+ maxd = L_MAX(maxd, d);
+ if (d != maxd)
+ same = 0;
+ }
+ *psame = same;
+ if (pmaxd) *pmaxd = maxd;
+ return 0;
+}
+
+
+/*!
+ * \brief pixaVerifyDimensions()
+ *
+ * \param[in] pixa
+ * \param[out] psame 1 if dimensions are the same for all pix; 0 otherwise
+ * \param[out] pmaxw [optional] max width of all pix
+ * \param[out] pmaxh [optional] max height of all pix
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is considered to be an error if there are no pix.
+ * </pre>
+ */
+l_ok
+pixaVerifyDimensions(PIXA *pixa,
+ l_int32 *psame,
+ l_int32 *pmaxw,
+ l_int32 *pmaxh)
+{
+l_int32 i, n, w, h, maxw, maxh, same;
+
+ PROCNAME("pixaVerifyDimensions");
+
+ if (pmaxw) *pmaxw = 0;
+ if (pmaxh) *pmaxh = 0;
+ if (!psame)
+ return ERROR_INT("psame not defined", procName, 1);
+ *psame = 0;
+ if (!pixa)
+ return ERROR_INT("pixa not defined", procName, 1);
+ if ((n = pixaGetCount(pixa)) == 0)
+ return ERROR_INT("no pix in pixa", procName, 1);
+
+ same = 1;
+ pixaGetPixDimensions(pixa, 0, &maxw, &maxh, NULL);
+ for (i = 1; i < n; i++) {
+ if (pixaGetPixDimensions(pixa, i, &w, &h, NULL))
+ return ERROR_INT("pix dimensions not found", procName, 1);
+ maxw = L_MAX(maxw, w);
+ maxh = L_MAX(maxh, h);
+ if (w != maxw || h != maxh)
+ same = 0;
+ }
+ *psame = same;
+ if (pmaxw) *pmaxw = maxw;
+ if (pmaxh) *pmaxh = maxh;
+ return 0;
+}
+
+
+/*!
+ * \brief pixaIsFull()
+ *
+ * \param[in] pixa
+ * \param[out] pfullpa [optional] 1 if pixa is full
+ * \param[out] pfullba [optional] 1 if boxa is full
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) A pixa is "full" if the array of pix is fully
+ * occupied from index 0 to index (pixa->n - 1).
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaCountText()
+ *
+ * \param[in] pixa
+ * \param[out] pntext number of pix with non-empty text strings
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) All pix have non-empty text strings if the returned value %ntext
+ * equals the pixa count.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaSetText()
+ *
+ * \param[in] pixa
+ * \param[in] text [optional] single text string, to insert in each pix
+ * \param[in] sa [optional] array of text strings, to insert in each pix
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) To clear all the text fields, use %sa == NULL and %text == NULL.
+ * (2) To set all the text fields to the same value %text, use %sa = NULL.
+ * (3) If %sa is defined, we ignore %text and use it; %sa must have
+ * the same count as %pixa.
+ * </pre>
+ */
+l_ok
+pixaSetText(PIXA *pixa,
+ const char *text,
+ SARRAY *sa)
+{
+char *str;
+l_int32 i, n;
+PIX *pix;
+
+ PROCNAME("pixaSetText");
+
+ if (!pixa)
+ return ERROR_INT("pixa not defined", procName, 1);
+
+ n = pixaGetCount(pixa);
+ if (sa && (sarrayGetCount(sa) != n))
+ return ERROR_INT("pixa and sa sizes differ", procName, 1);
+
+ if (!sa) {
+ for (i = 0; i < n; i++) {
+ if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL)
+ continue;
+ pixSetText(pix, text);
+ pixDestroy(&pix);
+ }
+ return 0;
+ }
+
+ for (i = 0; i < n; i++) {
+ if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL)
+ continue;
+ str = sarrayGetString(sa, i, L_NOCOPY);
+ pixSetText(pix, str);
+ pixDestroy(&pix);
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixaGetLinePtrs()
+ *
+ * \param[in] pixa of pix that all have the same depth
+ * \param[out] psize [optional] number of pix in the pixa
+ * \return array of array of line ptrs, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+void ***
+pixaGetLinePtrs(PIXA *pixa,
+ l_int32 *psize)
+{
+l_int32 i, n, same;
+void **lineptrs;
+void ***lineset;
+PIX *pix;
+
+ PROCNAME("pixaGetLinePtrs");
+
+ if (psize) *psize = 0;
+ if (!pixa)
+ return (void ***)ERROR_PTR("pixa not defined", procName, NULL);
+ pixaVerifyDepth(pixa, &same, NULL);
+ if (!same)
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaWriteStreamInfo()
+ *
+ * \param[in] fp file stream
+ * \param[in] pixa
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) For each pix in the pixa, write out the pix dimensions, spp,
+ * text string (if it exists), and cmap info.
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaReplacePix()
+ *
+ * \param[in] pixa
+ * \param[in] index to the index-th pix
+ * \param[in] pix insert to replace existing one
+ * \param[in] box [optional] insert to replace existing
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) In-place replacement of one pix.
+ * (2) The previous pix at that location is destroyed.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaInsertPix()
+ *
+ * \param[in] pixa
+ * \param[in] index at which pix is to be inserted
+ * \param[in] pixs new pix to be inserted
+ * \param[in] box [optional] new box to be inserted
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n);
+ return 1;
+ }
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+
+ if (n >= pixa->nalloc) { /* extend both ptr arrays */
+ if (pixaExtendArray(pixa))
+ return ERROR_INT("extension failed", procName, 1);
+ if (boxaExtendArray(pixa->boxa))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ 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;
+}
+
+
+/*!
+ * \brief pixaRemovePix()
+ *
+ * \param[in] pixa
+ * \param[in] index of pix to be removed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n - 1);
+ return 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;
+}
+
+
+/*!
+ * \brief pixaRemovePixAndSave()
+ *
+ * \param[in] pixa
+ * \param[in] index of pix to be removed
+ * \param[out] ppix [optional] removed pix
+ * \param[out] pbox [optional] removed box
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n - 1);
+ return 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;
+}
+
+
+/*!
+ * \brief pixaRemoveSelected()
+ *
+ * \param[in] pixa
+ * \param[in] naindex numa of indices of pix to be removed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This gives error messages for invalid indices
+ * </pre>
+ */
+l_ok
+pixaRemoveSelected(PIXA *pixa,
+ NUMA *naindex)
+{
+l_int32 i, n, index;
+NUMA *na1;
+
+ PROCNAME("pixaRemoveSelected");
+
+ if (!pixa)
+ return ERROR_INT("pixa not defined", procName, 1);
+ if (!naindex)
+ return ERROR_INT("naindex not defined", procName, 1);
+ if ((n = numaGetCount(naindex)) == 0)
+ return ERROR_INT("naindex is empty", procName, 1);
+
+ /* Remove from highest indices first */
+ na1 = numaSort(NULL, naindex, L_SORT_DECREASING);
+ for (i = 0; i < n; i++) {
+ numaGetIValue(na1, i, &index);
+ pixaRemovePix(pixa, index);
+ }
+ numaDestroy(&na1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixaInitFull()
+ *
+ * \param[in] pixa typically empty
+ * \param[in] pix [optional] to be replicated to the entire pixa ptr array
+ * \param[in] box [optional] to be replicated to the entire boxa ptr array
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+pixaInitFull(PIXA *pixa,
+ PIX *pix,
+ BOX *box)
+{
+l_int32 i, n;
+PIX *pix1;
+
+ 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)
+ pix1 = pixCopy(NULL, pix);
+ else
+ pix1 = pixCreate(1, 1, 1);
+ pixaReplacePix(pixa, i, pix1, NULL);
+ }
+ if (box)
+ boxaInitFull(pixa->boxa, box);
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixaClear()
+ *
+ * \param[in] pixa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaJoin()
+ *
+ * \param[in] pixad dest pixa; add to this one
+ * \param[in] pixas [optional] source pixa; add from this one
+ * \param[in] istart starting index in pixas
+ * \param[in] iend ending index in pixas; use -1 to cat all
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaInterleave()
+ *
+ * \param[in] pixa1 first src pixa
+ * \param[in] pixa2 second src pixa
+ * \param[in] copyflag L_CLONE, L_COPY
+ * \return pixa interleaved from sources, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) %copyflag determines if the pix are copied or cloned.
+ * The boxes, if they exist, are copied.
+ * (2) If the two pixa have different sizes, a warning is issued,
+ * and the number of pairs returned is the minimum size.
+ * </pre>
+ */
+PIXA *
+pixaInterleave(PIXA *pixa1,
+ PIXA *pixa2,
+ l_int32 copyflag)
+{
+l_int32 i, n1, n2, n, nb1, nb2;
+BOX *box;
+PIX *pix;
+PIXA *pixad;
+
+ PROCNAME("pixaInterleave");
+
+ if (!pixa1)
+ return (PIXA *)ERROR_PTR("pixa1 not defined", procName, NULL);
+ if (!pixa2)
+ return (PIXA *)ERROR_PTR("pixa2 not defined", procName, NULL);
+ if (copyflag != L_COPY && copyflag != L_CLONE)
+ return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL);
+ n1 = pixaGetCount(pixa1);
+ n2 = pixaGetCount(pixa2);
+ n = L_MIN(n1, n2);
+ if (n == 0)
+ return (PIXA *)ERROR_PTR("at least one input pixa is empty",
+ procName, NULL);
+ if (n1 != n2)
+ L_WARNING("counts differ: %d != %d\n", procName, n1, n2);
+
+ pixad = pixaCreate(2 * n);
+ nb1 = pixaGetBoxaCount(pixa1);
+ nb2 = pixaGetBoxaCount(pixa2);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixa1, i, copyflag);
+ pixaAddPix(pixad, pix, L_INSERT);
+ if (i < nb1) {
+ box = pixaGetBox(pixa1, i, L_COPY);
+ pixaAddBox(pixad, box, L_INSERT);
+ }
+ pix = pixaGetPix(pixa2, i, copyflag);
+ pixaAddPix(pixad, pix, L_INSERT);
+ if (i < nb2) {
+ box = pixaGetBox(pixa2, i, L_COPY);
+ pixaAddBox(pixad, box, L_INSERT);
+ }
+ }
+
+ return pixad;
+}
+
+
+/*!
+ * \brief pixaaJoin()
+ *
+ * \param[in] paad dest pixaa; add to this one
+ * \param[in] paas [optional] source pixaa; add from this one
+ * \param[in] istart starting index in pixaas
+ * \param[in] iend ending index in pixaas; use -1 to cat all
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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'
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaaCreate()
+ *
+ * \param[in] n initial number of pixa ptrs
+ * \return paa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIXAA *
+pixaaCreate(l_int32 n)
+{
+PIXAA *paa;
+
+ PROCNAME("pixaaCreate");
+
+ if (n <= 0 || n > MaxInitPtrArraySize)
+ n = InitialPtrArraySize;
+
+ paa = (PIXAA *)LEPT_CALLOC(1, sizeof(PIXAA));
+ 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;
+}
+
+
+/*!
+ * \brief pixaaCreateFromPixa()
+ *
+ * \param[in] pixa
+ * \param[in] n number specifying subdivision of pixa
+ * \param[in] type L_CHOOSE_CONSECUTIVE, L_CHOOSE_SKIP_BY
+ * \param[in] copyflag L_CLONE, L_COPY
+ * \return paa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaaDestroy()
+ *
+ * \param[in,out] ppaa use ptr address so it will 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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pixaa addition *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaaAddPixa()
+ *
+ * \param[in] paa
+ * \param[in] pixa to be added
+ * \param[in] 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_ok
+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) {
+ if (pixaaExtendArray(paa)) {
+ if (copyflag != L_INSERT)
+ pixaDestroy(&pixac);
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ }
+ paa->pixa[n] = pixac;
+ paa->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief pixaaExtendArray()
+ *
+ * \param[in] paa
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The max number of pixa ptrs is 1M.
+ * </pre>
+ */
+static l_int32
+pixaaExtendArray(PIXAA *paa)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("pixaaExtendArray");
+
+ if (!paa)
+ return ERROR_INT("paa not defined", procName, 1);
+ if (paa->nalloc > MaxPixaaPtrArraySize) /* belt & suspenders */
+ return ERROR_INT("paa has too many ptrs", procName, 1);
+ oldsize = paa->nalloc * sizeof(PIXA *);
+ newsize = 2 * oldsize;
+ if (newsize > 8 * MaxPixaaPtrArraySize)
+ return ERROR_INT("newsize > 8 MB; too large", procName, 1);
+
+ if ((paa->pixa = (PIXA **)reallocNew((void **)&paa->pixa,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+
+ paa->nalloc *= 2;
+ return 0;
+}
+
+
+/*!
+ * \brief pixaaAddPix()
+ *
+ * \param[in] paa input paa
+ * \param[in] index index of pixa in paa
+ * \param[in] pix to be added
+ * \param[in] box [optional] to be added
+ * \param[in] copyflag L_INSERT, L_COPY, L_CLONE
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaaAddBox()
+ *
+ * \param[in] paa
+ * \param[in] box
+ * \param[in] copyflag L_INSERT, L_COPY, L_CLONE
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The box can be used, for example, to hold the support region
+ * of a pixa that is being added to the pixaa.
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaaGetCount()
+ *
+ * \param[in] paa
+ * \param[out] pna [optional] number of pix in each pixa
+ * \return count, or 0 if no pixaa
+ *
+ * <pre>
+ * Notes:
+ * (1) If paa is empty, a returned na will also be empty.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaaGetPixa()
+ *
+ * \param[in] paa
+ * \param[in] index to the index-th pixa
+ * \param[in] accesstype L_COPY, L_CLONE, L_COPY_CLONE
+ * \return pixa, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixaaGetBoxa()
+ *
+ * \param[in] paa
+ * \param[in] accesstype L_COPY, L_CLONE
+ * \return boxa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixaaGetPix()
+ *
+ * \param[in] paa
+ * \param[in] index index into the pixa array in the pixaa
+ * \param[in] ipix index into the pix array in the pixa
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixaaVerifyDepth()
+ *
+ * \param[in] paa
+ * \param[out] psame 1 if all pix have the same depth; 0 otherwise
+ * \param[out] pmaxd [optional] max depth of all pix in pixaa
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is considered to be an error if any pixa have no pix.
+ * </pre>
+ */
+l_ok
+pixaaVerifyDepth(PIXAA *paa,
+ l_int32 *psame,
+ l_int32 *pmaxd)
+{
+l_int32 i, n, d, maxd, same, samed;
+PIXA *pixa;
+
+ PROCNAME("pixaaVerifyDepth");
+
+ if (pmaxd) *pmaxd = 0;
+ if (!psame)
+ return ERROR_INT("psame not defined", procName, 1);
+ *psame = 0;
+ if (!paa)
+ return ERROR_INT("paa not defined", procName, 1);
+ if ((n = pixaaGetCount(paa, NULL)) == 0)
+ return ERROR_INT("no pixa in paa", procName, 1);
+
+ pixa = pixaaGetPixa(paa, 0, L_CLONE);
+ pixaVerifyDepth(pixa, &same, &maxd); /* init same, maxd with first pixa */
+ pixaDestroy(&pixa);
+ for (i = 1; i < n; i++) {
+ pixa = pixaaGetPixa(paa, i, L_CLONE);
+ pixaVerifyDepth(pixa, &samed, &d);
+ pixaDestroy(&pixa);
+ maxd = L_MAX(maxd, d);
+ if (!samed || maxd != d)
+ same = 0;
+ }
+ *psame = same;
+ if (pmaxd) *pmaxd = maxd;
+ return 0;
+}
+
+
+/*!
+ * \brief pixaaVerifyDimensions()
+ *
+ * \param[in] paa
+ * \param[out] psame 1 if all pix have the same depth; 0 otherwise
+ * \param[out] pmaxw [optional] max width of all pix in pixaa
+ * \param[out] pmaxh [optional] max height of all pix in pixaa
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is considered to be an error if any pixa have no pix.
+ * </pre>
+ */
+l_ok
+pixaaVerifyDimensions(PIXAA *paa,
+ l_int32 *psame,
+ l_int32 *pmaxw,
+ l_int32 *pmaxh)
+{
+l_int32 i, n, w, h, maxw, maxh, same, same2;
+PIXA *pixa;
+
+ PROCNAME("pixaaVerifyDimensions");
+
+ if (pmaxw) *pmaxw = 0;
+ if (pmaxh) *pmaxh = 0;
+ if (!psame)
+ return ERROR_INT("psame not defined", procName, 1);
+ *psame = 0;
+ if (!paa)
+ return ERROR_INT("paa not defined", procName, 1);
+ if ((n = pixaaGetCount(paa, NULL)) == 0)
+ return ERROR_INT("no pixa in paa", procName, 1);
+
+ /* Init same; init maxw and maxh from first pixa */
+ pixa = pixaaGetPixa(paa, 0, L_CLONE);
+ pixaVerifyDimensions(pixa, &same, &maxw, &maxh);
+ pixaDestroy(&pixa);
+
+ for (i = 1; i < n; i++) {
+ pixa = pixaaGetPixa(paa, i, L_CLONE);
+ pixaVerifyDimensions(pixa, &same2, &w, &h);
+ pixaDestroy(&pixa);
+ maxw = L_MAX(maxw, w);
+ maxh = L_MAX(maxh, h);
+ if (!same2 || maxw != w || maxh != h)
+ same = 0;
+ }
+ *psame = same;
+ if (pmaxw) *pmaxw = maxw;
+ if (pmaxh) *pmaxh = maxh;
+ return 0;
+}
+
+
+/*!
+ * \brief pixaaIsFull()
+ *
+ * \param[in] paa
+ * \param[out] pfull 1 if all pixa in the paa have full pix arrays
+ * \return return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Does not require boxa associated with each pixa to be full.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaaInitFull()
+ *
+ * \param[in] paa typically empty
+ * \param[in] pixa to be replicated into the entire pixa ptr array
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaaReplacePixa()
+ *
+ * \param[in] paa
+ * \param[in] index to the index-th pixa
+ * \param[in] pixa insert to replace existing one
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaaClear()
+ *
+ * \param[in] paa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This destroys all pixa in the pixaa, and nulls the ptrs
+ * in the pixa ptr array.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaaTruncate()
+ *
+ * \param[in] paa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaRead()
+ *
+ * \param[in] filename
+ * \return pixa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The pix are stored in the file as png.
+ * If the png library is not linked, this will fail.
+ * </pre>
+ */
+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);
+ pixa = pixaReadStream(fp);
+ fclose(fp);
+ if (!pixa)
+ return (PIXA *)ERROR_PTR("pixa not read", procName, NULL);
+ return pixa;
+}
+
+
+/*!
+ * \brief pixaReadStream()
+ *
+ * \param[in] fp file stream
+ * \return pixa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The pix are stored in the file as png.
+ * If the png library is not linked, this will fail.
+ * (2) It is OK for the pixa to be empty.
+ * </pre>
+ */
+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 (n < 0)
+ return (PIXA *)ERROR_PTR("num pix ptrs < 0", procName, NULL);
+ if (n > MaxPixaPtrArraySize)
+ return (PIXA *)ERROR_PTR("too many pix ptrs", procName, NULL);
+ if (n == 0) L_INFO("the pixa is empty\n", procName);
+
+ 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;
+}
+
+
+/*!
+ * \brief pixaReadMem()
+ *
+ * \param[in] data of serialized pixa
+ * \param[in] size of data in bytes
+ * \return pixa, or NULL on error
+ */
+PIXA *
+pixaReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+PIXA *pixa;
+
+ PROCNAME("pixaReadMem");
+
+ if (!data)
+ return (PIXA *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (PIXA *)ERROR_PTR("stream not opened", procName, NULL);
+
+ pixa = pixaReadStream(fp);
+ fclose(fp);
+ if (!pixa) L_ERROR("pixa not read\n", procName);
+ return pixa;
+}
+
+
+/*!
+ * \brief pixaWriteDebug()
+ *
+ * \param[in] fname
+ * \param[in] pixa
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Debug version, intended for use in the library when writing
+ * to files in a temp directory with names that are compiled in.
+ * This is used instead of pixaWrite() for all such library calls.
+ * (2) The global variable LeptDebugOK defaults to 0, and can be set
+ * or cleared by the function setLeptDebugOK().
+ * </pre>
+ */
+l_ok
+pixaWriteDebug(const char *fname,
+ PIXA *pixa)
+{
+ PROCNAME("pixaWriteDebug");
+
+ if (LeptDebugOK) {
+ return pixaWrite(fname, pixa);
+ } else {
+ L_INFO("write to named temp file %s is disabled\n", procName, fname);
+ return 0;
+ }
+}
+
+
+/*!
+ * \brief pixaWrite()
+ *
+ * \param[in] filename
+ * \param[in] pixa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The pix are stored in the file as png.
+ * If the png library is not linked, this will fail.
+ * </pre>
+ */
+l_ok
+pixaWrite(const char *filename,
+ PIXA *pixa)
+{
+l_int32 ret;
+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);
+ ret = pixaWriteStream(fp, pixa);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("pixa not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixaWriteStream()
+ *
+ * \param[in] fp file stream opened for "wb"
+ * \param[in] pixa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The pix are stored in the file as png.
+ * If the png library is not linked, this will fail.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaWriteMem()
+ *
+ * \param[out] pdata data of serialized pixa
+ * \param[out] psize size of returned data
+ * \param[in] pixa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a pixa in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+pixaWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ PIXA *pixa)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("pixaWriteMem");
+
+ 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 (!pixa)
+ return ERROR_INT("pixa not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = pixaWriteStream(fp, pixa);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = pixaWriteStream(fp, pixa);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*!
+ * \brief pixaReadBoth()
+ *
+ * \param[in] filename
+ * \return pixa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This reads serialized files of either a pixa or a pixacomp,
+ * and returns a pixa in memory. It requires png and jpeg libraries.
+ * </pre>
+ */
+PIXA *
+pixaReadBoth(const char *filename)
+{
+char buf[32];
+char *sname;
+PIXA *pixa;
+PIXAC *pac;
+
+ PROCNAME("pixaReadBoth");
+
+ if (!filename)
+ return (PIXA *)ERROR_PTR("filename not defined", procName, NULL);
+
+ l_getStructStrFromFile(filename, L_STR_NAME, &sname);
+ if (!sname)
+ return (PIXA *)ERROR_PTR("struct name not found", procName, NULL);
+ snprintf(buf, sizeof(buf), "%s", sname);
+ LEPT_FREE(sname);
+
+ if (strcmp(buf, "Pixacomp") == 0) {
+ if ((pac = pixacompRead(filename)) == NULL)
+ return (PIXA *)ERROR_PTR("pac not made", procName, NULL);
+ pixa = pixaCreateFromPixacomp(pac, L_COPY);
+ pixacompDestroy(&pac);
+ } else if (strcmp(buf, "Pixa") == 0) {
+ if ((pixa = pixaRead(filename)) == NULL)
+ return (PIXA *)ERROR_PTR("pixa not made", procName, NULL);
+ } else {
+ return (PIXA *)ERROR_PTR("invalid file type", procName, NULL);
+ }
+ return pixa;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pixaa serialized I/O *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaaReadFromFiles()
+ *
+ * \param[in] dirname directory
+ * \param[in] substr [optional] substring filter on filenames; can be NULL
+ * \param[in] first 0-based
+ * \param[in] nfiles use 0 for everything from %first to the end
+ * \return paa, or NULL on error or if no pixa files are found.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaaRead()
+ *
+ * \param[in] filename
+ * \return paa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The pix are stored in the file as png.
+ * If the png library is not linked, this will fail.
+ * </pre>
+ */
+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);
+ paa = pixaaReadStream(fp);
+ fclose(fp);
+ if (!paa)
+ return (PIXAA *)ERROR_PTR("paa not read", procName, NULL);
+ return paa;
+}
+
+
+/*!
+ * \brief pixaaReadStream()
+ *
+ * \param[in] fp file stream
+ * \return paa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The pix are stored in the file as png.
+ * If the png library is not linked, this will fail.
+ * (2) It is OK for the pixaa to be empty.
+ * </pre>
+ */
+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 (n < 0)
+ return (PIXAA *)ERROR_PTR("num pixa ptrs < 0", procName, NULL);
+ if (n > MaxPixaaPtrArraySize)
+ return (PIXAA *)ERROR_PTR("too many pixa ptrs", procName, NULL);
+ if (n == 0) L_INFO("the pixaa is empty\n", procName);
+
+ if ((paa = pixaaCreate(n)) == NULL)
+ return (PIXAA *)ERROR_PTR("paa not made", procName, NULL);
+ if ((boxa = boxaReadStream(fp)) == NULL) {
+ pixaaDestroy(&paa);
+ 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) {
+ pixaaDestroy(&paa);
+ return (PIXAA *)ERROR_PTR("text reading", procName, NULL);
+ }
+ if ((pixa = pixaReadStream(fp)) == NULL) {
+ pixaaDestroy(&paa);
+ return (PIXAA *)ERROR_PTR("pixa not read", procName, NULL);
+ }
+ pixaaAddPixa(paa, pixa, L_INSERT);
+ }
+
+ return paa;
+}
+
+
+/*!
+ * \brief pixaaReadMem()
+ *
+ * \param[in] data of serialized pixaa
+ * \param[in] size of data in bytes
+ * \return paa, or NULL on error
+ */
+PIXAA *
+pixaaReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+PIXAA *paa;
+
+ PROCNAME("paaReadMem");
+
+ if (!data)
+ return (PIXAA *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (PIXAA *)ERROR_PTR("stream not opened", procName, NULL);
+
+ paa = pixaaReadStream(fp);
+ fclose(fp);
+ if (!paa) L_ERROR("paa not read\n", procName);
+ return paa;
+}
+
+
+/*!
+ * \brief pixaaWrite()
+ *
+ * \param[in] filename
+ * \param[in] paa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The pix are stored in the file as png.
+ * If the png library is not linked, this will fail.
+ * </pre>
+ */
+l_ok
+pixaaWrite(const char *filename,
+ PIXAA *paa)
+{
+l_int32 ret;
+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);
+ ret = pixaaWriteStream(fp, paa);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("paa not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixaaWriteStream()
+ *
+ * \param[in] fp file stream opened for "wb"
+ * \param[in] paa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The pix are stored in the file as png.
+ * If the png library is not linked, this will fail.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaaWriteMem()
+ *
+ * \param[out] pdata data of serialized pixaa
+ * \param[out] psize size of returned data
+ * \param[in] paa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a pixaa in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+pixaaWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ PIXAA *paa)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("pixaaWriteMem");
+
+ 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 (!paa)
+ return ERROR_INT("paa not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = pixaaWriteStream(fp, paa);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = pixaaWriteStream(fp, paa);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
diff --git a/leptonica/src/pixacc.c b/leptonica/src/pixacc.c
new file mode 100644
index 00000000..8b7273b2
--- /dev/null
+++ b/leptonica/src/pixacc.c
@@ -0,0 +1,355 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pixacc.c
+ * <pre>
+ *
+ * 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);
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*
+ * Pixacc creation, destruction *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaccCreate()
+ *
+ * \param[in] w, h of 32 bpp internal Pix
+ * \param[in] negflag 0 if only positive numbers are involved;
+ * 1 if there will be negative numbers
+ * \return pixacc, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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) {
+ pixaccDestroy(&pixacc);
+ return (PIXACC *)ERROR_PTR("pix not made", procName, NULL);
+ }
+
+ if (negflag) {
+ pixacc->offset = 0x40000000;
+ pixSetAllArbitrary(pixacc->pix, pixacc->offset);
+ }
+
+ return pixacc;
+}
+
+
+/*!
+ * \brief pixaccCreateFromPix()
+ *
+ * \param[in] pix
+ * \param[in] negflag 0 if only positive numbers are involved;
+ * 1 if there will be negative numbers
+ * \return pixacc, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixaccCreate()
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaccDestroy()
+ *
+ * \param[in,out] ppixacc will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pixacc finalization *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaccFinal()
+ *
+ * \param[in] pixacc
+ * \param[in] 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaccGetPix()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixaccGetOffset()
+ *
+ * \param[in] 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaccAdd()
+ *
+ * \param[in] pixacc
+ * \param[in] pix to be added
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaccSubtract()
+ *
+ * \param[in] pixacc
+ * \param[in] pix to be subtracted
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaccMultConst()
+ *
+ * \param[in] pixacc
+ * \param[in] factor
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaccMultConstAccumulate()
+ *
+ * \param[in] pixacc
+ * \param[in] pix
+ * \param[in] factor
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This creates a temp pix that is %pix multiplied by the
+ * constant %factor. It then adds that into %pixacc.
+ * </pre>
+ */
+l_ok
+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/leptonica/src/pixafunc1.c b/leptonica/src/pixafunc1.c
new file mode 100644
index 00000000..be2958e4
--- /dev/null
+++ b/leptonica/src/pixafunc1.c
@@ -0,0 +1,3112 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pixafunc1.c
+ * <pre>
+ *
+ * Filters
+ * PIX *pixSelectBySize()
+ * PIXA *pixaSelectBySize()
+ * NUMA *pixaMakeSizeIndicator()
+ *
+ * PIX *pixSelectByPerimToAreaRatio()
+ * PIXA *pixaSelectByPerimToAreaRatio()
+ * PIX *pixSelectByPerimSizeRatio()
+ * PIXA *pixaSelectByPerimSizeRatio()
+ * PIX *pixSelectByAreaFraction()
+ * PIXA *pixaSelectByAreaFraction()
+ * PIX *pixSelectByArea()
+ * PIXA *pixaSelectByArea()
+ * PIX *pixSelectByWidthHeightRatio()
+ * PIXA *pixaSelectByWidthHeightRatio()
+ * PIXA *pixaSelectByNumConnComp()
+ *
+ * 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()
+ * PIXA *pixaScaleToSizeRel()
+ * PIXA *pixaScale()
+ * PIXA *pixaScaleBySampling()
+ *
+ * Pixa rotation and translation
+ * PIXA *pixaRotate()
+ * PIXA *pixaRotateOrth()
+ * PIXA *pixaTranslate()
+ *
+ * Miscellaneous
+ * PIXA *pixaAddBorderGeneral()
+ * PIXA *pixaaFlattenToPixa()
+ * l_int32 pixaaSizeRange()
+ * l_int32 pixaSizeRange()
+ * PIXA *pixaClipToPix()
+ * PIXA *pixaClipToForeground()
+ * l_int32 pixaGetRenderingDepth()
+ * l_int32 pixaHasColor()
+ * l_int32 pixaAnyColormaps()
+ * l_int32 pixaGetDepthInfo()
+ * PIXA *pixaConvertToSameDepth()
+ * l_int32 pixaEqual()
+ * l_int32 pixaSetFullSizeBoxa()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 MinCompsForBinSort = 200;
+
+ /* Don't rotate any angle smaller than this */
+static const l_float32 MinAngleToRotate = 0.001f; /* radians; ~0.06 deg */
+
+/*---------------------------------------------------------------------*
+ * 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
+ * Area
+ * 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);
+ */
+
+
+/*!
+ * \brief pixSelectBySize()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] width, height threshold dimensions
+ * \param[in] connectivity 4 or 8
+ * \param[in] type L_SELECT_WIDTH, L_SELECT_HEIGHT,
+ * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH
+ * \param[in] relation L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 otherwise
+ * \return filtered pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaSelectBySize()
+ *
+ * \param[in] pixas
+ * \param[in] width, height threshold dimensions
+ * \param[in] type L_SELECT_WIDTH, L_SELECT_HEIGHT,
+ * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH
+ * \param[in] relation L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 otherwise
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaMakeSizeIndicator()
+ *
+ * \param[in] pixa
+ * \param[in] width, height threshold dimensions
+ * \param[in] type L_SELECT_WIDTH, L_SELECT_HEIGHT,
+ * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixSelectByPerimToAreaRatio()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] thresh threshold ratio of fg boundary to fg pixels
+ * \param[in] connectivity 4 or 8
+ * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaSelectByPerimToAreaRatio()
+ *
+ * \param[in] pixas
+ * \param[in] thresh threshold ratio of fg boundary to fg pixels
+ * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Returns a pixa clone if no components are removed.
+ * (2) Uses pix and box clones in the new pixa.
+ * (3) See pixSelectByPerimToAreaRatio().
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixSelectByPerimSizeRatio()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] thresh threshold ratio of fg boundary to fg pixels
+ * \param[in] connectivity 4 or 8
+ * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaSelectByPerimSizeRatio()
+ *
+ * \param[in] pixas
+ * \param[in] thresh threshold ratio of fg boundary to b.b. circumference
+ * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Returns a pixa clone if no components are removed.
+ * (2) Uses pix and box clones in the new pixa.
+ * (3) See pixSelectByPerimSizeRatio().
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixSelectByAreaFraction()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] thresh threshold ratio of fg pixels to (w * h)
+ * \param[in] connectivity 4 or 8
+ * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaSelectByAreaFraction()
+ *
+ * \param[in] pixas
+ * \param[in] thresh threshold ratio of fg pixels to (w * h)
+ * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixSelectByArea()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] thresh threshold number of FG pixels
+ * \param[in] connectivity 4 or 8
+ * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The args specify constraints on the number of foreground
+ * pixels in 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 number of fg pixels
+ * in each component.
+ * (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components
+ * with less than the threshold number of fg pixels, and
+ * L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
+ * </pre>
+ */
+PIX *
+pixSelectByArea(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("pixSelectByArea");
+
+ 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 = pixaSelectByArea(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;
+}
+
+
+/*!
+ * \brief pixaSelectByArea()
+ *
+ * \param[in] pixas
+ * \param[in] thresh threshold number of fg pixels
+ * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * 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 number of fg pixels
+ * in the component.
+ * (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components
+ * with less than the threshold number of fg pixels, and
+ * L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
+ * </pre>
+ */
+PIXA *
+pixaSelectByArea(PIXA *pixas,
+ l_float32 thresh,
+ l_int32 type,
+ l_int32 *pchanged)
+{
+NUMA *na, *nai;
+PIXA *pixad;
+
+ PROCNAME("pixaSelectByArea");
+
+ 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 area of each component */
+ na = pixaCountPixels(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;
+}
+
+
+/*!
+ * \brief pixSelectByWidthHeightRatio()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] thresh threshold ratio of width/height
+ * \param[in] connectivity 4 or 8
+ * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaSelectByWidthHeightRatio()
+ *
+ * \param[in] pixas
+ * \param[in] thresh threshold ratio of width/height
+ * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaSelectByNumConnComp()
+ *
+ * \param[in] pixas
+ * \param[in] nmin minimum number of components
+ * \param[in] nmax maximum number of components
+ * \param[in] connectivity 4 or 8
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Returns a pixa clone if no components are removed.
+ * (2) Uses pix and box clones in the new pixa.
+ * (3) This filters by the number of connected components in
+ * a given range.
+ * </pre>
+ */
+PIXA *
+pixaSelectByNumConnComp(PIXA *pixas,
+ l_int32 nmin,
+ l_int32 nmax,
+ l_int32 connectivity,
+ l_int32 *pchanged)
+{
+l_int32 n, i, count;
+NUMA *na;
+PIX *pix;
+PIXA *pixad;
+
+ PROCNAME("pixaSelectByNumConnComp");
+
+ if (pchanged) *pchanged = 0;
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (nmin > nmax)
+ return (PIXA *)ERROR_PTR("nmin > nmax", procName, NULL);
+ if (connectivity != 4 && connectivity != 8)
+ return (PIXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+ /* Get indicator array based on number of c.c. */
+ n = pixaGetCount(pixas);
+ na = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixas, i, L_CLONE);
+ pixCountConnComp(pix, connectivity, &count);
+ if (count >= nmin && count <= nmax)
+ numaAddNumber(na, 1);
+ else
+ numaAddNumber(na, 0);
+ pixDestroy(&pix);
+ }
+
+ /* Filter to get output */
+ pixad = pixaSelectWithIndicator(pixas, na, pchanged);
+ numaDestroy(&na);
+ return pixad;
+}
+
+
+/*!
+ * \brief pixaSelectWithIndicator()
+ *
+ * \param[in] pixas
+ * \param[in] na indicator numa
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIXA *
+pixaSelectWithIndicator(PIXA *pixas,
+ NUMA *na,
+ l_int32 *pchanged)
+{
+l_int32 i, n, nbox, ival, nsave;
+BOX *box;
+PIX *pix1;
+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;
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ pixaAddPix(pixad, pix1, L_INSERT);
+ if (nbox == n) { /* fully populated boxa */
+ box = pixaGetBox(pixas, i, L_CLONE);
+ pixaAddBox(pixad, box, L_INSERT);
+ }
+ }
+
+ return pixad;
+}
+
+
+/*!
+ * \brief pixRemoveWithIndicator()
+ *
+ * \param[in] pixs 1 bpp pix from which components are removed; in-place
+ * \param[in] pixa of connected components in pixs
+ * \param[in] na numa indicator: remove components corresponding to 1s
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This complements pixAddWithIndicator(). Here, the selected
+ * components are set subtracted from pixs.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixAddWithIndicator()
+ *
+ * \param[in] pixs 1 bpp pix from which components are added; in-place
+ * \param[in] pixa of connected components, some of which will be put
+ * into pixs
+ * \param[in] na numa indicator: add components corresponding to 1s
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This complements pixRemoveWithIndicator(). Here, the selected
+ * components are added to pixs.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaSelectWithString()
+ *
+ * \param[in] pixas
+ * \param[in] str string of indices into pixa, giving the pix to
+ * be selected
+ * \param[out] perror [optional] 1 if any indices are invalid;
+ * 0 if all indices are valid
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Returns a pixa with copies of selected pix.
+ * (2) Associated boxes are also copied, if fully populated.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaRenderComponent()
+ *
+ * \param[in] pixs [optional] 1 bpp pix
+ * \param[in] pixa of 1 bpp connected components, one of which will
+ * be rendered in pixs, with its origin determined
+ * by the associated box.
+ * \param[in] index of component to be rendered
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixaRenderComponent(PIX *pixs,
+ PIXA *pixa,
+ l_int32 index)
+{
+l_int32 n, x, y, w, h, same, maxd;
+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, &same, &maxd);
+ if (maxd > 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaSort()
+ *
+ * \param[in] pixas
+ * \param[in] 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
+ * \param[in] sortorder L_SORT_INCREASING, L_SORT_DECREASING
+ * \param[out] pnaindex [optional] index of sorted order into
+ * original array
+ * \param[in] copyflag L_COPY, L_CLONE
+ * \return pixad sorted version of pixas, or NULL on error
+ *
+ * <pre>
+ * 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) If the boxa is empty, it makes one corresponding to the
+ * dimensions of each pix, which allows meaningful sorting on
+ * all types except x and y.
+ * (3) The copyflag refers to the pix and box copies that are
+ * inserted into the sorted pixa. These are either L_COPY
+ * or L_CLONE.
+ * </pre>
+ */
+PIXA *
+pixaSort(PIXA *pixas,
+ l_int32 sorttype,
+ l_int32 sortorder,
+ NUMA **pnaindex,
+ l_int32 copyflag)
+{
+l_int32 i, n, nb, 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);
+
+ /* Check the pixa and boxa counts. Make a boxa if required. */
+ if ((n = pixaGetCount(pixas)) == 0) {
+ L_INFO("no pix in pixa\n", procName);
+ return pixaCopy(pixas, copyflag);
+ }
+ if ((boxa = pixas->boxa) == NULL) /* not owned; do not destroy */
+ return (PIXA *)ERROR_PTR("boxa not found!", procName, NULL);
+ nb = boxaGetCount(boxa);
+ if (nb == 0) {
+ pixaSetFullSizeBoxa(pixas);
+ nb = n;
+ boxa = pixas->boxa; /* not owned */
+ if (sorttype == L_SORT_BY_X || sorttype == L_SORT_BY_Y)
+ L_WARNING("sort by x or y where all values are 0\n", procName);
+ }
+ if (nb != n)
+ return (PIXA *)ERROR_PTR("boxa and pixa counts differ", procName, NULL);
+
+ /* Use O(n) binsort if possible */
+ if (n > MinCompsForBinSort &&
+ ((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 */
+ naindex = numaGetSortIndex(na, sortorder);
+ numaDestroy(&na);
+ if (!naindex)
+ return (PIXA *)ERROR_PTR("naindex not made", procName, NULL);
+
+ /* Build up sorted pixa using sort index */
+ if ((pixad = pixaSortByIndex(pixas, naindex, copyflag)) == NULL) {
+ numaDestroy(&naindex);
+ return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);
+ }
+
+ if (pnaindex)
+ *pnaindex = naindex;
+ else
+ numaDestroy(&naindex);
+ return pixad;
+}
+
+
+/*!
+ * \brief pixaBinSort()
+ *
+ * \param[in] pixas
+ * \param[in] sorttype L_SORT_BY_X, L_SORT_BY_Y, L_SORT_BY_WIDTH,
+ * L_SORT_BY_HEIGHT, L_SORT_BY_PERIMETER
+ * \param[in] sortorder L_SORT_INCREASING, L_SORT_DECREASING
+ * \param[out] pnaindex [optional] index of sorted order into
+ * original array
+ * \param[in] copyflag L_COPY, L_CLONE
+ * \return pixad sorted version of pixas, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+ naindex = numaGetBinSortIndex(na, sortorder);
+ numaDestroy(&na);
+ if (!naindex)
+ return (PIXA *)ERROR_PTR("naindex not made", procName, NULL);
+
+ /* Build up sorted pixa using sort index */
+ if ((pixad = pixaSortByIndex(pixas, naindex, copyflag)) == NULL) {
+ numaDestroy(&naindex);
+ return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);
+ }
+
+ if (pnaindex)
+ *pnaindex = naindex;
+ else
+ numaDestroy(&naindex);
+ return pixad;
+}
+
+
+/*!
+ * \brief pixaSortByIndex()
+ *
+ * \param[in] pixas
+ * \param[in] naindex na that maps from the new pixa to the input pixa
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixaSort2dByIndex()
+ *
+ * \param[in] pixas
+ * \param[in] naa numaa that maps from the new pixaa to the input pixas
+ * \param[in] 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaSelectRange()
+ *
+ * \param[in] pixas
+ * \param[in] first use 0 to select from the beginning
+ * \param[in] last use -1 to select to the end
+ * \param[in] copyflag L_COPY, L_CLONE
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 (last >= n) {
+ L_WARNING("last = %d is beyond max index = %d; adjusting\n",
+ procName, last, n - 1);
+ last = n - 1;
+ }
+ 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;
+}
+
+
+/*!
+ * \brief pixaaSelectRange()
+ *
+ * \param[in] paas
+ * \param[in] first use 0 to select from the beginning
+ * \param[in] last use -1 to select to the end
+ * \param[in] copyflag L_COPY, L_CLONE
+ * \return paad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 (last >= n) {
+ L_WARNING("last = %d is beyond max index = %d; adjusting\n",
+ procName, last, n - 1);
+ last = n - 1;
+ }
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaaScaleToSize()
+ *
+ * \param[in] paas
+ * \param[in] wd target width; use 0 if using height as target
+ * \param[in] hd target height; use 0 if using width as target
+ * \return paad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaaScaleToSizeVar()
+ *
+ * \param[in] paas
+ * \param[in] nawd [optional] target widths; use NULL if using height
+ * \param[in] nahd [optional] target height; use NULL if using width
+ * \return paad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaScaleToSize()
+ *
+ * \param[in] pixas
+ * \param[in] wd target width; use 0 if using height as target
+ * \param[in] hd target height; use 0 if using width as target
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixaaScaleToSize()
+ * </pre>
+ */
+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) /* no scaling requested */
+ return pixaCopy(pixas, L_CLONE);
+
+ 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;
+}
+
+
+/*!
+ * \brief pixaScaleToSizeRel()
+ *
+ * \param[in] pixas
+ * \param[in] delw change in width, in pixels; 0 means no change
+ * \param[in] delh change in height, in pixels; 0 means no change
+ * return pixad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If a requested change in a pix is not possible because
+ * either the requested width or height is <= 0, issue a
+ * warning and return a copy.
+ * </pre>
+ */
+PIXA *
+pixaScaleToSizeRel(PIXA *pixas,
+ l_int32 delw,
+ l_int32 delh)
+{
+l_int32 n, i;
+PIX *pix1, *pix2;
+PIXA *pixad;
+
+ PROCNAME("pixaScaleToSizeRel");
+
+ 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 = pixScaleToSizeRel(pix1, delw, delh);
+ if (pix2) {
+ pixaAddPix(pixad, pix2, L_INSERT);
+ } else {
+ L_WARNING("relative scale to size failed; use a copy\n", procName);
+ pixaAddPix(pixad, pix1, L_COPY);
+ }
+ pixDestroy(&pix1);
+ }
+ return pixad;
+}
+
+
+/*!
+ * \brief pixaScale()
+ *
+ * \param[in] pixas
+ * \param[in] scalex
+ * \param[in] scaley
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If pixas has a full boxes, it is scaled as well.
+ * </pre>
+ */
+PIXA *
+pixaScale(PIXA *pixas,
+ l_float32 scalex,
+ l_float32 scaley)
+{
+l_int32 i, n, nb;
+BOXA *boxa1, *boxa2;
+PIX *pix1, *pix2;
+PIXA *pixad;
+
+ PROCNAME("pixaScale");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (scalex <= 0.0 || scaley <= 0.0)
+ return (PIXA *)ERROR_PTR("invalid scaling parameters", procName, NULL);
+
+ n = pixaGetCount(pixas);
+ pixad = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ pix2 = pixScale(pix1, scalex, scaley);
+ pixCopyText(pix2, pix1);
+ pixaAddPix(pixad, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ boxa1 = pixaGetBoxa(pixas, L_CLONE);
+ nb = boxaGetCount(boxa1);
+ if (nb == n) {
+ boxa2 = boxaTransform(boxa1, 0, 0, scalex, scaley);
+ pixaSetBoxa(pixad, boxa2, L_INSERT);
+ }
+ boxaDestroy(&boxa1);
+ return pixad;
+}
+
+
+/*!
+ * \brief pixaScaleBySampling()
+ *
+ * \param[in] pixas
+ * \param[in] scalex
+ * \param[in] scaley
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If pixas has a full boxes, it is scaled as well.
+ * </pre>
+ */
+PIXA *
+pixaScaleBySampling(PIXA *pixas,
+ l_float32 scalex,
+ l_float32 scaley)
+{
+l_int32 i, n, nb;
+BOXA *boxa1, *boxa2;
+PIX *pix1, *pix2;
+PIXA *pixad;
+
+ PROCNAME("pixaScaleBySampling");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (scalex <= 0.0 || scaley <= 0.0)
+ return (PIXA *)ERROR_PTR("invalid scaling parameters", procName, NULL);
+
+ n = pixaGetCount(pixas);
+ pixad = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ pix2 = pixScaleBySampling(pix1, scalex, scaley);
+ pixCopyText(pix2, pix1);
+ pixaAddPix(pixad, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ boxa1 = pixaGetBoxa(pixas, L_CLONE);
+ nb = boxaGetCount(boxa1);
+ if (nb == n) {
+ boxa2 = boxaTransform(boxa1, 0, 0, scalex, scaley);
+ pixaSetBoxa(pixad, boxa2, L_INSERT);
+ }
+ boxaDestroy(&boxa1);
+ return pixad;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pixa rotation and translation *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaRotate()
+ *
+ * \param[in] pixas 1, 2, 4, 8, 32 bpp rgb
+ * \param[in] angle rotation angle in radians; clockwise is positive
+ * \param[in] type L_ROTATE_AREA_MAP, L_ROTATE_SHEAR, L_ROTATE_SAMPLING
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \param[in] width original width; use 0 to avoid embedding
+ * \param[in] height original height; use 0 to avoid embedding
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Each pix is rotated about its center. See pixRotate() for details.
+ * (2) The boxa array is copied. Why is it not rotated?
+ * If a boxa exists, the array of boxes is in 1-to-1
+ * correspondence with the array of pix, and each box typically
+ * represents the location of the pix relative to an image from
+ * which it has been extracted. Like the pix, we could rotate
+ * each box around its center, and then generate a box that
+ * contains all four corners, as is done in boxaRotate(), but
+ * this seems unnecessary.
+ * </pre>
+ */
+PIXA *
+pixaRotate(PIXA *pixas,
+ l_float32 angle,
+ l_int32 type,
+ l_int32 incolor,
+ l_int32 width,
+ l_int32 height)
+{
+l_int32 i, n;
+BOXA *boxa;
+PIX *pixs, *pixd;
+PIXA *pixad;
+
+ PROCNAME("pixaRotate");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (type != L_ROTATE_SHEAR && type != L_ROTATE_AREA_MAP &&
+ type != L_ROTATE_SAMPLING)
+ return (PIXA *)ERROR_PTR("invalid type", procName, NULL);
+ if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+ return (PIXA *)ERROR_PTR("invalid incolor", procName, NULL);
+ if (L_ABS(angle) < MinAngleToRotate)
+ return pixaCopy(pixas, L_COPY);
+
+ n = pixaGetCount(pixas);
+ if ((pixad = pixaCreate(n)) == NULL)
+ return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);
+ boxa = pixaGetBoxa(pixad, L_COPY);
+ pixaSetBoxa(pixad, boxa, L_INSERT);
+ for (i = 0; i < n; i++) {
+ if ((pixs = pixaGetPix(pixas, i, L_CLONE)) == NULL) {
+ pixaDestroy(&pixad);
+ return (PIXA *)ERROR_PTR("pixs not found", procName, NULL);
+ }
+ pixd = pixRotate(pixs, angle, type, incolor, width, height);
+ pixaAddPix(pixad, pixd, L_INSERT);
+ pixDestroy(&pixs);
+ }
+
+ return pixad;
+}
+
+
+/*!
+ * \brief pixaRotateOrth()
+ *
+ * \param[in] pixas
+ * \param[in] rotation 0 = noop, 1 = 90 deg, 2 = 180 deg, 3 = 270 deg;
+ * all rotations are clockwise
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Rotates each pix in the pixa. Rotates and saves the boxes in
+ * the boxa if the boxa is full.
+ * </pre>
+ */
+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) {
+ pixaDestroy(&pixad);
+ 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;
+}
+
+
+/*!
+ * \brief pixaTranslate()
+ *
+ * \param[in] pixas
+ * \param[in] hshift horizontal shift; hshift > 0 is to right
+ * \param[in] vshift vertical shift; vshift > 0 is down
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixad, or NULL on error.
+ */
+PIXA *
+pixaTranslate(PIXA *pixas,
+ l_int32 hshift,
+ l_int32 vshift,
+ l_int32 incolor)
+{
+l_int32 i, n, nb;
+BOXA *boxas, *boxad;
+PIX *pixs, *pixd;
+PIXA *pixad;
+
+ PROCNAME("pixaTranslate");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (hshift == 0 && vshift == 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) {
+ pixaDestroy(&pixad);
+ return (PIXA *)ERROR_PTR("pixs not found", procName, NULL);
+ }
+ pixd = pixTranslate(NULL, pixs, hshift, vshift, incolor);
+ pixaAddPix(pixad, pixd, L_INSERT);
+ pixDestroy(&pixs);
+ }
+ if (n == nb) {
+ boxas = pixaGetBoxa(pixas, L_CLONE);
+ boxad = boxaTransform(boxas, hshift, vshift, 1.0, 1.0);
+ pixaSetBoxa(pixad, boxad, L_INSERT);
+ boxaDestroy(&boxas);
+ }
+
+ return pixad;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Miscellaneous functions *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaAddBorderGeneral()
+ *
+ * \param[in] pixad can be null or equal to pixas
+ * \param[in] pixas containing pix of all depths; colormap ok
+ * \param[in] left, right, top, bot number of pixels added
+ * \param[in] val value of added border pixels
+ * \return pixad with border added to each pix, including on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaaFlattenToPixa()
+ *
+ * \param[in] paa
+ * \param[out] pnaindex [optional] the pixa index in the pixaa
+ * \param[in] copyflag L_COPY or L_CLONE
+ * \return pixa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaaSizeRange()
+ *
+ * \param[in] paa
+ * \param[out] pminw, pminh, pmaxw, pmaxh [optional] range of
+ * dimensions of all boxes
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaSizeRange()
+ *
+ * \param[in] pixa
+ * \param[out] pminw, pminh, pmaxw, pmaxh [optional] range of
+ * dimensions of pix in the array
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaClipToPix()
+ *
+ * \param[in] pixas
+ * \param[in] pixs
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaClipToForeground()
+ *
+ * \param[in] pixas
+ * \param[out] ppixad [optional] pixa of clipped pix returned
+ * \param[out] pboxa [optional] clipping boxes returned
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) At least one of [&pixd, &boxa] must be specified.
+ * (2) Any pix with no fg pixels is skipped.
+ * (3) See pixClipToForeground().
+ * </pre>
+ */
+l_ok
+pixaClipToForeground(PIXA *pixas,
+ PIXA **ppixad,
+ BOXA **pboxa)
+{
+l_int32 i, n;
+BOX *box1;
+PIX *pix1, *pix2;
+
+ PROCNAME("pixaClipToForeground");
+
+ if (ppixad) *ppixad = NULL;
+ if (pboxa) *pboxa = NULL;
+ if (!pixas)
+ return ERROR_INT("pixas not defined", procName, 1);
+ if (!ppixad && !pboxa)
+ return ERROR_INT("no output requested", procName, 1);
+
+ n = pixaGetCount(pixas);
+ if (ppixad) *ppixad = pixaCreate(n);
+ if (pboxa) *pboxa = boxaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ pixClipToForeground(pix1, &pix2, &box1);
+ pixDestroy(&pix1);
+ if (ppixad)
+ pixaAddPix(*ppixad, pix2, L_INSERT);
+ else
+ pixDestroy(&pix2);
+ if (pboxa)
+ boxaAddBox(*pboxa, box1, L_INSERT);
+ else
+ boxDestroy(&box1);
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixaGetRenderingDepth()
+ *
+ * \param[in] pixa
+ * \param[out] pdepth depth required to render if all colormaps are removed
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaHasColor()
+ *
+ * \param[in] pixa
+ * \param[out] phascolor 1 if any pix is rgb or has a colormap with color;
+ * 0 otherwise
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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);
+ hascolor = 0;
+ 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;
+}
+
+
+/*!
+ * \brief pixaAnyColormaps()
+ *
+ * \param[in] pixa
+ * \param[out] phascmap 1 if any pix has a colormap; 0 otherwise
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaGetDepthInfo()
+ *
+ * \param[in] pixa
+ * \param[out] pmaxdepth [optional] max pixel depth of pix in pixa
+ * \param[out] psame [optional] true if all depths are equal
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaConvertToSameDepth()
+ *
+ * \param[in] pixas
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIXA *
+pixaConvertToSameDepth(PIXA *pixas)
+{
+l_int32 i, n, same, hascmap, maxdepth;
+BOXA *boxa;
+PIX *pix1, *pix2;
+PIXA *pixa1, *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) {
+ pixa1 = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ pix2 = pixConvertTo32(pix1);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ } else {
+ pixa1 = pixaCopy(pixas, L_CLONE);
+ }
+
+ pixaGetDepthInfo(pixa1, &maxdepth, &same);
+ if (!same) { /* at least one pix has depth < maxdepth */
+ pixad = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa1, i, L_CLONE);
+ if (maxdepth <= 8)
+ pix2 = pixConvertTo8(pix1, 0);
+ else
+ pix2 = pixConvertTo32(pix1);
+ pixaAddPix(pixad, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ } else {
+ pixad = pixaCopy(pixa1, L_CLONE);
+ }
+
+ boxa = pixaGetBoxa(pixas, L_COPY);
+ pixaSetBoxa(pixad, boxa, L_INSERT);
+ pixaDestroy(&pixa1);
+ return pixad;
+}
+
+
+/*!
+ * \brief pixaEqual()
+ *
+ * \param[in] pixa1
+ * \param[in] pixa2
+ * \param[in] maxdist
+ * \param[out] pnaindex [optional] index array of correspondences
+ * \param[out] psame 1 if equal; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 of the pix 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.
+ * </pre>
+ */
+l_ok
+pixaEqual(PIXA *pixa1,
+ PIXA *pixa2,
+ l_int32 maxdist,
+ NUMA **pnaindex,
+ l_int32 *psame)
+{
+l_int32 i, j, n, empty1, empty2, 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;
+
+ /* If there are no boxes, strict ordering of the pix in each
+ * pixa is required. */
+ boxa1 = pixaGetBoxa(pixa1, L_CLONE);
+ boxa2 = pixaGetBoxa(pixa2, L_CLONE);
+ empty1 = (boxaGetCount(boxa1) == 0) ? 1 : 0;
+ empty2 = (boxaGetCount(boxa2) == 0) ? 1 : 0;
+ if (!empty1 && !empty2) {
+ boxaEqual(boxa1, boxa2, maxdist, &na, &sameboxa);
+ if (!sameboxa) {
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ numaDestroy(&na);
+ return 0;
+ }
+ }
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ if ((!empty1 && empty2) || (empty1 && !empty2))
+ 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;
+}
+
+
+/*!
+ * \brief pixaSetFullSizeBoxa()
+ *
+ * \param[in] pixa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Replaces the existing boxa. Each box gives the dimensions
+ * of the corresponding pix. This is needed for functions
+ * like pixaSort() that sort based on the boxes.
+ * </pre>
+ */
+l_ok
+pixaSetFullSizeBoxa(PIXA *pixa)
+{
+l_int32 i, n, w, h;
+BOX *box;
+BOXA *boxa;
+PIX *pix;
+
+ PROCNAME("pixaSetFullSizeBoxa");
+
+ if (!pixa)
+ return ERROR_INT("pixa not defined", procName, 1);
+ if ((n = pixaGetCount(pixa)) == 0) {
+ L_INFO("pixa contains no pix\n", procName);
+ return 0;
+ }
+
+ boxa = boxaCreate(n);
+ pixaSetBoxa(pixa, boxa, L_INSERT);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixa, i, L_CLONE);
+ pixGetDimensions(pix, &w, &h, NULL);
+ box = boxCreate(0, 0, w, h);
+ boxaAddBox(boxa, box, L_INSERT);
+ pixDestroy(&pix);
+ }
+ return 0;
+}
+
diff --git a/leptonica/src/pixafunc2.c b/leptonica/src/pixafunc2.c
new file mode 100644
index 00000000..b34c4cca
--- /dev/null
+++ b/leptonica/src/pixafunc2.c
@@ -0,0 +1,2610 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pixafunc2.c
+ * <pre>
+ *
+ * Pixa display (render into a pix)
+ * PIX *pixaDisplay()
+ * PIX *pixaDisplayRandomCmap()
+ * PIX *pixaDisplayLinearly()
+ * PIX *pixaDisplayOnLattice()
+ * PIX *pixaDisplayUnsplit()
+ * PIX *pixaDisplayTiled()
+ * PIX *pixaDisplayTiledInRows()
+ * PIX *pixaDisplayTiledInColumns()
+ * 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 *pixaConvertTo8Colormap()
+ * PIXA *pixaConvertTo32()
+ *
+ * Pixa constrained selection and pdf generation
+ * PIXA *pixaConstrainedSelect()
+ * l_int32 pixaSelectToPdf()
+ *
+ * Generate pixa from tiled images
+ * PIXA *pixaMakeFromTiledPixa()
+ * PIXA *pixaMakeFromTiledPix()
+ * l_int32 pixGetTileCount()
+ *
+ * Pixa display into multiple tiles
+ * PIXA *pixaDisplayMultiTiled()
+ *
+ * Split pixa into files
+ * l_int32 pixaSplitIntoFiles()
+ *
+ * Tile N-Up
+ * l_int32 convertToNUpFiles()
+ * PIXA *convertToNUpPixa()
+ * PIXA *pixaConvertToNUpPixa()
+ *
+ * Render two pixa side-by-side for comparison *
+ * l_int32 pixaCompareInPdf()
+ *
+ * We give twelve pixaDisplay*() methods for tiling 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; others tile onto an irregular lattice;
+ * one uses an associated index array to determine which column
+ * each pix goes into.
+ *
+ * Here is a brief description of what the pixa display functions do.
+ *
+ * pixaDisplay()
+ * This uses the boxes in the pixa to lay out each pix. This
+ * can be used to reconstruct a pix that has been broken into
+ * components, if the boxes represents the positions of the
+ * components in the original image.
+ * 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.
+ * pixaDisplayTiledInColumns()
+ * This puts each pix down in a series of rows, each row having
+ * a specified number of pix. 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. 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.
+ *
+ * To render mosaics of images in a pixaa, display functions are
+ * provided that handle situations where the images are all scaled to
+ * the same size, or the number of images on each row needs to vary.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h> /* for sqrt() */
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*
+ * Pixa Display *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaDisplay()
+ *
+ * \param[in] pixa
+ * \param[in] w, h if set to 0, the size is determined from the
+ * bounding box of the components in pixa
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixaDisplay(PIXA *pixa,
+ l_int32 w,
+ l_int32 h)
+{
+l_int32 i, n, d, xb, yb, wb, hb, res;
+BOXA *boxa;
+PIX *pix1, *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);
+ if (w == 0 || h == 0)
+ return (PIX *)ERROR_PTR("no associated boxa", procName, NULL);
+ }
+
+ /* Use the first pix in pixa to determine depth and resolution */
+ pix1 = pixaGetPix(pixa, 0, L_CLONE);
+ d = pixGetDepth(pix1);
+ res = pixGetXRes(pix1);
+ pixDestroy(&pix1);
+
+ if ((pixd = pixCreate(w, h, d)) == NULL)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ pixSetResolution(pixd, res, res);
+ 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;
+ }
+ pix1 = pixaGetPix(pixa, i, L_CLONE);
+ if (d == 1)
+ pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pix1, 0, 0);
+ else
+ pixRasterop(pixd, xb, yb, wb, hb, PIX_SRC, pix1, 0, 0);
+ pixDestroy(&pix1);
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixaDisplayRandomCmap()
+ *
+ * \param[in] pixa 1 bpp regions, with boxa delineating those regions
+ * \param[in] w, h if set to 0, the size is determined from the
+ * bounding box of the components in pixa
+ * \return pix 8 bpp, cmapped, with random colors assigned to each region,
+ * or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This uses the boxes to place each pix in the rendered composite.
+ * The fg of each pix in %pixa, such as a single connected
+ * component or a line of text, is given a random color.
+ * (2) By default, the background color is black (cmap index 0).
+ * This can be changed by pixcmapResetColor()
+ * </pre>
+ */
+PIX *
+pixaDisplayRandomCmap(PIXA *pixa,
+ l_int32 w,
+ l_int32 h)
+{
+l_int32 i, n, same, maxd, index, xb, yb, wb, hb, res;
+BOXA *boxa;
+PIX *pixs, *pix1, *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, &same, &maxd);
+ if (maxd > 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);
+ if (i == 0) res = pixGetXRes(pixs);
+ pix1 = pixConvert1To8(NULL, pixs, 0, index);
+ pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pix1, 0, 0);
+ pixDestroy(&pixs);
+ pixDestroy(&pix1);
+ }
+
+ pixSetResolution(pixd, res, res);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixaDisplayLinearly()
+ *
+ * \param[in] pixas
+ * \param[in] direction L_HORIZ or L_VERT
+ * \param[in] scalefactor applied to every pix; use 1.0 for no scaling
+ * \param[in] background 0 for white, 1 for black; this is the color
+ * of the spacing between the images
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of black border added to each image;
+ * use 0 for no border
+ * \param[out] pboxa [optional] location of images in output pix
+ * \return pix of composite images, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaDisplayOnLattice()
+ *
+ * \param[in] pixa
+ * \param[in] cellw lattice cell width
+ * \param[in] cellh lattice cell height
+ * \param[out] pncols [optional] number of columns in output lattice
+ * \param[out] pboxa [optional] location of images in lattice
+ * \return pix of composite images, or NULL on error
+ *
+ * <pre>
+ * 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) Save the number of tiled images in the text field of the pix,
+ * in the format: n = %d. This survives write/read into png files,
+ * for example.
+ * (5) 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.
+ * </pre>
+ */
+PIX *
+pixaDisplayOnLattice(PIXA *pixa,
+ l_int32 cellw,
+ l_int32 cellh,
+ l_int32 *pncols,
+ BOXA **pboxa)
+{
+char buf[16];
+l_int32 n, nw, nh, w, h, d, wt, ht, res, samedepth;
+l_int32 index, i, j, hascmap;
+BOX *box;
+BOXA *boxa;
+PIX *pix1, *pix2, *pixd;
+PIXA *pixa1;
+
+ 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, or if the depths differ, generate rgb */
+ if ((n = pixaGetCount(pixa)) == 0)
+ return (PIX *)ERROR_PTR("no components", procName, NULL);
+ pixaAnyColormaps(pixa, &hascmap);
+ pixaVerifyDepth(pixa, &samedepth, NULL);
+ if (hascmap || !samedepth) {
+ pixa1 = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa, i, L_CLONE);
+ pix2 = pixConvertTo32(pix1);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ } else {
+ pixa1 = pixaCopy(pixa, L_CLONE);
+ }
+
+ /* 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 to determine output depth and resolution */
+ pix1 = pixaGetPix(pixa1, 0, L_CLONE);
+ d = pixGetDepth(pix1);
+ res = pixGetXRes(pix1);
+ pixDestroy(&pix1);
+ if ((pixd = pixCreate(w, h, d)) == NULL) {
+ pixaDestroy(&pixa1);
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ }
+ pixSetBlackOrWhite(pixd, L_SET_WHITE);
+ pixSetResolution(pixd, res, res);
+ boxa = boxaCreate(n);
+
+ /* Tile the output */
+ index = 0;
+ for (i = 0; i < nh; i++) {
+ for (j = 0; j < nw && index < n; j++, index++) {
+ pix1 = pixaGetPix(pixa1, index, L_CLONE);
+ pixGetDimensions(pix1, &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(&pix1);
+ continue;
+ }
+ pixRasterop(pixd, j * cellw, i * cellh, wt, ht,
+ PIX_SRC, pix1, 0, 0);
+ box = boxCreate(j * cellw, i * cellh, wt, ht);
+ boxaAddBox(boxa, box, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ }
+
+ /* Save the number of tiles in the text field */
+ snprintf(buf, sizeof(buf), "n = %d", boxaGetCount(boxa));
+ pixSetText(pixd, buf);
+
+ if (pncols) *pncols = nw;
+ if (pboxa)
+ *pboxa = boxa;
+ else
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa1);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixaDisplayUnsplit()
+ *
+ * \param[in] pixa
+ * \param[in] nx number of mosaic cells horizontally
+ * \param[in] ny number of mosaic cells vertically
+ * \param[in] borderwidth of added border on all sides
+ * \param[in] bordercolor in our RGBA format: 0xrrggbbaa
+ * \return pix of tiled images, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *pix1, *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);
+ pix1 = pixaGetPix(pixa, 0, L_CLONE);
+ pixCopyColormap(pixd, pix1);
+ pixDestroy(&pix1);
+ 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++) {
+ pix1 = pixaGetPix(pixa, k, L_CLONE);
+ pixRasterop(pixd, x, y, wt, ht, PIX_SRC, pix1, 0, 0);
+ pixDestroy(&pix1);
+ x += wt + 2 * borderwidth;
+ }
+ y += ht + 2 * borderwidth;
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixaDisplayTiled()
+ *
+ * \param[in] pixa
+ * \param[in] maxwidth of output image
+ * \param[in] background 0 for white, 1 for black
+ * \param[in] spacing
+ * \return pix of tiled images, or NULL on error
+ *
+ * <pre>
+ * 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!
+ * </pre>
+ */
+PIX *
+pixaDisplayTiled(PIXA *pixa,
+ l_int32 maxwidth,
+ l_int32 background,
+ l_int32 spacing)
+{
+l_int32 wmax, hmax, wd, hd, d, hascmap, res, same;
+l_int32 i, j, n, ni, ncols, nrows;
+l_int32 ystart, xstart, wt, ht;
+PIX *pix1, *pix2, *pixd;
+PIXA *pixa1;
+
+ 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) {
+ pixa1 = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa, i, L_CLONE);
+ pix2 = pixConvertTo32(pix1);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ } else {
+ pixa1 = pixaCopy(pixa, L_CLONE);
+ }
+
+ /* Find the max dimensions and depth subimages */
+ pixaGetDepthInfo(pixa1, &d, &same);
+ if (!same) {
+ pixaDestroy(&pixa1);
+ return (PIX *)ERROR_PTR("depths not equal", procName, NULL);
+ }
+ pixaSizeRange(pixa1, NULL, NULL, &wmax, &hmax);
+
+ /* 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));
+ ncols = L_MAX(ncols, 1);
+ 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(&pixa1);
+ 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);
+ pix1 = pixaGetPix(pixa1, ni, L_CLONE);
+ if (ni == 0) res = pixGetXRes(pix1);
+ pixGetDimensions(pix1, &wt, &ht, NULL);
+ pixRasterop(pixd, xstart, ystart, wt, ht, PIX_SRC, pix1, 0, 0);
+ pixDestroy(&pix1);
+ }
+ }
+ pixSetResolution(pixd, res, res);
+
+ pixaDestroy(&pixa1);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixaDisplayTiledInRows()
+ *
+ * \param[in] pixa
+ * \param[in] outdepth output depth: 1, 8 or 32 bpp
+ * \param[in] maxwidth of output image
+ * \param[in] scalefactor applied to every pix; use 1.0 for no scaling
+ * \param[in] background 0 for white, 1 for black; this is the color
+ * of the spacing between the images
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of black border added to each image;
+ * use 0 for no border
+ * \return pixd of tiled images, or NULL on error
+ *
+ * <pre>
+ * 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, 0, 0, NULL);
+ * </pre>
+ */
+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, res;
+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, *pix1, *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)
+ pix1 = pixScale(pixn, scalefactor, scalefactor);
+ else
+ pix1 = pixClone(pixn);
+ if (border)
+ pixd = pixAddBorder(pix1, border, bordval);
+ else
+ pixd = pixClone(pix1);
+ pixDestroy(&pixn);
+ pixDestroy(&pix1);
+
+ 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);
+ if (index == 0) {
+ res = pixGetXRes(pix);
+ pixSetResolution(pixd, res, res);
+ }
+ 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;
+ }
+ if (boxaWriteMem(&data, &size, boxa) == 0)
+ pixSetText(pixd, (char *)data); /* data is ascii */
+ LEPT_FREE(data);
+ boxaDestroy(&boxa);
+
+ numaDestroy(&nainrow);
+ numaDestroy(&namaxh);
+ pixaDestroy(&pixan);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixaDisplayTiledInColumns()
+ *
+ * \param[in] pixas
+ * \param[in] nx number of columns in output image
+ * \param[in] scalefactor applied to every pix; use 1.0 for no scaling
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of black border added to each image;
+ * use 0 for no border
+ * \return pixd of tiled images, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This renders a pixa to a single image with &nx columns of
+ * subimages. The background color is white, and each row
+ * is tiled such that the top of each pix is aligned and
+ * each pix is separated by 'spacing' from the next one.
+ * A black border can be added to each pix.
+ * (2) The output depth is determined by the largest depth
+ * required by the pix in the pixa. Colormaps are removed.
+ * (3) 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 = pixaDisplayTiledInColumns(pixa1, 3, 1.0, 0, 30, 2);
+ * char *boxatxt = pixGetText(pix1);
+ * boxa1 = boxaReadMem((l_uint8 *)boxatxt, strlen(boxatxt));
+ * pixa2 = pixaCreateFromBoxa(pix1, boxa1, NULL);
+ * </pre>
+ */
+PIX *
+pixaDisplayTiledInColumns(PIXA *pixas,
+ l_int32 nx,
+ l_float32 scalefactor,
+ l_int32 spacing,
+ l_int32 border)
+{
+l_int32 i, j, index, n, x, y, nrows, wb, hb, w, h, maxd, maxh, bordval, res;
+size_t size;
+l_uint8 *data;
+BOX *box;
+BOXA *boxa;
+PIX *pix1, *pix2, *pix3, *pixd;
+PIXA *pixa1, *pixa2;
+
+ PROCNAME("pixaDisplayTiledInColumns");
+
+ if (!pixas)
+ return (PIX *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (border < 0)
+ border = 0;
+ if (scalefactor <= 0.0) scalefactor = 1.0;
+
+ if ((n = pixaGetCount(pixas)) == 0)
+ return (PIX *)ERROR_PTR("no components", procName, NULL);
+
+ /* Convert to same depth, if necessary */
+ pixa1 = pixaConvertToSameDepth(pixas);
+ pixaGetDepthInfo(pixa1, &maxd, NULL);
+
+ /* Scale and optionally add border */
+ pixa2 = pixaCreate(n);
+ bordval = (maxd == 1) ? 1 : 0;
+ for (i = 0; i < n; i++) {
+ if ((pix1 = pixaGetPix(pixa1, i, L_CLONE)) == NULL)
+ 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);
+ if (i == 0) res = pixGetXRes(pix3);
+ pixaAddPix(pixa2, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ pixaDestroy(&pixa1);
+ if (pixaGetCount(pixa2) != n) {
+ n = pixaGetCount(pixa2);
+ L_WARNING("only got %d components\n", procName, n);
+ if (n == 0) {
+ pixaDestroy(&pixa2);
+ return (PIX *)ERROR_PTR("no components", procName, NULL);
+ }
+ }
+
+ /* Compute layout parameters and save as a boxa */
+ boxa = boxaCreate(n);
+ nrows = (n + nx - 1) / nx;
+ y = spacing;
+ for (i = 0, index = 0; i < nrows; i++) {
+ x = spacing;
+ maxh = 0;
+ for (j = 0; j < nx && index < n; j++) {
+ pixaGetPixDimensions(pixa2, index, &wb, &hb, NULL);
+ box = boxCreate(x, y, wb, hb);
+ boxaAddBox(boxa, box, L_INSERT);
+ maxh = L_MAX(maxh, hb + spacing);
+ x += wb + spacing;
+ index++;
+ }
+ y += maxh;
+ }
+ pixaSetBoxa(pixa2, boxa, L_INSERT);
+
+ /* Render the output pix */
+ boxaGetExtent(boxa, &w, &h, NULL);
+ pixd = pixaDisplay(pixa2, w + spacing, h + spacing);
+ pixSetResolution(pixd, res, res);
+
+ /* Save the boxa in the text field of the output pix */
+ if (boxaWriteMem(&data, &size, boxa) == 0)
+ pixSetText(pixd, (char *)data); /* data is ascii */
+ LEPT_FREE(data);
+
+ pixaDestroy(&pixa2);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixaDisplayTiledAndScaled()
+ *
+ * \param[in] pixa
+ * \param[in] outdepth output depth: 1, 8 or 32 bpp
+ * \param[in] tilewidth each pix is scaled to this width
+ * \param[in] ncols number of tiles in each row
+ * \param[in] background 0 for white, 1 for black; this is the color
+ * of the spacing between the images
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of additional black border on each image;
+ * use 0 for no border
+ * \return pix of tiled images, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, res;
+l_int32 i, n, nrows, maxht, ninrow, irow, bordval;
+l_int32 *rowht;
+l_float32 scalefact;
+PIX *pix, *pixn, *pix1, *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 (ncols <= 0)
+ return (PIX *)ERROR_PTR("ncols must be > 0", 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)
+ pix1 = pixScaleToGray(pix, scalefact);
+ else
+ pix1 = pixScale(pix, scalefact, scalefact);
+
+ if (outdepth == 1)
+ pixn = pixConvertTo1(pix1, 128);
+ else if (outdepth == 8)
+ pixn = pixConvertTo8(pix1, FALSE);
+ else /* outdepth == 32 */
+ pixn = pixConvertTo32(pix1);
+ pixDestroy(&pix1);
+
+ 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) {
+ pixaDestroy(&pixan);
+ 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);
+ if (i == 0) {
+ res = pixGetXRes(pix);
+ pixSetResolution(pixd, res, res);
+ }
+ 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;
+}
+
+
+/*!
+ * \brief pixaDisplayTiledWithText()
+ *
+ * \param[in] pixa
+ * \param[in] maxwidth of output image
+ * \param[in] scalefactor applied to every pix; use 1.0 for no scaling
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of black border added to each image;
+ * use 0 for no border
+ * \param[in] fontsize 4, 6, ... 20
+ * \param[in] textcolor 0xrrggbb00
+ * \return pixd of tiled images, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaDisplayTiledByIndex()
+ *
+ * \param[in] pixa
+ * \param[in] na numa with indices corresponding to the pix in pixa
+ * \param[in] width each pix is scaled to this width
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of black border added to each image;
+ * use 0 for no border
+ * \param[in] fontsize 4, 6, ... 20
+ * \param[in] textcolor 0xrrggbb00
+ * \return pixd of tiled images, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaaDisplay()
+ *
+ * \param[in] paa
+ * \param[in] w, h if set to 0, the size is determined from the
+ * bounding box of the components in pixa
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *pix1, *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);
+ pix1 = pixaGetPix(pixa, 0, L_CLONE);
+ d = pixGetDepth(pix1);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix1);
+
+ if ((pixd = pixCreate(w, h, d)) == NULL) {
+ boxaDestroy(&boxa1);
+ 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);
+ pix1 = pixaGetPix(pixa, j, L_CLONE);
+ pixRasterop(pixd, x + xb, y + yb, wb, hb, PIX_PAINT, pix1, 0, 0);
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa);
+ }
+ boxaDestroy(&boxa1);
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixaaDisplayByPixa()
+ *
+ * \param[in] paa
+ * \param[in] maxnx maximum number of columns for rendering each pixa
+ * \param[in] scalefactor applied to every pix; use 1.0 for no scaling
+ * \param[in] hspacing between images on a row (in the pixa)
+ * \param[in] vspacing between tiles rows, each corresponding to a pixa
+ * \param[in] border width of black border added to each image;
+ * use 0 for no border
+ * \return pixd of images in %paa, tiled by pixa in row-major order
+ *
+ * <pre>
+ * Notes:
+ * (1) This renders a pixaa into a single image. The pix from each pixa
+ * are rendered on a row. If the number of pix in the pixa is
+ * larger than %maxnx, the pix will be rendered into more than 1 row.
+ * To insure that each pixa is rendered into one row, use %maxnx
+ * at least as large as the max number of pix in the pixa.
+ * (2) Each row is tiled such that the top of each pix is aligned and
+ * each pix is separated by %hspacing from the next one.
+ * A black border can be added to each pix.
+ * (3) The resulting pix from each row are then rendered vertically,
+ * separated by %vspacing from each other.
+ * (4) The output depth is determined by the largest depth of all
+ * the pix in %paa. Colormaps are removed.
+ * </pre>
+ */
+PIX *
+pixaaDisplayByPixa(PIXAA *paa,
+ l_int32 maxnx,
+ l_float32 scalefactor,
+ l_int32 hspacing,
+ l_int32 vspacing,
+ l_int32 border)
+{
+l_int32 i, n, vs;
+PIX *pix1, *pix2;
+PIXA *pixa1, *pixa2;
+
+ PROCNAME("pixaaDisplayByPixa");
+
+ if (!paa)
+ return (PIX *)ERROR_PTR("paa not defined", procName, NULL);
+ if (scalefactor <= 0.0) scalefactor = 1.0;
+ if (hspacing < 0) hspacing = 0;
+ if (vspacing < 0) vspacing = 0;
+ if (border < 0) border = 0;
+
+ if ((n = pixaaGetCount(paa, NULL)) == 0)
+ return (PIX *)ERROR_PTR("no components", procName, NULL);
+
+ /* Vertical spacing of amount %hspacing is also added at this step */
+ pixa2 = pixaCreate(0);
+ for (i = 0; i < n; i++) {
+ pixa1 = pixaaGetPixa(paa, i, L_CLONE);
+ pix1 = pixaDisplayTiledInColumns(pixa1, maxnx, scalefactor,
+ hspacing, border);
+ pixaAddPix(pixa2, pix1, L_INSERT);
+ pixaDestroy(&pixa1);
+ }
+
+ vs = vspacing - 2 * hspacing;
+ pix2 = pixaDisplayTiledInColumns(pixa2, 1, scalefactor, vs, 0);
+ pixaDestroy(&pixa2);
+ return pix2;
+}
+
+
+/*!
+ * \brief pixaaDisplayTiledAndScaled()
+ *
+ * \param[in] paa
+ * \param[in] outdepth output depth: 1, 8 or 32 bpp
+ * \param[in] tilewidth each pix is scaled to this width
+ * \param[in] ncols number of tiles in each row
+ * \param[in] background 0 for white, 1 for black; this is the color
+ * of the spacing between the images
+ * \param[in] spacing between images, and on outside
+ * \param[in] 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
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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 (ncols <= 0)
+ return (PIXA *)ERROR_PTR("ncols must be > 0", 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) *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaConvertTo1()
+ *
+ * \param[in] pixas
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixaConvertTo8()
+ *
+ * \param[in] pixas
+ * \param[in] cmapflag 1 to give pixd a colormap; 0 otherwise
+ * \return pixad each pix is 8 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes for pixConvertTo8(), applied to each pix in pixas.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaConvertTo8Colormap()
+ *
+ * \param[in] pixas
+ * \param[in] dither 1 to dither if necessary; 0 otherwise
+ * \return pixad each pix is 8 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes for pixConvertTo8Colormap(), applied to each pix in pixas.
+ * </pre>
+ */
+PIXA *
+pixaConvertTo8Colormap(PIXA *pixas,
+ l_int32 dither)
+{
+l_int32 i, n;
+BOXA *boxa;
+PIX *pix1, *pix2;
+PIXA *pixad;
+
+ PROCNAME("pixaConvertTo8Colormap");
+
+ 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 = pixConvertTo8Colormap(pix1, dither);
+ pixaAddPix(pixad, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ boxa = pixaGetBoxa(pixas, L_COPY);
+ pixaSetBoxa(pixad, boxa, L_INSERT);
+ return pixad;
+}
+
+
+/*!
+ * \brief pixaConvertTo32()
+ *
+ * \param[in] pixas
+ * \return pixad 32 bpp rgb, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes for pixConvertTo32(), applied to each pix in pixas.
+ * (2) This can be used to allow 1 bpp pix in a pixa to be displayed
+ * with color.
+ * </pre>
+ */
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pixa constrained selection *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaConstrainedSelect()
+ *
+ * \param[in] pixas
+ * \param[in] first first index to choose; >= 0
+ * \param[in] last biggest possible index to reach;
+ * use -1 to go to the end; otherwise, last >= first
+ * \param[in] nmax maximum number of pix to select; > 0
+ * \param[in] use_pairs 1 = select pairs of adjacent pix;
+ * 0 = select individual pix
+ * \param[in] copyflag L_COPY, L_CLONE
+ * \return pixad if OK, NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes in genConstrainedNumaInRange() for how selection
+ * is made.
+ * (2) This returns a selection of the pix in the input pixa.
+ * (3) Use copyflag == L_COPY if you don't want changes in the pix
+ * in the returned pixa to affect those in the input pixa.
+ * </pre>
+ */
+PIXA *
+pixaConstrainedSelect(PIXA *pixas,
+ l_int32 first,
+ l_int32 last,
+ l_int32 nmax,
+ l_int32 use_pairs,
+ l_int32 copyflag)
+{
+l_int32 i, n, nselect, index;
+NUMA *na;
+PIX *pix1;
+PIXA *pixad;
+
+ PROCNAME("pixaConstrainedSelect");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ n = pixaGetCount(pixas);
+ first = L_MAX(0, first);
+ last = (last < 0) ? n - 1 : L_MIN(n - 1, last);
+ if (last < first)
+ return (PIXA *)ERROR_PTR("last < first!", procName, NULL);
+ if (nmax < 1)
+ return (PIXA *)ERROR_PTR("nmax < 1!", procName, NULL);
+
+ na = genConstrainedNumaInRange(first, last, nmax, use_pairs);
+ nselect = numaGetCount(na);
+ pixad = pixaCreate(nselect);
+ for (i = 0; i < nselect; i++) {
+ numaGetIValue(na, i, &index);
+ pix1 = pixaGetPix(pixas, index, copyflag);
+ pixaAddPix(pixad, pix1, L_INSERT);
+ }
+ numaDestroy(&na);
+ return pixad;
+}
+
+
+/*!
+ * \brief pixaSelectToPdf()
+ *
+ * \param[in] pixas
+ * \param[in] first first index to choose; >= 0
+ * \param[in] last biggest possible index to reach;
+ * use -1 to go to the end; otherwise, last >= first
+ * \param[in] res override the resolution of each input image, in ppi;
+ * use 0 to respect the resolution embedded in the input
+ * \param[in] scalefactor scaling factor applied to each image; > 0.0
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, or 0 for default
+ * \param[in] quality used for JPEG only; 0 for default (75)
+ * \param[in] color of numbers added to each image (e.g., 0xff000000)
+ * \param[in] fontsize to print number below each image. The valid set
+ * is {4,6,8,10,12,14,16,18,20}. Use 0 to disable.
+ * \param[in] fileout pdf file of all images
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This writes a pdf of the selected images from %pixas, one to
+ * a page. They are optionally scaled and annotated with the
+ * index printed to the left of the image.
+ * (2) If the input images are 1 bpp and you want the numbers to be
+ * in color, first promote each pix to 8 bpp with a colormap:
+ * pixa1 = pixaConvertTo8(pixas, 1);
+ * and then call this function with the specified color
+ * </pre>
+ */
+l_ok
+pixaSelectToPdf(PIXA *pixas,
+ l_int32 first,
+ l_int32 last,
+ l_int32 res,
+ l_float32 scalefactor,
+ l_int32 type,
+ l_int32 quality,
+ l_uint32 color,
+ l_int32 fontsize,
+ const char *fileout)
+{
+l_int32 n;
+L_BMF *bmf;
+NUMA *na;
+PIXA *pixa1, *pixa2;
+
+ PROCNAME("pixaSelectToPdf");
+
+ if (!pixas)
+ return ERROR_INT("pixas not defined", procName, 1);
+ if (type < 0 || type > L_FLATE_ENCODE) {
+ L_WARNING("invalid compression type; using default\n", procName);
+ type = 0;
+ }
+ if (!fileout)
+ return ERROR_INT("fileout not defined", procName, 1);
+
+ /* Select from given range */
+ n = pixaGetCount(pixas);
+ first = L_MAX(0, first);
+ last = (last < 0) ? n - 1 : L_MIN(n - 1, last);
+ if (first > last) {
+ L_ERROR("first = %d > last = %d\n", procName, first, last);
+ return 1;
+ }
+ pixa1 = pixaSelectRange(pixas, first, last, L_CLONE);
+
+ /* Optionally add index numbers */
+ bmf = (fontsize <= 0) ? NULL : bmfCreate(NULL, fontsize);
+ if (bmf) {
+ na = numaMakeSequence(first, 1.0, last - first + 1);
+ pixa2 = pixaAddTextNumber(pixa1, bmf, na, color, L_ADD_LEFT);
+ numaDestroy(&na);
+ } else {
+ pixa2 = pixaCopy(pixa1, L_CLONE);
+ }
+ pixaDestroy(&pixa1);
+ bmfDestroy(&bmf);
+
+ pixaConvertToPdf(pixa2, res, scalefactor, type, quality, NULL, fileout);
+ pixaDestroy(&pixa2);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Generate pixa from tiled images *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaMakeFromTiledPixa()
+ *
+ * \param[in] pixas of mosaiced templates, one for each digit
+ * \param[in] w width of samples (use 0 for default = 20)
+ * \param[in] h height of samples (use 0 for default = 30)
+ * \param[in] nsamp number of requested samples (use 0 for default = 100)
+ * \return pixa of individual, scaled templates, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This converts from a compressed representation of 1 bpp digit
+ * templates to a pixa where each pix has a single labeled template.
+ * (2) The mosaics hold 100 templates each, and the number of templates
+ * %nsamp selected for each digit can be between 1 and 100.
+ * (3) Each mosaic has the number of images written in the text field,
+ * and the i-th pix contains samples of the i-th digit. That value
+ * is written into the text field of each template in the output.
+ * </pre>
+ */
+PIXA *
+pixaMakeFromTiledPixa(PIXA *pixas,
+ l_int32 w,
+ l_int32 h,
+ l_int32 nsamp)
+{
+char buf[8];
+l_int32 ntiles, i;
+PIX *pix1;
+PIXA *pixad, *pixa1;
+
+ PROCNAME("pixaMakeFromTiledPixa");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (nsamp > 1000)
+ return (PIXA *)ERROR_PTR("nsamp too large; typ. 100", procName, NULL);
+
+ if (w <= 0) w = 20;
+ if (h <= 0) h = 30;
+ if (nsamp <= 0) nsamp = 100;
+
+ /* pixas has 10 pix of mosaic'd digits. Each of these images
+ * must be extracted into a pixa of templates, where each template
+ * is labeled with the digit value, and then selectively
+ * concatenated into an output pixa. */
+ pixad = pixaCreate(10 * nsamp);
+ for (i = 0; i < 10; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ pixGetTileCount(pix1, &ntiles);
+ if (nsamp > ntiles)
+ L_WARNING("requested %d; only %d tiles\n", procName, nsamp, ntiles);
+ pixa1 = pixaMakeFromTiledPix(pix1, w, h, 0, nsamp, NULL);
+ snprintf(buf, sizeof(buf), "%d", i);
+ pixaSetText(pixa1, buf, NULL);
+ pixaJoin(pixad, pixa1, 0, -1);
+ pixaDestroy(&pixa1);
+ pixDestroy(&pix1);
+ }
+ return pixad;
+}
+
+
+/*!
+ * \brief pixaMakeFromTiledPix()
+ *
+ * \param[in] pixs any depth; colormap OK
+ * \param[in] w width of each tile
+ * \param[in] h height of each tile
+ * \param[in] start first tile to use
+ * \param[in] num number of tiles; use 0 to go to the end
+ * \param[in] boxa [optional] location of rectangular regions
+ * to be extracted
+ * \return pixa if OK, NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Operations that generate a pix by tiling from a pixa, and
+ * the inverse that generate a pixa from tiles of a pix,
+ * are useful. One such pair is pixaDisplayUnsplit() and
+ * pixaSplitPix(). This function is a very simple one that
+ * generates a pixa from tiles of a pix. There are two cases:
+ * - the tiles can all be the same size (the inverse of
+ * pixaDisplayOnLattice(), or
+ * - the tiles can differ in size, where there is an
+ * associated boxa (the inverse of pixaCreateFromBoxa().
+ * (2) If all tiles are the same size, %w by %h, use %boxa = NULL.
+ * If the tiles differ in size, use %boxa to extract the
+ * individual images (%w and %h are then ignored).
+ * (3) If the pix was made by pixaDisplayOnLattice(), the number
+ * of tiled images is written into the text field, in the format
+ * n = <number>.
+ * (4) Typical usage: a set of character templates all scaled to
+ * the same size can be stored on a lattice of that size in
+ * a pix, and this function can regenerate the pixa. If the
+ * templates differ in size, a boxa generated when the tiled
+ * pix was made can be used to indicate the location of
+ * the templates.
+ * </pre>
+ */
+PIXA *
+pixaMakeFromTiledPix(PIX *pixs,
+ l_int32 w,
+ l_int32 h,
+ l_int32 start,
+ l_int32 num,
+ BOXA *boxa)
+{
+l_int32 i, j, k, ws, hs, d, nx, ny, n, n_isvalid, ntiles, nmax;
+PIX *pix1;
+PIXA *pixa1;
+PIXCMAP *cmap;
+
+ PROCNAME("pixaMakeFromTiledPix");
+
+ if (!pixs)
+ return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
+ if (!boxa && (w <= 0 || h <= 0))
+ return (PIXA *)ERROR_PTR("w and h must be > 0", procName, NULL);
+
+ if (boxa) /* general case */
+ return pixaCreateFromBoxa(pixs, boxa, start, num, NULL);
+
+ /* All tiles are the same size */
+ pixGetDimensions(pixs, &ws, &hs, &d);
+ nx = ws / w;
+ ny = hs / h;
+ if (nx < 1 || ny < 1)
+ return (PIXA *)ERROR_PTR("invalid dimensions", procName, NULL);
+ if (nx * w != ws || ny * h != hs)
+ L_WARNING("some tiles will be clipped\n", procName);
+
+ /* Check the text field of the pix. It may tell how many
+ * tiles hold valid data. If a valid value is not found,
+ * assume all (nx * ny) tiles are valid. */
+ pixGetTileCount(pixs, &n);
+ n_isvalid = (n <= nx * ny && n > nx * (ny - 1)) ? TRUE : FALSE;
+ ntiles = (n_isvalid) ? n : nx * ny;
+ nmax = ntiles - start; /* max available from start */
+ num = (num == 0) ? nmax : L_MIN(num, nmax);
+
+ /* Extract the tiles */
+ if ((pixa1 = pixaCreate(num)) == NULL) {
+ return (PIXA *)ERROR_PTR("pixa1 not made", procName, NULL);
+ }
+ cmap = pixGetColormap(pixs);
+ for (i = 0, k = 0; i < ny; i++) {
+ for (j = 0; j < nx; j++, k++) {
+ if (k < start) continue;
+ if (k >= start + num) break;
+ pix1 = pixCreate(w, h, d);
+ if (cmap) pixSetColormap(pix1, pixcmapCopy(cmap));
+ pixRasterop(pix1, 0, 0, w, h, PIX_SRC, pixs, j * w, i * h);
+ pixaAddPix(pixa1, pix1, L_INSERT);
+ }
+ }
+ return pixa1;
+}
+
+
+/*!
+ * \brief pixGetTileCount()
+ *
+ * \param[in] pix
+ * \param[out] *pn number embedded in pix text field
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If the pix was made by pixaDisplayOnLattice(), the number
+ * of tiled images is written into the text field, in the format
+ * n = <number>.
+ * (2) This returns 0 if the data is not in the text field, or on error.
+ * </pre>
+ */
+l_ok
+pixGetTileCount(PIX *pix,
+ l_int32 *pn)
+{
+char *text;
+l_int32 n;
+
+ PROCNAME("pixGetTileCount");
+
+ if (!pn)
+ return ERROR_INT("&n not defined", procName, 1);
+ *pn = 0;
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+
+ text = pixGetText(pix);
+ if (text && strlen(text) > 4) {
+ if (sscanf(text, "n = %d", &n) == 1)
+ *pn = n;
+ }
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pixa display into multiple tiles *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaDisplayMultiTiled()
+ *
+ * \param[in] pixas
+ * \param[in] nx, ny in [1, ... 50], tiling factors in each direction
+ * \param[in] maxw, maxh max sizes to keep
+ * \param[in] scalefactor scale each image by this
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of additional black border on each image;
+ * use 0 for no border
+ * \return pixad if OK, NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Each set of %nx * %ny images is optionally scaled and saved
+ * into a new pix, and then aggregated.
+ * (2) Set %maxw = %maxh = 0 if you want to include all pix from %pixs.
+ * (3) This is useful for generating a pdf from the output pixa, where
+ * each page is a tile of (%nx * %ny) images from the input pixa.
+ * </pre>
+ */
+PIXA *
+pixaDisplayMultiTiled(PIXA *pixas,
+ l_int32 nx,
+ l_int32 ny,
+ l_int32 maxw,
+ l_int32 maxh,
+ l_float32 scalefactor,
+ l_int32 spacing,
+ l_int32 border)
+{
+l_int32 n, i, j, ntile, nout, index;
+PIX *pix1, *pix2;
+PIXA *pixa1, *pixa2, *pixad;
+
+ PROCNAME("pixaDisplayMultiTiled");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (nx < 1 || ny < 1 || nx > 50 || ny > 50)
+ return (PIXA *)ERROR_PTR("invalid tiling factor(s)", procName, NULL);
+ if ((n = pixaGetCount(pixas)) == 0)
+ return (PIXA *)ERROR_PTR("pixas is empty", procName, NULL);
+
+ /* Filter out large ones if requested */
+ if (maxw == 0 && maxh == 0) {
+ pixa1 = pixaCopy(pixas, L_CLONE);
+ } else {
+ maxw = (maxw == 0) ? 1000000 : maxw;
+ maxh = (maxh == 0) ? 1000000 : maxh;
+ pixa1 = pixaSelectBySize(pixas, maxw, maxh, L_SELECT_IF_BOTH,
+ L_SELECT_IF_LTE, NULL);
+ n = pixaGetCount(pixa1);
+ }
+
+ ntile = nx * ny;
+ nout = L_MAX(1, (n + ntile - 1) / ntile);
+ pixad = pixaCreate(nout);
+ for (i = 0, index = 0; i < nout; i++) { /* over tiles */
+ pixa2 = pixaCreate(ntile);
+ for (j = 0; j < ntile && index < n; j++, index++) {
+ pix1 = pixaGetPix(pixa1, index, L_COPY);
+ pixaAddPix(pixa2, pix1, L_INSERT);
+ }
+ pix2 = pixaDisplayTiledInColumns(pixa2, nx, scalefactor, spacing,
+ border);
+ pixaAddPix(pixad, pix2, L_INSERT);
+ pixaDestroy(&pixa2);
+ }
+ pixaDestroy(&pixa1);
+
+ return pixad;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Split pixa into files *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaSplitIntoFiles()
+ *
+ * \param[in] pixas
+ * \param[in] nsplit split pixas into this number of pixa; >= 2
+ * \param[in] scale scalefactor applied to each pix
+ * \param[in] outwidth the maxwidth parameter of tiled images
+ * for write_pix
+ * \param[in] write_pixa 1 to write the split pixa as separate files
+ * \param[in] write_pix 1 to write tiled images of the split pixa
+ * \param[in] write_pdf 1 to write pdfs of the split pixa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For each requested output, %nsplit files are written into
+ * directory /tmp/lept/split/.
+ * (2) This is useful when a pixa is so large that the images
+ * are not conveniently displayed as a single tiled image at
+ * full resolution.
+ * </pre>
+ */
+l_ok
+pixaSplitIntoFiles(PIXA *pixas,
+ l_int32 nsplit,
+ l_float32 scale,
+ l_int32 outwidth,
+ l_int32 write_pixa,
+ l_int32 write_pix,
+ l_int32 write_pdf)
+{
+char buf[64];
+l_int32 i, j, index, n, nt;
+PIX *pix1, *pix2;
+PIXA *pixa1;
+
+ PROCNAME("pixaSplitIntoFiles");
+
+ if (!pixas)
+ return ERROR_INT("pixas not defined", procName, 1);
+ if (nsplit <= 1)
+ return ERROR_INT("nsplit must be >= 2", procName, 1);
+ if ((nt = pixaGetCount(pixas)) == 0)
+ return ERROR_INT("pixas is empty", procName, 1);
+ if (!write_pixa && !write_pix && !write_pdf)
+ return ERROR_INT("no output is requested", procName, 1);
+
+ lept_mkdir("lept/split");
+ n = (nt + nsplit - 1) / nsplit;
+ lept_stderr("nt = %d, n = %d, nsplit = %d\n", nt, n, nsplit);
+ for (i = 0, index = 0; i < nsplit; i++) {
+ pixa1 = pixaCreate(n);
+ for (j = 0; j < n && index < nt; j++, index++) {
+ pix1 = pixaGetPix(pixas, index, L_CLONE);
+ pix2 = pixScale(pix1, scale, scale);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ if (write_pixa) {
+ snprintf(buf, sizeof(buf), "/tmp/lept/split/split%d.pa", i + 1);
+ pixaWriteDebug(buf, pixa1);
+ }
+ if (write_pix) {
+ snprintf(buf, sizeof(buf), "/tmp/lept/split/split%d.tif", i + 1);
+ pix1 = pixaDisplayTiledInRows(pixa1, 1, outwidth, 1.0, 0, 20, 2);
+ pixWriteDebug(buf, pix1, IFF_TIFF_G4);
+ pixDestroy(&pix1);
+ }
+ if (write_pdf) {
+ snprintf(buf, sizeof(buf), "/tmp/lept/split/split%d.pdf", i + 1);
+ pixaConvertToPdf(pixa1, 0, 1.0, L_G4_ENCODE, 0, buf, buf);
+ }
+ pixaDestroy(&pixa1);
+ }
+
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Tile N-Up *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief convertToNUpFiles()
+ *
+ * \param[in] dir full path to directory of images
+ * \param[in] substr [optional] can be null
+ * \param[in] nx, ny in [1, ... 50], tiling factors in each direction
+ * \param[in] tw target width, in pixels; must be >= 20
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of additional black border on each image;
+ * use 0 for no border
+ * \param[in] fontsize to print tail of filename with image. Valid set is
+ * {4,6,8,10,12,14,16,18,20}. Use 0 to disable.
+ * \param[in] outdir subdirectory of /tmp to put N-up tiled images
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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, %tw. This is typically used when all images are
+ * roughly the same size.
+ * (3) This is useful for generating a pdf from the set of input
+ * files, where each page is a tile of (%nx * %ny) input images.
+ * Typical values for %nx and %ny are in the range [2 ... 5].
+ * (4) If %fontsize != 0, each image has the tail of its filename
+ * rendered below it.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief convertToNUpPixa()
+ *
+ * \param[in] dir full path to directory of images
+ * \param[in] substr [optional] can be null
+ * \param[in] nx, ny in [1, ... 50], tiling factors in each direction
+ * \param[in] tw target width, in pixels; must be >= 20
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of additional black border on each image;
+ * use 0 for no border
+ * \param[in] 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
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes for convertToNUpFiles()
+ * </pre>
+ */
+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, n;
+char *fname, *tail;
+PIXA *pixa1, *pixa2;
+SARRAY *sa1, *sa2;
+
+ 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);
+
+ sa1 = getSortedPathnamesInDirectory(dir, substr, 0, 0);
+ pixa1 = pixaReadFilesSA(sa1);
+ n = sarrayGetCount(sa1);
+ sa2 = sarrayCreate(n);
+ for (i = 0; i < n; i++) {
+ fname = sarrayGetString(sa1, i, L_NOCOPY);
+ splitPathAtDirectory(fname, NULL, &tail);
+ sarrayAddString(sa2, tail, L_INSERT);
+ }
+ sarrayDestroy(&sa1);
+ pixa2 = pixaConvertToNUpPixa(pixa1, sa2, nx, ny, tw, spacing,
+ border, fontsize);
+ pixaDestroy(&pixa1);
+ sarrayDestroy(&sa2);
+ return pixa2;
+}
+
+
+/*!
+ * \brief pixaConvertToNUpPixa()
+ *
+ * \param[in] pixas
+ * \param[in] sa [optional] array of strings associated with each pix
+ * \param[in] nx, ny in [1, ... 50], tiling factors in each direction
+ * \param[in] tw target width, in pixels; must be >= 20
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of additional black border on each image;
+ * use 0 for no border
+ * \param[in] fontsize to print string with each image. Valid set is
+ * {4,6,8,10,12,14,16,18,20}. Use 0 to disable.
+ * \return pixad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This takes an input pixa and an optional array of strings, and
+ * generates a pixa of NUp tiles from the input, labeled with
+ * the strings if they exist and %fontsize != 0.
+ * (2) See notes for convertToNUpFiles()
+ * </pre>
+ */
+PIXA *
+pixaConvertToNUpPixa(PIXA *pixas,
+ SARRAY *sa,
+ 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 *str;
+L_BMF *bmf;
+PIX *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa1, *pixad;
+
+ PROCNAME("pixaConvertToNUpPixa");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas 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);
+
+ nt = pixaGetCount(pixas);
+ if (sa && (sarrayGetCount(sa) != nt)) {
+ L_WARNING("pixa size %d not equal to sarray size %d\n", procName,
+ 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++) {
+ pixa1 = pixaCreate(n2);
+ for (k = 0; k < n2 && j < nt; j++, k++) {
+ pix1 = pixaGetPix(pixas, j, L_CLONE);
+ pix2 = pixScaleToSize(pix1, tw, 0); /* all images have width tw */
+ if (bmf && sa) {
+ str = sarrayGetString(sa, j, L_NOCOPY);
+ pix3 = pixAddTextlines(pix2, bmf, str, 0xff000000,
+ L_ADD_BELOW);
+ } else {
+ pix3 = pixClone(pix2);
+ }
+ pixaAddPix(pixa1, pix3, L_INSERT);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ if (pixaGetCount(pixa1) == 0) { /* probably won't happen */
+ pixaDestroy(&pixa1);
+ continue;
+ }
+
+ /* Add 2 * border to image width to prevent scaling */
+ pixaGetRenderingDepth(pixa1, &d);
+ pix4 = pixaDisplayTiledAndScaled(pixa1, d, tw + 2 * border, nx, 0,
+ spacing, border);
+ pixaAddPix(pixad, pix4, L_INSERT);
+ pixaDestroy(&pixa1);
+ }
+
+ bmfDestroy(&bmf);
+ return pixad;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Render two pixa side-by-side for comparison *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaCompareInPdf()
+ *
+ * \param[in] pixa1
+ * \param[in] pixa2
+ * \param[in] nx, ny in [1, ... 20], tiling factors in each direction
+ * \param[in] tw target width, in pixels; must be >= 20
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of additional black border on each image
+ * and on each pair; use 0 for no border
+ * \param[in] fontsize to print index of each pair of images. Valid set
+ * is {4,6,8,10,12,14,16,18,20}. Use 0 to disable.
+ * \param[in] fileout output pdf file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This takes two pixa and renders them interleaved, side-by-side
+ * in a pdf. A warning is issued if the input pixa arrays
+ * have different lengths.
+ * (2) %nx and %ny specify how many side-by-side pairs are displayed
+ * on each pdf page. For example, if %nx = 1 and %ny = 2, then
+ * two pairs are shown, one above the other, on each page.
+ * (3) The input pix are scaled to a target width of %tw, and
+ * then paired with optional %spacing between and optional
+ * black border of width %border.
+ * (4) After a pixa is generated of these tiled images, it is
+ * written to %fileout as a pdf.
+ * (5) Typical numbers for the input parameters are:
+ * %nx = small integer (1 - 4)
+ * %ny = 2 * %nx
+ * %tw = 200 - 500 pixels
+ * %spacing = 10
+ * %border = 2
+ * %fontsize = 10
+ * (6) If %fontsize != 0, the index of the pix pair in their pixa
+ * is printed out below each pair.
+ * </pre>
+ */
+l_ok
+pixaCompareInPdf(PIXA *pixa1,
+ PIXA *pixa2,
+ l_int32 nx,
+ l_int32 ny,
+ l_int32 tw,
+ l_int32 spacing,
+ l_int32 border,
+ l_int32 fontsize,
+ const char *fileout)
+{
+l_int32 n1, n2, npairs;
+PIXA *pixa3, *pixa4, *pixa5;
+SARRAY *sa;
+
+ PROCNAME("pixaCompareInPdf");
+
+ if (!pixa1 || !pixa2)
+ return ERROR_INT("pixa1 and pixa2 not both defined", procName, 1);
+ if (nx < 1 || ny < 1 || nx > 20 || ny > 20)
+ return ERROR_INT("invalid tiling factors", procName, 1);
+ if (tw < 20)
+ return ERROR_INT("invalid tw; tw must be >= 20", procName, 1);
+ if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2)
+ return ERROR_INT("invalid fontsize", procName, 1);
+ if (!fileout)
+ return ERROR_INT("fileout not defined", procName, 1);
+ n1 = pixaGetCount(pixa1);
+ n2 = pixaGetCount(pixa2);
+ if (n1 == 0 || n2 == 0)
+ return ERROR_INT("at least one pixa is empty", procName, 1);
+ if (n1 != n2)
+ L_WARNING("sizes (%d, %d) differ; using the minimum in interleave\n",
+ procName, n1, n2);
+
+ /* Interleave the input pixa */
+ if ((pixa3 = pixaInterleave(pixa1, pixa2, L_CLONE)) == NULL)
+ return ERROR_INT("pixa3 not made", procName, 1);
+
+ /* Scale the images if necessary and pair them up side/by/side */
+ pixa4 = pixaConvertToNUpPixa(pixa3, NULL, 2, 1, tw, spacing, border, 0);
+ pixaDestroy(&pixa3);
+
+ /* Label the pairs and mosaic into pages without further scaling */
+ npairs = pixaGetCount(pixa4);
+ sa = (fontsize > 0) ? sarrayGenerateIntegers(npairs) : NULL;
+ pixa5 = pixaConvertToNUpPixa(pixa4, sa, nx, ny,
+ 2 * tw + 4 * border + spacing,
+ spacing, border, fontsize);
+ pixaDestroy(&pixa4);
+ sarrayDestroy(&sa);
+
+ /* Output as pdf without scaling */
+ pixaConvertToPdf(pixa5, 0, 1.0, 0, 0, NULL, fileout);
+ pixaDestroy(&pixa5);
+ return 0;
+}
+
+
diff --git a/leptonica/src/pixalloc.c b/leptonica/src/pixalloc.c
new file mode 100644
index 00000000..99545ac7
--- /dev/null
+++ b/leptonica/src/pixalloc.c
@@ -0,0 +1,532 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pixalloc.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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.
+ */
+
+/*! Pix memory storage */
+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;
+
+
+/*!
+ * \brief pmsCreate()
+ *
+ * \param[in] minsize of data chunk that can be supplied by pms
+ * \param[in] smallest bytes of the smallest pre-allocated data chunk.
+ * \param[in] numalloc array with the number of data chunks for each
+ * size that are in the memory store
+ * \param[in] logfile use for debugging; null otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+
+ pms = (L_PIX_MEM_STORE *)LEPT_CALLOC(1, sizeof(L_PIX_MEM_STORE));
+ 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;
+}
+
+
+/*!
+ * \brief pmsDestroy()
+ *
+ * <pre>
+ * Notes:
+ * (1) Important: call this function at the end of the program, after
+ * the last pix has been destroyed.
+ * </pre>
+ */
+void
+pmsDestroy(void)
+{
+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;
+}
+
+
+/*!
+ * \brief pmsCustomAlloc()
+ *
+ * \param[in] nbytes min number of bytes in the chunk to be retrieved
+ * \return data ptr to chunk
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pmsCustomDealloc()
+ *
+ * \param[in] 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]--;
+ }
+}
+
+
+/*!
+ * \brief pmsGetAlloc()
+ *
+ * \param[in] nbytes
+ * \return data
+ *
+ * <pre>
+ * 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) The C99 platform-independent format specifier for size_t is %zu.
+ * Windows since at least VC-2015 is conforming; we can now use %zu.
+ * </pre>
+ */
+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) {
+ if ((fp = fopenWriteStream(pms->logfile, "a")) != NULL) {
+ fprintf(fp, "Alloc %zu bytes at %p\n", nbytes, data);
+ fclose(fp);
+ } else {
+ L_ERROR("failed to open stream for %s\n", procName, pms->logfile);
+ }
+ }
+ return data;
+}
+
+
+/*!
+ * \brief pmsGetLevelForAlloc()
+ *
+ * \param[in] nbytes min number of bytes in the chunk to be retrieved
+ * \param[out] plevel -1 if either too small or too large
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pmsGetLevelForDealloc()
+ *
+ * \param[in] data ptr to memory chunk
+ * \param[out] plevel level in memory store; -1 if allocated
+ * outside the store
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pmsLogInfo()
+ */
+void
+pmsLogInfo(void)
+{
+l_int32 i;
+L_PIX_MEM_STORE *pms;
+
+ if ((pms = CustomPMS) == NULL)
+ return;
+
+ lept_stderr("Total number of pix used at each level\n");
+ for (i = 0; i < pms->nlevels; i++)
+ lept_stderr(" Level %d (%zu bytes): %d\n", i,
+ pms->sizes[i], pms->memused[i]);
+
+ lept_stderr("Max number of pix in use at any time in each level\n");
+ for (i = 0; i < pms->nlevels; i++)
+ lept_stderr(" Level %d (%zu bytes): %d\n", i,
+ pms->sizes[i], pms->memmax[i]);
+
+ lept_stderr("Number of pix alloc'd because none were available\n");
+ for (i = 0; i < pms->nlevels; i++)
+ lept_stderr(" Level %d (%zu bytes): %d\n", i,
+ pms->sizes[i], pms->memempty[i]);
+}
diff --git a/leptonica/src/pixarith.c b/leptonica/src/pixarith.c
new file mode 100644
index 00000000..abc15534
--- /dev/null
+++ b/leptonica/src/pixarith.c
@@ -0,0 +1,1633 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pixarith.c
+ * <pre>
+ *
+ * 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()
+ * PIX *pixMultiplyGray()
+ *
+ * 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
+ * PIX *pixMaxDynamicRange()
+ * PIX *pixMaxDynamicRangeRGB()
+ *
+ * RGB pixel value scaling
+ * l_uint32 linearScaleRGBVal()
+ * l_uint32 logScaleRGBVal()
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+/*-------------------------------------------------------------*
+ * One-image grayscale arithmetic operations *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixAddConstantGray()
+ *
+ * \param[in] pixs 8, 16 or 32 bpp
+ * \param[in] val amount to add to each pixel
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixMultConstantGray()
+ *
+ * \param[in] pixs 8, 16 or 32 bpp
+ * \param[in] val >= 0.0; amount to multiply by each pixel
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixAddGray()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs1, or
+ * different from pixs1
+ * \param[in] pixs1 can be equal to pixd
+ * \param[in] pixs2
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixSubtractGray()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs1, or
+ * different from pixs1
+ * \param[in] pixs1 can be equal to pixd
+ * \param[in] pixs2
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixMultiplyGray()
+ *
+ * \param[in] pixs 32 bpp rgb or 8 bpp gray
+ * \param[in] pixg 8 bpp gray
+ * \param[in] norm multiplicative factor to avoid overflow; 0 for default
+ * \return pixd, or null on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function can be used for correcting a scanned image
+ * under non-uniform illumination. For that application,
+ * %pixs is the scanned image, %pixg is an image whose values
+ * are inversely related to light from a uniform (say, white)
+ * target, and %norm is typically the inverse of the maximum
+ * pixel value in %pixg.
+ * (2) Set norm = 0 to get the default value, which is the inverse
+ * of the max value in %pixg. This avoids overflow in the product.
+ * (3) For 32 bpp %pixs, all 3 components are multiplied by the
+ * same number.
+ * (4) Alignment is to UL corner.
+ * </pre>
+ */
+PIX *
+pixMultiplyGray(PIX *pixs,
+ PIX *pixg,
+ l_float32 norm)
+{
+l_int32 i, j, w, h, d, ws, hs, ds, wpls, wplg, wpld;
+l_int32 rval, gval, bval, rval2, gval2, bval2, vals, valg, val, maxgray;
+l_uint32 val32;
+l_uint32 *datas, *datag, *datad, *lines, *lineg, *lined;
+PIX *pixd;
+
+ PROCNAME("pixMultiplyGray");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+ pixGetDimensions(pixs, &ws, &hs, &ds);
+ if (ds != 8 && ds != 32)
+ return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+ if (!pixg)
+ return (PIX *)ERROR_PTR("pixg not defined", procName, NULL);
+ pixGetDimensions(pixg, &w, &h, &d);
+ if (d != 8)
+ return (PIX *)ERROR_PTR("pixg not 8 bpp", procName, NULL);
+
+ if (norm <= 0.0) {
+ pixGetExtremeValue(pixg, 1, L_SELECT_MAX, NULL, NULL, NULL, &maxgray);
+ norm = (maxgray > 0) ? 1.0 / (l_float32)maxgray : 1.0;
+ }
+
+ if ((pixd = pixCreateTemplate(pixs)) == NULL)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ datas = pixGetData(pixs);
+ datag = pixGetData(pixg);
+ datad = pixGetData(pixd);
+ wpls = pixGetWpl(pixs);
+ wplg = pixGetWpl(pixg);
+ wpld = pixGetWpl(pixd);
+ w = L_MIN(ws, w);
+ h = L_MIN(hs, h);
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ lineg = datag + i * wplg;
+ lined = datad + i * wpld;
+ if (ds == 8) {
+ for (j = 0; j < w; j++) {
+ vals = GET_DATA_BYTE(lines, j);
+ valg = GET_DATA_BYTE(lineg, j);
+ val = (l_int32)(vals * valg * norm + 0.5);
+ val = L_MIN(255, val);
+ SET_DATA_BYTE(lined, j, val);
+ }
+ } else { /* ds == 32 */
+ for (j = 0; j < w; j++) {
+ val32 = *(lines + j);
+ extractRGBValues(val32, &rval, &gval, &bval);
+ valg = GET_DATA_BYTE(lineg, j);
+ rval2 = (l_int32)(rval * valg * norm + 0.5);
+ rval2 = L_MIN(255, rval2);
+ gval2 = (l_int32)(gval * valg * norm + 0.5);
+ gval2 = L_MIN(255, gval2);
+ bval2 = (l_int32)(bval * valg * norm + 0.5);
+ bval2 = L_MIN(255, bval2);
+ composeRGBPixel(rval2, gval2, bval2, lined + j);
+ }
+ }
+ }
+
+ return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ * Grayscale threshold operation *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixThresholdToValue()
+ *
+ * \param[in] pixd [optional]; if not null, must be equal to pixs
+ * \param[in] pixs 8, 16, 32 bpp
+ * \param[in] threshval
+ * \param[in] setval
+ * \return pixd always
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixInitAccumulate()
+ *
+ * \param[in] w, h of accumulate array
+ * \param[in] offset initialize the 32 bpp to have this
+ * value; not more than 0x40000000
+ * \return pixd 32 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) %offset must be >= 0.
+ * (2) %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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFinalAccumulate()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] offset same as used for initialization
+ * \param[in] depth 8, 16 or 32 bpp, of destination
+ * \return pixd 8, 16 or 32 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) %offset must be >= 0 and should not exceed 0x40000000.
+ * (2) %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]
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFinalAccumulateThreshold()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] offset same as used for initialization
+ * \param[in] threshold values less than this are set in the destination
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) %offset must be >= 0 and should not exceed 0x40000000.
+ * (2) %offset is subtracted from the src 32 bpp image
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixAccumulate()
+ *
+ * \param[in] pixd 32 bpp
+ * \param[in] pixs 1, 8, 16 or 32 bpp
+ * \param[in] op L_ARITH_ADD or L_ARITH_SUBTRACT
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixMultConstAccumulate()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] factor
+ * \param[in] offset same as used for initialization
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) %offset must be >= 0 and should not exceed 0x40000000.
+ * (2) This multiplies each pixel, relative to offset, by %factor.
+ * (3) The result is returned with %offset back in place.
+ * </pre>
+ */
+l_ok
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixAbsDifference()
+ *
+ * \param[in] pixs1, pixs2 both either 8 or 16 bpp gray, or 32 bpp RGB
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixAddRGB()
+ *
+ * \param[in] pixs1, pixs2 32 bpp RGB, or colormapped
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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) *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixMinOrMax()
+ *
+ * \param[in] pixd [optional] destination: this can be null,
+ * equal to pixs1, or different from pixs1
+ * \param[in] pixs1 can be equal to pixd
+ * \param[in] pixs2
+ * \param[in] type L_CHOOSE_MIN, L_CHOOSE_MAX
+ * \return pixd always
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixMaxDynamicRange()
+ *
+ * \param[in] pixs 4, 8, 16 or 32 bpp source
+ * \param[in] type L_LINEAR_SCALE or L_LOG_SCALE
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Scales pixel values to fit maximally within the dest 8 bpp pixd
+ * (2) Assumes the source 'pixels' are a 1-component scalar. For
+ * a 32 bpp source, each pixel is treated as a single number --
+ * not as a 3-component rgb pixel value.
+ * (3) Uses a LUT for log scaling.
+ * </pre>
+ */
+PIX *
+pixMaxDynamicRange(PIX *pixs,
+ l_int32 type)
+{
+l_uint8 dval;
+l_int32 i, j, w, h, d, wpls, wpld, max;
+l_uint32 *datas, *datad;
+l_uint32 word, sval;
+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);
+ pixGetDimensions(pixs, &w, &h, &d);
+ 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);
+
+ 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 (rgb) */
+ max = L_MAX(max, word);
+ }
+ }
+ }
+
+ /* Map to the full dynamic range */
+ 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;
+}
+
+
+/*!
+ * \brief pixMaxDynamicRangeRGB()
+ *
+ * \param[in] pixs 32 bpp rgb source
+ * \param[in] type L_LINEAR_SCALE or L_LOG_SCALE
+ * \return pixd 32 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Scales pixel values to fit maximally within a 32 bpp dest pixd
+ * (2) All color components are scaled with the same factor, based
+ * on the maximum r,g or b component in the image. This should
+ * not be used if the 32-bit value is a single number (e.g., a
+ * count in a histogram generated by pixMakeHistoHS()).
+ * (3) Uses a LUT for log scaling.
+ * </pre>
+ */
+PIX *
+pixMaxDynamicRangeRGB(PIX *pixs,
+ l_int32 type)
+{
+l_int32 i, j, w, h, wpls, wpld, max;
+l_uint32 sval, dval, word;
+l_uint32 *datas, *datad;
+l_uint32 *lines, *lined;
+l_float32 factor;
+l_float32 *tab;
+PIX *pixd;
+
+ PROCNAME("pixMaxDynamicRangeRGB");
+
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+ if (type != L_LINEAR_SCALE && type != L_LOG_SCALE)
+ return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+ /* Get max */
+ pixd = pixCreateTemplate(pixs);
+ datas = pixGetData(pixs);
+ datad = pixGetData(pixd);
+ wpls = pixGetWpl(pixs);
+ wpld = pixGetWpl(pixd);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ max = 0;
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ for (j = 0; j < wpls; j++) {
+ word = lines[j];
+ max = L_MAX(max, word >> 24);
+ max = L_MAX(max, (word >> 16) & 0xff);
+ max = L_MAX(max, (word >> 8) & 0xff);
+ }
+ }
+ if (max == 0) {
+ L_WARNING("max = 0; setting to 1\n", procName);
+ max = 1;
+ }
+
+ /* Map to the full dynamic range */
+ 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 = linearScaleRGBVal(sval, factor);
+ 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 = logScaleRGBVal(sval, tab, factor);
+ lined[j] = dval;
+ }
+ }
+ LEPT_FREE(tab);
+ }
+
+ return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ * RGB pixel value scaling *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief linearScaleRGBVal()
+ *
+ * \param[in] sval 32-bit rgb pixel value
+ * \param[in] factor multiplication factor on each component
+ * \return dval linearly scaled version of %sval
+ *
+ * <pre>
+ * Notes:
+ * (1) %factor must be chosen to be not greater than (255 / maxcomp),
+ * where maxcomp is the maximum value of the pixel components.
+ * Otherwise, the product will overflow a uint8. In use, factor
+ * is the same for all pixels in a pix.
+ * (2) No scaling is performed on the transparency ("A") component.
+ * </pre>
+ */
+l_uint32
+linearScaleRGBVal(l_uint32 sval,
+ l_float32 factor)
+{
+l_uint32 dval;
+
+ dval = ((l_uint8)(factor * (sval >> 24) + 0.5) << 24) |
+ ((l_uint8)(factor * ((sval >> 16) & 0xff) + 0.5) << 16) |
+ ((l_uint8)(factor * ((sval >> 8) & 0xff) + 0.5) << 8) |
+ (sval & 0xff);
+ return dval;
+}
+
+
+/*!
+ * \brief logScaleRGBVal()
+ *
+ * \param[in] sval 32-bit rgb pixel value
+ * \param[in] tab 256 entry log-base-2 table
+ * \param[in] factor multiplication factor on each component
+ * \return dval log scaled version of %sval
+ *
+ * <pre>
+ * Notes:
+ * (1) %tab is made with makeLogBase2Tab().
+ * (2) %factor must be chosen to be not greater than
+ * 255.0 / log[base2](maxcomp), where maxcomp is the maximum
+ * value of the pixel components. Otherwise, the product
+ * will overflow a uint8. In use, factor is the same for
+ * all pixels in a pix.
+ * (3) No scaling is performed on the transparency ("A") component.
+ * </pre>
+ */
+l_uint32
+logScaleRGBVal(l_uint32 sval,
+ l_float32 *tab,
+ l_float32 factor)
+{
+l_uint32 dval;
+
+ dval = ((l_uint8)(factor * getLogBase2(sval >> 24, tab) + 0.5) << 24) |
+ ((l_uint8)(factor * getLogBase2(((sval >> 16) & 0xff), tab) + 0.5)
+ << 16) |
+ ((l_uint8)(factor * getLogBase2(((sval >> 8) & 0xff), tab) + 0.5)
+ << 8) |
+ (sval & 0xff);
+ return dval;
+}
+
+
+/*-----------------------------------------------------------------------*
+ * Log base2 lookup *
+ *-----------------------------------------------------------------------*/
+/*
+ * \brief makeLogBase2Tab()
+ *
+ * \return tab table giving the log[base2] of values from 1 to 255
+ */
+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;
+}
+
+
+/*
+ * \brief getLogBase2()
+ *
+ * \param[in] val in range [0 ... 255]
+ * \param[in] logtab 256-entry table of logs
+ * \return logval log[base2] of %val, 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/leptonica/src/pixcomp.c b/leptonica/src/pixcomp.c
new file mode 100644
index 00000000..568a280c
--- /dev/null
+++ b/leptonica/src/pixcomp.c
@@ -0,0 +1,2481 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pixcomp.c
+ * <pre>
+ *
+ * Pixcomp creation and destruction
+ * PIXC *pixcompCreateFromPix()
+ * PIXC *pixcompCreateFromString()
+ * PIXC *pixcompCreateFromFile()
+ * void pixcompDestroy()
+ * PIXC *pixcompCopy()
+
+ * Pixcomp accessors
+ * l_int32 pixcompGetDimensions()
+ * l_int32 pixcompGetParameters()
+ *
+ * Pixcomp compression selection
+ * 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()
+ *
+ * Combining pixacomp
+ * l_int32 pixacompJoin()
+ * PIXAC *pixacompInterleave()
+ *
+ * Pixacomp serialized I/O
+ * PIXAC *pixacompRead()
+ * PIXAC *pixacompReadStream()
+ * PIXAC *pixacompReadMem()
+ * l_int32 pixacompWrite()
+ * l_int32 pixacompWriteStream()
+ * l_int32 pixacompWriteMem()
+ *
+ * Conversion to pdf
+ * l_int32 pixacompConvertToPdf()
+ * l_int32 pixacompConvertToPdfData()
+ * l_int32 pixacompFastConvertToPdfData()
+ *
+ * Output for debugging
+ * l_int32 pixacompWriteStreamInfo()
+ * l_int32 pixcompWriteStreamInfo()
+ * PIX *pixacompDisplayTiledAndScaled()
+ * l_int32 pixacompWriteFiles()
+ * l_int32 pixcompWriteFile()
+ *
+ * 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 jpeg compression quality
+ * can be specified using l_setJpegQuality(); otherwise the default is 75.
+ *
+ * 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, initialize the pixacomp with
+ * pixacompCreate(), which generates an empty array of pixcomps ptrs.
+ * 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 could 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Bounds on pixacomp array size */
+static const l_uint32 MaxPtrArraySize = 1000000;
+static const l_int32 InitialPtrArraySize = 20; /*!< n'importe quoi */
+
+ /* Bound on size for a compressed data string */
+static const size_t MaxDataSize = 1000000000; /* 1 GB */
+
+ /* These two globals are defined in writefile.c */
+extern l_int32 NumImageFileFormatExtensions;
+extern const char *ImageFileFormatExtensions[];
+
+ /* Static functions */
+static l_int32 pixacompExtendArray(PIXAC *pixac);
+static l_int32 pixcompFastConvertToPdfData(PIXC *pixc, const char *title,
+ l_uint8 **pdata, size_t *pnbytes);
+
+
+/*---------------------------------------------------------------------*
+ * Pixcomp creation and destruction *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixcompCreateFromPix()
+ *
+ * \param[in] pix
+ * \param[in] comptype IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return pixc, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Use %comptype == IFF_DEFAULT to have the compression
+ * type automatically determined.
+ * (2) To compress jpeg with a quality other than the default (75), use
+ * l_jpegSetQuality()
+ * </pre>
+ */
+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);
+
+ pixc = (PIXC *)LEPT_CALLOC(1, sizeof(PIXC));
+ 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;
+}
+
+
+/*!
+ * \brief pixcompCreateFromString()
+ *
+ * \param[in] data compressed string
+ * \param[in] size number of bytes
+ * \param[in] copyflag L_INSERT or L_COPY
+ * \return pixc, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ pixc = (PIXC *)LEPT_CALLOC(1, sizeof(PIXC));
+ 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;
+}
+
+
+/*!
+ * \brief pixcompCreateFromFile()
+ *
+ * \param[in] filename
+ * \param[in] comptype IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return pixc, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixcompDestroy()
+ *
+ * \param[in,out] ppixc use ptr address so it will be nulled
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixcompCopy()
+ *
+ * \param[in] pixcs
+ * \return pixcd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Limit the size of the compressed pix to 500 MB.
+ * </pre>
+ */
+PIXC *
+pixcompCopy(PIXC *pixcs)
+{
+size_t size;
+l_uint8 *datas, *datad;
+PIXC *pixcd;
+
+ PROCNAME("pixcompCopy");
+
+ if (!pixcs)
+ return (PIXC *)ERROR_PTR("pixcs not defined", procName, NULL);
+ size = pixcs->size;
+ if (size > MaxDataSize)
+ return (PIXC *)ERROR_PTR("size > 1 GB; too big", procName, NULL);
+
+ pixcd = (PIXC *)LEPT_CALLOC(1, sizeof(PIXC));
+ pixcd->w = pixcs->w;
+ pixcd->h = pixcs->h;
+ pixcd->d = pixcs->d;
+ pixcd->xres = pixcs->xres;
+ pixcd->yres = pixcs->yres;
+ pixcd->comptype = pixcs->comptype;
+ if (pixcs->text != NULL)
+ pixcd->text = stringNew(pixcs->text);
+ pixcd->cmapflag = pixcs->cmapflag;
+
+ /* Copy image data */
+ datas = pixcs->data;
+ if ((datad = (l_uint8 *)LEPT_CALLOC(size, sizeof(l_int8))) == NULL) {
+ pixcompDestroy(&pixcd);
+ return (PIXC *)ERROR_PTR("pixcd not made", procName, NULL);
+ }
+ memcpy(datad, datas, size);
+ pixcd->data = datad;
+ pixcd->size = size;
+ return pixcd;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pixcomp accessors *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixcompGetDimensions()
+ *
+ * \param[in] pixc
+ * \param[out] pw, ph, pd [optional]
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixcompGetParameters()
+ *
+ * \param[in] pixc
+ * \param[out] pxres, pyres, pcomptype, pcmapflag [optional]
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixcompGetParameters(PIXC *pixc,
+ l_int32 *pxres,
+ l_int32 *pyres,
+ l_int32 *pcomptype,
+ l_int32 *pcmapflag)
+{
+ PROCNAME("pixcompGetParameters");
+
+ if (!pixc)
+ return ERROR_INT("pixc not defined", procName, 1);
+ if (pxres) *pxres = pixc->xres;
+ if (pyres) *pyres = pixc->yres;
+ if (pcomptype) *pcomptype = pixc->comptype;
+ if (pcmapflag) *pcmapflag = pixc->cmapflag;
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pixcomp compression selection *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixcompDetermineFormat()
+ *
+ * \param[in] comptype IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \param[in] d pix depth
+ * \param[in] cmapflag 1 if pix to be compressed as a colormap; 0 otherwise
+ * \param[out] pformat IFF_TIFF, IFF_PNG or IFF_JFIF_JPEG
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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 %d == 8 with no colormap and:
+ * * you wish to compress with png, use %comptype == IFF_PNG
+ * * you wish to compress with jpeg, use either
+ * %comptype == IFF_JFIF_JPEG or %comptype == IFF_DEFAULT.
+ * (4) If the pix cannot be compressed by the input value of
+ * %comptype, this selects IFF_PNG, which can compress all pix.
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixCreateFromPixcomp()
+ *
+ * \param[in] 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixacompCreate()
+ *
+ * \param[in] n initial number of ptrs
+ * \return pixac, or NULL on error
+ */
+PIXAC *
+pixacompCreate(l_int32 n)
+{
+PIXAC *pixac;
+
+ PROCNAME("pixacompCreate");
+
+ if (n <= 0 || n > MaxPtrArraySize)
+ n = InitialPtrArraySize;
+
+ pixac = (PIXAC *)LEPT_CALLOC(1, sizeof(PIXAC));
+ pixac->n = 0;
+ pixac->nalloc = n;
+ pixac->offset = 0;
+ if ((pixac->pixc = (PIXC **)LEPT_CALLOC(n, sizeof(PIXC *))) == NULL) {
+ pixacompDestroy(&pixac);
+ return (PIXAC *)ERROR_PTR("pixc ptrs not made", procName, NULL);
+ }
+ if ((pixac->boxa = boxaCreate(n)) == NULL) {
+ pixacompDestroy(&pixac);
+ return (PIXAC *)ERROR_PTR("boxa not made", procName, NULL);
+ }
+
+ return pixac;
+}
+
+
+/*!
+ * \brief pixacompCreateWithInit()
+ *
+ * \param[in] n initial number of ptrs
+ * \param[in] offset difference: accessor index - pixacomp array index
+ * \param[in] pix [optional] initialize each ptr in pixacomp
+ * to this pix; can be NULL
+ * \param[in] comptype IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return pixac, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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 || n > MaxPtrArraySize)
+ return (PIXAC *)ERROR_PTR("n out of valid bounds", 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, L_INSERT);
+ }
+ pixDestroy(&pixt);
+
+ return pixac;
+}
+
+
+/*!
+ * \brief pixacompCreateFromPixa()
+ *
+ * \param[in] pixa
+ * \param[in] comptype IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \param[in] accesstype L_COPY, L_CLONE, L_COPY_CLONE
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * (3) To compress jpeg with a quality other than the default (75), use
+ * l_jpegSetQuality()
+ * </pre>
+ */
+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) {
+ boxaDestroy(&pixac->boxa);
+ pixac->boxa = boxa;
+ }
+
+ return pixac;
+}
+
+
+/*!
+ * \brief pixacompCreateFromFiles()
+ *
+ * \param[in] dirname
+ * \param[in] substr [optional] substring filter on filenames; can be null
+ * \param[in] comptype IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return pixac, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixacompCreateFromSA()
+ *
+ * \param[in] sa full pathnames for all files
+ * \param[in] comptype IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return pixac, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, L_INSERT);
+ }
+ return pixac;
+}
+
+
+/*!
+ * \brief pixacompDestroy()
+ *
+ * \param[in,out] ppixac use ptr address so it will be nulled
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pixacomp addition *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixacompAddPix()
+ *
+ * \param[in] pixac
+ * \param[in] pix to be added
+ * \param[in] comptype IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The array is filled up to the (n-1)-th element, and this
+ * converts the input pix to a pixc and adds it at
+ * the n-th position.
+ * (2) The pixc produced from the pix is owned by the pixac.
+ * The input pix is not affected.
+ * </pre>
+ */
+l_ok
+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, L_INSERT);
+ return 0;
+}
+
+
+/*!
+ * \brief pixacompAddPixcomp()
+ *
+ * \param[in] pixac
+ * \param[in] pixc to be added by insertion
+ * \param[in] copyflag L_INSERT, L_COPY
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Anything added to a pixac is owned by the pixac.
+ * So do not L_INSERT a pixc that is owned by another pixac,
+ * or destroy a pixc that has been L_INSERTed.
+ * </pre>
+ */
+l_ok
+pixacompAddPixcomp(PIXAC *pixac,
+ PIXC *pixc,
+ l_int32 copyflag)
+{
+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);
+ if (copyflag != L_INSERT && copyflag != L_COPY)
+ return ERROR_INT("invalid copyflag", procName, 1);
+
+ n = pixac->n;
+ if (n >= pixac->nalloc) {
+ if (pixacompExtendArray(pixac))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+
+ if (copyflag == L_INSERT)
+ pixac->pixc[n] = pixc;
+ else /* L_COPY */
+ pixac->pixc[n] = pixcompCopy(pixc);
+ pixac->n++;
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixacompExtendArray()
+ *
+ * \param[in] pixac
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * (2) The max number of pixcomp ptrs is 1M.
+ * </pre>
+ */
+static l_int32
+pixacompExtendArray(PIXAC *pixac)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("pixacompExtendArray");
+
+ if (!pixac)
+ return ERROR_INT("pixac not defined", procName, 1);
+ if (pixac->nalloc > MaxPtrArraySize) /* belt & suspenders */
+ return ERROR_INT("pixac has too many ptrs", procName, 1);
+ oldsize = pixac->nalloc * sizeof(PIXC *);
+ newsize = 2 * oldsize;
+ if (newsize > 8 * MaxPtrArraySize) /* ptrs for 1M pixcomp */
+ return ERROR_INT("newsize > 8 MB; too large", procName, 1);
+
+ if ((pixac->pixc = (PIXC **)reallocNew((void **)&pixac->pixc,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+ pixac->nalloc *= 2;
+ boxaExtendArray(pixac->boxa);
+ return 0;
+}
+
+
+/*!
+ * \brief pixacompReplacePix()
+ *
+ * \param[in] pixac
+ * \param[in] index caller's view of index within pixac; includes offset
+ * \param[in] pix owned by the caller
+ * \param[in] comptype IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixacompReplacePixcomp()
+ *
+ * \param[in] pixac
+ * \param[in] index caller's view of index within pixac; includes offset
+ * \param[in] pixc to replace existing one, which is destroyed
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, L_NOCOPY); /* use %index */
+ pixcompDestroy(&pixct);
+ pixac->pixc[aindex] = pixc; /* replace; use array index */
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixacompAddBox()
+ *
+ * \param[in] pixac
+ * \param[in] box
+ * \param[in] copyflag L_INSERT, L_COPY
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixacompGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixacompGetPixcomp()
+ *
+ * \param[in] pixac
+ * \param[in] index caller's view of index within pixac; includes offset
+ * \param[in] copyflag L_NOCOPY, L_COPY
+ * \return pixc, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The %index includes the offset, which must be subtracted
+ * to get the actual index into the ptr array.
+ * (2) If copyflag == L_NOCOPY, the pixc is owned by %pixac; do
+ * not destroy.
+ * </pre>
+ */
+PIXC *
+pixacompGetPixcomp(PIXAC *pixac,
+ l_int32 index,
+ l_int32 copyflag)
+{
+l_int32 aindex;
+
+ PROCNAME("pixacompGetPixcomp");
+
+ if (!pixac)
+ return (PIXC *)ERROR_PTR("pixac not defined", procName, NULL);
+ if (copyflag != L_NOCOPY && copyflag != L_COPY)
+ return (PIXC *)ERROR_PTR("invalid copyflag", procName, NULL);
+ aindex = index - pixac->offset;
+ if (aindex < 0 || aindex >= pixac->n)
+ return (PIXC *)ERROR_PTR("array index not valid", procName, NULL);
+
+ if (copyflag == L_NOCOPY)
+ return pixac->pixc[aindex];
+ else /* L_COPY */
+ return pixcompCopy(pixac->pixc[aindex]);
+}
+
+
+/*!
+ * \brief pixacompGetPix()
+ *
+ * \param[in] pixac
+ * \param[in] index caller's view of index within pixac; includes offset
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The %index includes the offset, which must be subtracted
+ * to get the actual index into the ptr array.
+ * </pre>
+ */
+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, L_NOCOPY);
+ return pixCreateFromPixcomp(pixc);
+}
+
+
+/*!
+ * \brief pixacompGetPixDimensions()
+ *
+ * \param[in] pixac
+ * \param[in] index caller's view of index within pixac;
+ * includes offset
+ * \param[out] pw, ph, pd [optional] each can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The %index includes the offset, which must be subtracted
+ * to get the actual index into the ptr array.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixacompGetBoxa()
+ *
+ * \param[in] pixac
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief pixacompGetBoxaCount()
+ *
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief pixacompGetBox()
+ *
+ * \param[in] pixac
+ * \param[in] index caller's view of index within pixac;
+ * includes offset
+ * \param[in] accesstype L_COPY or L_CLONE
+ * \return box if null, not automatically an error, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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;
+ }
+}
+
+
+/*!
+ * \brief pixacompGetBoxGeometry()
+ *
+ * \param[in] pixac
+ * \param[in] index caller's view of index within pixac;
+ * includes offset
+ * \param[out] px, py, pw, ph [optional] each can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The %index includes the offset, which must be subtracted
+ * to get the actual index into the ptr array.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixacompGetOffset()
+ *
+ * \param[in] pixac
+ * \return offset, or 0 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_int32
+pixacompGetOffset(PIXAC *pixac)
+{
+ PROCNAME("pixacompGetOffset");
+
+ if (!pixac)
+ return ERROR_INT("pixac not defined", procName, 0);
+ return pixac->offset;
+}
+
+
+/*!
+ * \brief pixacompSetOffset()
+ *
+ * \param[in] pixac
+ * \param[in] offset non-negative
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaCreateFromPixacomp()
+ *
+ * \param[in] pixac
+ * \param[in] accesstype L_COPY, L_CLONE, L_COPY_CLONE; for boxa
+ * \return pixa if OK, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Because the pixa has no notion of offset, the offset must
+ * be set to 0 before the conversion, so that pixacompGetPix()
+ * fetches all the pixcomps. It is reset at the end.
+ * </pre>
+ */
+PIXA *
+pixaCreateFromPixacomp(PIXAC *pixac,
+ l_int32 accesstype)
+{
+l_int32 i, n, offset;
+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);
+ offset = pixacompGetOffset(pixac);
+ pixacompSetOffset(pixac, 0);
+ 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);
+ }
+ pixacompSetOffset(pixac, offset);
+
+ return pixa;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Combining pixacomp
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixacompJoin()
+ *
+ * \param[in] pixacd dest pixac; add to this one
+ * \param[in] pixacs [optional] source pixac; add from this one
+ * \param[in] istart starting index in pixacs
+ * \param[in] iend ending index in pixacs; use -1 to cat all
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This appends a clone of each indicated pixc in pixcas to pixcad
+ * (2) istart < 0 is taken to mean 'read from the start' (istart = 0)
+ * (3) iend < 0 means 'read to the end'
+ * (4) If pixacs is NULL or contains no pixc, this is a no-op.
+ * </pre>
+ */
+l_ok
+pixacompJoin(PIXAC *pixacd,
+ PIXAC *pixacs,
+ l_int32 istart,
+ l_int32 iend)
+{
+l_int32 i, n, nb;
+BOXA *boxas, *boxad;
+PIXC *pixc;
+
+ PROCNAME("pixacompJoin");
+
+ if (!pixacd)
+ return ERROR_INT("pixacd not defined", procName, 1);
+ if (!pixacs || ((n = pixacompGetCount(pixacs)) == 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++) {
+ pixc = pixacompGetPixcomp(pixacs, i, L_NOCOPY);
+ pixacompAddPixcomp(pixacd, pixc, L_COPY);
+ }
+
+ boxas = pixacompGetBoxa(pixacs, L_CLONE);
+ boxad = pixacompGetBoxa(pixacd, L_CLONE);
+ nb = pixacompGetBoxaCount(pixacs);
+ iend = L_MIN(iend, nb - 1);
+ boxaJoin(boxad, boxas, istart, iend);
+ boxaDestroy(&boxas); /* just the clones */
+ boxaDestroy(&boxad); /* ditto */
+ return 0;
+}
+
+
+/*!
+ * \brief pixacompInterleave()
+ *
+ * \param[in] pixac1 first src pixac
+ * \param[in] pixac2 second src pixac
+ * \return pixacd interleaved from sources, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) If the two pixac have different sizes, a warning is issued,
+ * and the number of pairs returned is the minimum size.
+ * </pre>
+ */
+PIXAC *
+pixacompInterleave(PIXAC *pixac1,
+ PIXAC *pixac2)
+{
+l_int32 i, n1, n2, n, nb1, nb2;
+BOX *box;
+PIXC *pixc1, *pixc2;
+PIXAC *pixacd;
+
+ PROCNAME("pixacompInterleave");
+
+ if (!pixac1)
+ return (PIXAC *)ERROR_PTR("pixac1 not defined", procName, NULL);
+ if (!pixac2)
+ return (PIXAC *)ERROR_PTR("pixac2 not defined", procName, NULL);
+ n1 = pixacompGetCount(pixac1);
+ n2 = pixacompGetCount(pixac2);
+ n = L_MIN(n1, n2);
+ if (n == 0)
+ return (PIXAC *)ERROR_PTR("at least one input pixac is empty",
+ procName, NULL);
+ if (n1 != n2)
+ L_WARNING("counts differ: %d != %d\n", procName, n1, n2);
+
+ pixacd = pixacompCreate(2 * n);
+ nb1 = pixacompGetBoxaCount(pixac1);
+ nb2 = pixacompGetBoxaCount(pixac2);
+ for (i = 0; i < n; i++) {
+ pixc1 = pixacompGetPixcomp(pixac1, i, L_COPY);
+ pixacompAddPixcomp(pixacd, pixc1, L_INSERT);
+ if (i < nb1) {
+ box = pixacompGetBox(pixac1, i, L_COPY);
+ pixacompAddBox(pixacd, box, L_INSERT);
+ }
+ pixc2 = pixacompGetPixcomp(pixac2, i, L_COPY);
+ pixacompAddPixcomp(pixacd, pixc2, L_INSERT);
+ if (i < nb2) {
+ box = pixacompGetBox(pixac2, i, L_COPY);
+ pixacompAddBox(pixacd, box, L_INSERT);
+ }
+ }
+
+ return pixacd;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pixacomp serialized I/O *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixacompRead()
+ *
+ * \param[in] filename
+ * \return pixac, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ pixac = pixacompReadStream(fp);
+ fclose(fp);
+ if (!pixac)
+ return (PIXAC *)ERROR_PTR("pixac not read", procName, NULL);
+ return pixac;
+}
+
+
+/*!
+ * \brief pixacompReadStream()
+ *
+ * \param[in] fp file stream
+ * \return pixac, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is OK for the pixacomp to be empty.
+ * </pre>
+ */
+PIXAC *
+pixacompReadStream(FILE *fp)
+{
+char buf[256];
+l_uint8 *data;
+l_int32 n, offset, i, w, h, d, ignore;
+l_int32 comptype, cmapflag, version, xres, yres;
+size_t size;
+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", &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 (n < 0)
+ return (PIXAC *)ERROR_PTR("num pixcomp ptrs < 0", procName, NULL);
+ if (n > MaxPtrArraySize)
+ return (PIXAC *)ERROR_PTR("too many pixcomp ptrs", procName, NULL);
+ if (n == 0) L_INFO("the pixacomp is empty\n", procName);
+
+ if ((pixac = pixacompCreate(n)) == NULL)
+ return (PIXAC *)ERROR_PTR("pixac not made", procName, NULL);
+ if ((boxa = boxaReadStream(fp)) == NULL) {
+ pixacompDestroy(&pixac);
+ 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 (fscanf(fp, "\nPixcomp[%d]: w = %d, h = %d, d = %d\n",
+ &ignore, &w, &h, &d) != 4) {
+ pixacompDestroy(&pixac);
+ return (PIXAC *)ERROR_PTR("dimension reading", procName, NULL);
+ }
+ if (fscanf(fp, " comptype = %d, size = %zu, cmapflag = %d\n",
+ &comptype, &size, &cmapflag) != 3) {
+ pixacompDestroy(&pixac);
+ return (PIXAC *)ERROR_PTR("comptype/size reading", procName, NULL);
+ }
+ if (size > MaxDataSize) {
+ pixacompDestroy(&pixac);
+ L_ERROR("data size = %zu is too big", procName, size);
+ return 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) {
+ pixacompDestroy(&pixac);
+ return (PIXAC *)ERROR_PTR("fgets read fail", procName, NULL);
+ }
+ if (sscanf(buf, " xres = %d, yres = %d\n", &xres, &yres) != 2) {
+ pixacompDestroy(&pixac);
+ return (PIXAC *)ERROR_PTR("read fail for res", procName, NULL);
+ }
+ if ((data = (l_uint8 *)LEPT_CALLOC(1, size)) == NULL) {
+ pixacompDestroy(&pixac);
+ return (PIXAC *)ERROR_PTR("calloc fail for data", procName, NULL);
+ }
+ if (fread(data, 1, size, fp) != size) {
+ pixacompDestroy(&pixac);
+ LEPT_FREE(data);
+ return (PIXAC *)ERROR_PTR("error reading data", procName, NULL);
+ }
+ fgetc(fp); /* swallow the ending nl */
+ pixc = (PIXC *)LEPT_CALLOC(1, sizeof(PIXC));
+ 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, L_INSERT);
+ }
+ return pixac;
+}
+
+
+/*!
+ * \brief pixacompReadMem()
+ *
+ * \param[in] data in pixacomp format
+ * \param[in] size of data
+ * \return pixac, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Deseralizes a buffer of pixacomp data into a pixac in memory.
+ * </pre>
+ */
+PIXAC *
+pixacompReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+PIXAC *pixac;
+
+ PROCNAME("pixacompReadMem");
+
+ if (!data)
+ return (PIXAC *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (PIXAC *)ERROR_PTR("stream not opened", procName, NULL);
+
+ pixac = pixacompReadStream(fp);
+ fclose(fp);
+ if (!pixac) L_ERROR("pixac not read\n", procName);
+ return pixac;
+}
+
+
+/*!
+ * \brief pixacompWrite()
+ *
+ * \param[in] filename
+ * \param[in] pixac
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+pixacompWrite(const char *filename,
+ PIXAC *pixac)
+{
+l_int32 ret;
+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);
+ ret = pixacompWriteStream(fp, pixac);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("pixacomp not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixacompWriteStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] pixac
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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", 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, pixac->offset + i, L_NOCOPY))
+ == 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 = %zu, cmapflag = %d\n",
+ pixc->comptype, 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;
+}
+
+
+/*!
+ * \brief pixacompWriteMem()
+ *
+ * \param[out] pdata serialized data of pixac
+ * \param[out] psize size of serialized data
+ * \param[in] pixac
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a pixac in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+pixacompWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ PIXAC *pixac)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("pixacompWriteMem");
+
+ 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 (!pixac)
+ return ERROR_INT("&pixac not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = pixacompWriteStream(fp, pixac);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = pixacompWriteStream(fp, pixac);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Conversion to pdf *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixacompConvertToPdf()
+ *
+ * \param[in] pixac containing images all at the same resolution
+ * \param[in] res override the resolution of each input image,
+ * in ppi; 0 to respect the resolution embedded
+ * in the input
+ * \param[in] scalefactor scaling factor applied to each image; > 0.0
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, L_JP2K_ENCODE, or
+ * L_DEFAULT_ENCODE for default)
+ * \param[in] quality used for JPEG only; 0 for default (75)
+ * \param[in] title [optional] pdf title
+ * \param[in] fileout pdf file of all images
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * (5) If all images are jpeg compressed, don't require scaling
+ * and have the same resolution, it is much faster to skip
+ * transcoding with pixacompFastConvertToPdfData(), and then
+ * write the data out to file.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixacompConvertToPdfData()
+ *
+ * \param[in] pixac containing images all at the same resolution
+ * \param[in] res input resolution of all images
+ * \param[in] scalefactor scaling factor applied to each image; > 0.0
+ * \param[in] type encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ * L_FLATE_ENCODE, L_JP2K_ENCODE, or
+ * L_DEFAULT_ENCODE for default)
+ * \param[in] quality used for JPEG only; 0 for default (75)
+ * \param[in] title [optional] pdf title
+ * \param[out] pdata output pdf data (of all images
+ * \param[out] pnbytes size of output pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixacompConvertToPdf().
+ * </pre>
+ */
+l_ok
+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_JPEG_ENCODE &&
+ type != L_G4_ENCODE && type != L_FLATE_ENCODE &&
+ type != L_JP2K_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);
+
+ /* Select the encoding type */
+ 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);
+ 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;
+}
+
+
+/*!
+ * \brief pixacompFastConvertToPdfData()
+ *
+ * \param[in] pixac containing images all at the same resolution
+ * \param[in] title [optional] pdf title
+ * \param[out] pdata output pdf data (of all images
+ * \param[out] pnbytes size of output pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates the pdf without transcoding if all the
+ * images in %pixac are compressed with jpeg.
+ * Images not jpeg compressed are skipped.
+ * (2) It assumes all images have the same resolution, and that
+ * the resolution embedded in each jpeg file is correct.
+ * </pre>
+ */
+l_ok
+pixacompFastConvertToPdfData(PIXAC *pixac,
+ const char *title,
+ l_uint8 **pdata,
+ size_t *pnbytes)
+{
+l_uint8 *imdata;
+l_int32 i, n, ret, comptype;
+size_t imbytes;
+L_BYTEA *ba;
+PIXC *pixc;
+L_PTRA *pa_data;
+
+ PROCNAME("pixacompFastConvertToPdfData");
+
+ 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);
+
+ /* Generate all the encoded pdf strings */
+ n = pixacompGetCount(pixac);
+ pa_data = ptraCreate(n);
+ for (i = 0; i < n; i++) {
+ if ((pixc = pixacompGetPixcomp(pixac, i, L_NOCOPY)) == NULL) {
+ L_ERROR("pixc[%d] not retrieved\n", procName, i);
+ continue;
+ }
+ pixcompGetParameters(pixc, NULL, NULL, &comptype, NULL);
+ if (comptype != IFF_JFIF_JPEG) {
+ L_ERROR("pixc[%d] not jpeg compressed\n", procName, i);
+ continue;
+ }
+ ret = pixcompFastConvertToPdfData(pixc, title, &imdata, &imbytes);
+ if (ret) {
+ L_ERROR("pdf encoding failed for pixc[%d]\n", procName, i);
+ continue;
+ }
+ ba = l_byteaInitFromMem(imdata, imbytes);
+ 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);
+
+ /* Clean up */
+ 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;
+}
+
+
+/*!
+ * \brief pixcompFastConvertToPdfData()
+ *
+ * \param[in] pixc containing images all at the same resolution
+ * \param[in] title [optional] pdf title
+ * \param[out] pdata output pdf data (of all images
+ * \param[out] pnbytes size of output pdf data
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates the pdf without transcoding.
+ * (2) It assumes all images are jpeg encoded, have the same
+ * resolution, and that the resolution embedded in each
+ * jpeg file is correct. (It is transferred to the pdf
+ * via the cid.)
+ * </pre>
+ */
+static l_int32
+pixcompFastConvertToPdfData(PIXC *pixc,
+ const char *title,
+ l_uint8 **pdata,
+ size_t *pnbytes)
+{
+l_uint8 *data;
+L_COMP_DATA *cid;
+
+ PROCNAME("pixacompFastConvertToPdfData");
+
+ 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 (!pixc)
+ return ERROR_INT("pixc not defined", procName, 1);
+
+ /* Make a copy of the data */
+ data = l_binaryCopy(pixc->data, pixc->size);
+ cid = l_generateJpegDataMem(data, pixc->size, 0);
+
+ /* Note: cid is destroyed, along with data, by this function */
+ return cidConvertToPdfData(cid, title, pdata, pnbytes);
+}
+
+
+/*--------------------------------------------------------------------*
+ * Output for debugging *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixacompWriteStreamInfo()
+ *
+ * \param[in] fp file stream
+ * \param[in] pixac
+ * \param[in] text [optional] identifying string; can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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, L_NOCOPY);
+ pixcompWriteStreamInfo(fp, pixc, NULL);
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief pixcompWriteStreamInfo()
+ *
+ * \param[in] fp file stream
+ * \param[in] pixc
+ * \param[in] text [optional] identifying string; can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 = %zu\n",
+ pixc->xres, pixc->yres, 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;
+}
+
+
+/*!
+ * \brief pixacompDisplayTiledAndScaled()
+ *
+ * \param[in] pixac
+ * \param[in] outdepth output depth: 1, 8 or 32 bpp
+ * \param[in] tilewidth each pix is scaled to this width
+ * \param[in] ncols number of tiles in each row
+ * \param[in] background 0 for white, 1 for black; this is the color
+ * of the spacing between the images
+ * \param[in] spacing between images, and on outside
+ * \param[in] border width of additional black border on each image;
+ * use 0 for no border
+ * \return pix of tiled images, or NULL on error
+ *
+ * <pre>
+ * 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) See pixaDisplayTiledAndScaled() for details.
+ * </pre>
+ */
+PIX *
+pixacompDisplayTiledAndScaled(PIXAC *pixac,
+ l_int32 outdepth,
+ l_int32 tilewidth,
+ l_int32 ncols,
+ l_int32 background,
+ l_int32 spacing,
+ l_int32 border)
+{
+PIX *pixd;
+PIXA *pixa;
+
+ PROCNAME("pixacompDisplayTiledAndScaled");
+
+ if (!pixac)
+ return (PIX *)ERROR_PTR("pixac not defined", procName, NULL);
+
+ if ((pixa = pixaCreateFromPixacomp(pixac, L_COPY)) == NULL)
+ return (PIX *)ERROR_PTR("pixa not made", procName, NULL);
+
+ pixd = pixaDisplayTiledAndScaled(pixa, outdepth, tilewidth, ncols,
+ background, spacing, border);
+ pixaDestroy(&pixa);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixacompWriteFiles()
+ *
+ * \param[in] pixac
+ * \param[in] subdir subdirectory of /tmp
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixacompWriteFiles(PIXAC *pixac,
+ const char *subdir)
+{
+char buf[128];
+l_int32 i, n;
+PIXC *pixc;
+
+ PROCNAME("pixacompWriteFiles");
+
+ if (!pixac)
+ return ERROR_INT("pixac not defined", procName, 1);
+
+ if (lept_mkdir(subdir) > 0)
+ return ERROR_INT("invalid subdir", procName, 1);
+
+ n = pixacompGetCount(pixac);
+ for (i = 0; i < n; i++) {
+ pixc = pixacompGetPixcomp(pixac, i, L_NOCOPY);
+ snprintf(buf, sizeof(buf), "/tmp/%s/%03d", subdir, i);
+ pixcompWriteFile(buf, pixc);
+ }
+ return 0;
+}
+
+extern const char *ImageFileFormatExtensions[];
+
+/*!
+ * \brief pixcompWriteFile()
+ *
+ * \param[in] rootname
+ * \param[in] pixc
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The compressed data is written to file, and the filename is
+ * generated by appending the format extension to %rootname.
+ * </pre>
+ */
+l_ok
+pixcompWriteFile(const char *rootname,
+ PIXC *pixc)
+{
+char buf[128];
+
+ PROCNAME("pixcompWriteFile");
+
+ if (!pixc)
+ return ERROR_INT("pixc not defined", procName, 1);
+
+ snprintf(buf, sizeof(buf), "%s.%s", rootname,
+ ImageFileFormatExtensions[pixc->comptype]);
+ l_binaryWrite(buf, "w", pixc->data, pixc->size);
+ return 0;
+}
diff --git a/leptonica/src/pixconv.c b/leptonica/src/pixconv.c
new file mode 100644
index 00000000..2776d6cb
--- /dev/null
+++ b/leptonica/src/pixconv.c
@@ -0,0 +1,4297 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pixconv.c
+ * <pre>
+ *
+ * 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 8 bit gray
+ * PIX *pixConvertRGBToLuminance()
+ * PIX *pixConvertRGBToGrayGeneral()
+ * PIX *pixConvertRGBToGray()
+ * PIX *pixConvertRGBToGrayFast()
+ * PIX *pixConvertRGBToGrayMinMax()
+ * PIX *pixConvertRGBToGraySatBoost()
+ * PIX *pixConvertRGBToGrayArb()
+ * PIX *pixConvertRGBToBinaryArb()
+ *
+ * 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()
+ *
+ * Conversion from colormap to 1 bpp
+ * PIX *pixConvertCmapTo1()
+ *
+ * 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 *pixConvertTo1Adaptive()
+ * PIX *pixConvertTo1()
+ * PIX *pixConvertTo1BySampling()
+ *
+ * Top-level conversion to 2 bpp
+ * PIX *pixConvertTo2()
+ * PIX *pixConvert8To2()
+ *
+ * Top-level conversion to 4 bpp
+ * PIX *pixConvertTo4()
+ * PIX *pixConvert8To4()
+ *
+ * Top-level conversion to 8 bpp
+ * PIX *pixConvertTo8()
+ * PIX *pixConvertTo8BySampling()
+ * PIX *pixConvertTo8Colormap()
+ *
+ * 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()
+ *
+ * Setting neutral point for min/max boost conversion to gray
+ * void l_setNeutralBoostVal()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+/* ------- Set neutral point for min/max boost conversion to gray ------ */
+ /* Call l_setNeutralBoostVal() to change this */
+static l_int32 var_NEUTRAL_BOOST_VAL = 180;
+
+
+#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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixThreshold8()
+ *
+ * \param[in] pixs 8 bpp grayscale
+ * \param[in] d destination depth: 1, 2, 4 or 8
+ * \param[in] nlevels number of levels to be used for colormap
+ * \param[in] cmapflag 1 if makes colormap; 0 otherwise
+ * \return pixd thresholded with standard dest thresholds,
+ * or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixRemoveColormapGeneral()
+ *
+ * \param[in] pixs any depth, with or without colormap
+ * \param[in] type REMOVE_CMAP_TO_BINARY,
+ * REMOVE_CMAP_TO_GRAYSCALE,
+ * REMOVE_CMAP_TO_FULL_COLOR,
+ * REMOVE_CMAP_WITH_ALPHA,
+ * REMOVE_CMAP_BASED_ON_SRC
+ * \param[in] ifnocmap L_CLONE, L_COPY
+ * \return pixd always a new pix; without colormap, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Convenience function that allows choice between returning
+ * a clone or a copy if pixs does not have a colormap.
+ * (2) See pixRemoveColormap().
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixRemoveColormap()
+ *
+ * \param[in] pixs see restrictions below
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, nalloc, count;
+l_int32 opaque, colorfound, blackwhite;
+l_int32 *rmap, *gmap, *bmap, *amap;
+l_uint32 *datas, *lines, *datad, *lined, *lut, *graymap;
+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);
+
+ ncolors = pixcmapGetCount(cmap);
+ nalloc = 1 << d; /* allocate for max size in case of pixel corruption */
+ if (ncolors > nalloc)
+ return (PIX *)ERROR_PTR("too many colors for pixel depth",
+ 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;
+ }
+ }
+
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+ if (type == REMOVE_CMAP_TO_BINARY) {
+ if ((pixd = pixCopy(NULL, pixs)) == NULL) {
+ L_ERROR("pixd not made\n", procName);
+ goto cleanup_arrays;
+ }
+ 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) {
+ L_ERROR("pixd not made\n", procName);
+ goto cleanup_arrays;
+ }
+ pixCopyResolution(pixd, pixs);
+ pixCopyInputFormat(pixd, pixs);
+ datad = pixGetData(pixd);
+ wpld = pixGetWpl(pixd);
+ graymap = (l_uint32 *)LEPT_CALLOC(nalloc, sizeof(l_uint32));
+ for (i = 0; i < ncolors; i++) {
+ graymap[i] = (l_uint32)(L_RED_WEIGHT * rmap[i] +
+ L_GREEN_WEIGHT * gmap[i] +
+ L_BLUE_WEIGHT * bmap[i] + 0.5);
+ }
+ 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) { \
+ lept_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) {
+ L_ERROR("pixd not made\n", procName);
+ goto cleanup_arrays;
+ }
+ pixCopyInputFormat(pixd, pixs);
+ pixCopyResolution(pixd, pixs);
+ if (type == REMOVE_CMAP_WITH_ALPHA)
+ pixSetSpp(pixd, 4);
+ datad = pixGetData(pixd);
+ wpld = pixGetWpl(pixd);
+ lut = (l_uint32 *)LEPT_CALLOC(nalloc, sizeof(l_uint32));
+ 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 /* (d == 1) */
+ sval = GET_DATA_BIT(lines, j);
+ if (sval >= ncolors)
+ L_WARNING("pixel value out of bounds\n", procName);
+ else
+ lined[j] = lut[sval];
+ }
+ }
+ LEPT_FREE(lut);
+ }
+
+cleanup_arrays:
+ LEPT_FREE(rmap);
+ LEPT_FREE(gmap);
+ LEPT_FREE(bmap);
+ LEPT_FREE(amap);
+ return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ * Add colormap losslessly (8 to 8) *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixAddGrayColormap8()
+ *
+ * \param[in] pixs 8 bpp
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If pixs has a colormap, this is a no-op.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixAddMinimalGrayColormap8()
+ *
+ * \param[in] pixs 8 bpp
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixAddMinimalGrayColormap8(PIX *pixs)
+{
+l_int32 ncolors, w, h, i, j, wpl1, wpld, index, val;
+l_int32 *inta, *revmap;
+l_uint32 *data1, *datad, *line1, *lined;
+PIX *pix1, *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
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+ } else {
+ if (ncolors == 256) {
+ pix1 = pixCopy(NULL, pixs);
+ pixAddGrayColormap8(pix1);
+ return pix1;
+ }
+ pix1 = pixClone(pixs);
+ }
+
+ /* Find the gray levels and make a reverse map */
+ pixGetDimensions(pix1, &w, &h, NULL);
+ data1 = pixGetData(pix1);
+ wpl1 = pixGetWpl(pix1);
+ inta = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl1;
+ for (j = 0; j < w; j++) {
+ val = GET_DATA_BYTE(line1, 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(pix1);
+ pixSetColormap(pixd, cmap);
+ pixCopyInputFormat(pixd, pixs);
+ pixCopyResolution(pixd, pixs);
+ datad = pixGetData(pixd);
+ wpld = pixGetWpl(pixd);
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl1;
+ lined = datad + i * wpld;
+ for (j = 0; j < w; j++) {
+ val = GET_DATA_BYTE(line1, j);
+ SET_DATA_BYTE(lined, j, revmap[val]);
+ }
+ }
+
+ pixDestroy(&pix1);
+ LEPT_FREE(inta);
+ LEPT_FREE(revmap);
+ return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ * Conversion from RGB color to grayscale *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixConvertRGBToLuminance()
+ *
+ * \param[in] pixs 32 bpp RGB
+ * \return 8 bpp pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Use a standard luminance conversion.
+ * </pre>
+ */
+PIX *
+pixConvertRGBToLuminance(PIX *pixs)
+{
+ return pixConvertRGBToGray(pixs, 0.0, 0.0, 0.0);
+}
+
+
+/*!
+ * \brief pixConvertRGBToGrayGeneral()
+ *
+ * \param[in] pixs 32 bpp RGB
+ * \param[in] type color selection flag
+ * \param[in] rwt, gwt, bwt ignored if type != L_SELECT_WEIGHTED;
+ * if used, must sum to 1.0.
+ * \return 8 bpp pix, or NULL on error
+ *
+ * <pre>
+ * 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,
+ * L_SELECT_HUE, L_SELECT_SATURATION, L_SELECT_WEIGHTED.
+ * (2) The weights, if used, must all be non-negative and must sum to 1.0.
+ * </pre>
+ */
+PIX *
+pixConvertRGBToGrayGeneral(PIX *pixs,
+ l_int32 type,
+ l_float32 rwt,
+ l_float32 gwt,
+ l_float32 bwt)
+{
+PIX *pix1;
+
+ PROCNAME("pixConvertRGBToGrayGeneral");
+
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", 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 &&
+ type != L_SELECT_HUE && type != L_SELECT_SATURATION &&
+ type != L_SELECT_WEIGHTED)
+ return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+ if (type == L_SELECT_RED) {
+ pix1 = pixGetRGBComponent(pixs, COLOR_RED);
+ } else if (type == L_SELECT_GREEN) {
+ pix1 = pixGetRGBComponent(pixs, COLOR_GREEN);
+ } else if (type == L_SELECT_BLUE) {
+ pix1 = pixGetRGBComponent(pixs, COLOR_BLUE);
+ } else if (type == L_SELECT_MIN) {
+ pix1 = pixConvertRGBToGrayMinMax(pixs, L_CHOOSE_MIN);
+ } else if (type == L_SELECT_MAX) {
+ pix1 = pixConvertRGBToGrayMinMax(pixs, L_CHOOSE_MAX);
+ } else if (type == L_SELECT_AVERAGE) {
+ pix1 = pixConvertRGBToGray(pixs, 0.34f, 0.33f, 0.33f);
+ } else if (type == L_SELECT_HUE) {
+ pix1 = pixConvertRGBToHue(pixs);
+ } else if (type == L_SELECT_SATURATION) {
+ pix1 = pixConvertRGBToSaturation(pixs);
+ } else { /* L_SELECT_WEIGHTED */
+ if (rwt < 0.0 || gwt < 0.0 || bwt < 0.0)
+ return (PIX *)ERROR_PTR("weights not all >= 0.0", procName, NULL);
+ if (rwt + gwt + bwt != 1.0)
+ return (PIX *)ERROR_PTR("weights don't sum to 1.0", procName, NULL);
+ pix1 = pixConvertRGBToGray(pixs, rwt, gwt, bwt);
+ }
+
+ return pix1;
+}
+
+
+/*!
+ * \brief pixConvertRGBToGray()
+ *
+ * \param[in] pixs 32 bpp RGB
+ * \param[in] 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
+ *
+ * <pre>
+ * Notes:
+ * (1) Use a weighted average of the RGB values.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvertRGBToGrayFast()
+ *
+ * \param[in] pixs 32 bpp RGB
+ * \return 8 bpp pix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvertRGBToGrayMinMax()
+ *
+ * \param[in] pixs 32 bpp RGB
+ * \param[in] type L_CHOOSE_MIN, L_CHOOSE_MAX, L_CHOOSE_MAXDIFF,
+ * L_CHOOSE_MIN_BOOST, L_CHOOSE_MAX_BOOST
+ * \return 8 bpp pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This chooses various components or combinations of them,
+ * from the three RGB sample values. In addition to choosing
+ * the min, max, and maxdiff (difference between max and min),
+ * this also allows boosting the min and max about a reference
+ * value.
+ * (2) The default reference value for boosting the min and max
+ * is 200. This can be changed with l_setNeutralBoostVal()
+ * (3) The result with L_CHOOSE_MAXDIFF is surprisingly sensitive
+ * to a jpeg compression/decompression cycle with quality = 75.
+ * </pre>
+ */
+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_MAXDIFF && type != L_CHOOSE_MIN_BOOST &&
+ type != L_CHOOSE_MAX_BOOST)
+ 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 || type == L_CHOOSE_MIN_BOOST) {
+ val = L_MIN(rval, gval);
+ val = L_MIN(val, bval);
+ if (type == L_CHOOSE_MIN_BOOST)
+ val = L_MIN(255, (val * val) / var_NEUTRAL_BOOST_VAL);
+ } else if (type == L_CHOOSE_MAX || type == L_CHOOSE_MAX_BOOST) {
+ val = L_MAX(rval, gval);
+ val = L_MAX(val, bval);
+ if (type == L_CHOOSE_MAX_BOOST)
+ val = L_MIN(255, (val * val) / var_NEUTRAL_BOOST_VAL);
+ } else { /* L_CHOOSE_MAXDIFF */
+ 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;
+}
+
+
+/*!
+ * \brief pixConvertRGBToGraySatBoost()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] refval between 1 and 255; typ. less than 128
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvertRGBToGrayArb()
+ *
+ * \param[in] pixs 32 bpp RGB
+ * \param[in] rc, gc, bc arithmetic factors; can be negative
+ * \return 8 bpp pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This converts to gray using an arbitrary linear combination
+ * of the rgb color components. It differs from pixConvertToGray(),
+ * which uses only positive coefficients that sum to 1.
+ * (2) The gray output values are clipped to 0 and 255.
+ * </pre>
+ */
+PIX *
+pixConvertRGBToGrayArb(PIX *pixs,
+ l_float32 rc,
+ l_float32 gc,
+ l_float32 bc)
+{
+l_int32 i, j, w, h, wpls, wpld, rval, gval, bval, val;
+l_uint32 *datas, *lines, *datad, *lined;
+PIX *pixd;
+
+ PROCNAME("pixConvertRGBToGrayArb");
+
+ 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 (rc <= 0 && gc <= 0 && bc <= 0)
+ return (PIX *)ERROR_PTR("all coefficients <= 0", 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);
+ val = (l_int32)(rc * rval + gc * gval + bc * bval);
+ val = L_MIN(255, L_MAX(0, val));
+ SET_DATA_BYTE(lined, j, val);
+ }
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixConvertRGBToBinaryArb()
+ *
+ * \param[in] pixs 32 bpp RGB
+ * \param[in] rc, gc, bc arithmetic factors; can be negative
+ * \param[in] thresh binarization threshold
+ * \param[in] relation L_SELECT_IF_LT, L_SELECT_IF_GT
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \return 1 bpp pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This makes a 1 bpp mask from an RGB image, using an arbitrary
+ * linear combination of the rgb color components, along with
+ * a threshold and a selection choice of the gray value relative
+ * to %thresh.
+ * </pre>
+ */
+PIX *
+pixConvertRGBToBinaryArb(PIX *pixs,
+ l_float32 rc,
+ l_float32 gc,
+ l_float32 bc,
+ l_int32 thresh,
+ l_int32 relation)
+{
+l_int32 threshold;
+PIX *pix1, *pix2;
+
+ PROCNAME("pixConvertRGBToBinaryArb");
+
+ if (!pixs || pixGetDepth(pixs) != 32)
+ return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+ if (rc <= 0 && gc <= 0 && bc <= 0)
+ return (PIX *)ERROR_PTR("all coefficients <= 0", 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);
+
+ pix1 = pixConvertRGBToGrayArb(pixs, rc, gc, bc);
+ threshold = (relation == L_SELECT_IF_LTE || relation == L_SELECT_IF_GT) ?
+ thresh : thresh + 1;
+ pix2 = pixThresholdToBinary(pix1, threshold);
+ if (relation == L_SELECT_IF_GT || relation == L_SELECT_IF_GTE)
+ pixInvert(pix2, pix2);
+ pixDestroy(&pix1);
+ return pix2;
+}
+
+
+/*---------------------------------------------------------------------------*
+ * Conversion from grayscale to colormap *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertGrayToColormap()
+ *
+ * \param[in] pixs 2, 4 or 8 bpp grayscale
+ * \return pixd 2, 4 or 8 bpp with colormap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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_INFO("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;
+}
+
+
+/*!
+ * \brief pixConvertGrayToColormap8()
+ *
+ * \param[in] pixs 8 bpp grayscale
+ * \param[in] mindepth of pixd; valid values are 2, 4 and 8
+ * \return pixd 2, 4 or 8 bpp with colormap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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_INFO("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++) {
+ array[i] = 0; /* only to quiet the static checker */
+ 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixColorizeGray()
+ *
+ * \param[in] pixs 8 bpp gray; 2, 4 or 8 bpp colormapped
+ * \param[in] color 32 bit rgba pixel
+ * \param[in] cmapflag 1 for result to have colormap; 0 for RGB
+ * \return pixd 8 bpp colormapped or 32 bpp rgb, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertRGBToColormap()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] ditherflag 1 to dither, 0 otherwise
+ * \return pixd 2, 4 or 8 bpp with colormap, or NULL on error
+ *
+ * <pre>
+ * 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) 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).
+ * </pre>
+ */
+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 256 or fewer occupied leaf octcubes, quantize to those octcubes */
+ if (ncolors <= 256) {
+ pixd = pixFewColorsOctcubeQuant2(pixs, 4, na, ncolors, NULL);
+ pixCopyInputFormat(pixd, pixs);
+ numaDestroy(&na);
+ return pixd;
+ }
+
+ /* There are too many occupied leaf octcubes to be represented
+ * directly in a colormap. Fall back to octree quantization,
+ * optionally with dithering. */
+ 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);
+}
+
+
+/*---------------------------------------------------------------------------*
+ * Conversion from colormap to 1 bpp *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertCmapTo1()
+ *
+ * \param[in] pixs cmapped
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is an extreme color quantizer. It decides which
+ * colors map to FG (black) and which to BG (white).
+ * (2) This uses two heuristics to make the decision:
+ * (a) colors similar to each other are likely to be in the same class
+ * (b) there is usually much less FG than BG.
+ * </pre>
+ */
+PIX *
+pixConvertCmapTo1(PIX *pixs)
+{
+l_int32 i, j, nc, w, h, imin, imax, factor, wpl1, wpld;
+l_int32 index, rmin, gmin, bmin, rmax, gmax, bmax, dmin, dmax;
+l_float32 minfract, ifract;
+l_int32 *lut;
+l_uint32 *line1, *lined, *data1, *datad;
+NUMA *na1, *na2; /* histograms */
+PIX *pix1, *pixd;
+PIXCMAP *cmap;
+
+ PROCNAME("pixConvertCmapTo1");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+ if ((cmap = pixGetColormap(pixs)) == NULL)
+ return (PIX *)ERROR_PTR("no colormap", procName, NULL);
+
+ /* Select target colors for the two classes. Find the
+ * colors with smallest and largest average component values.
+ * The smallest is class 0 and the largest is class 1. */
+ pixcmapGetRangeValues(cmap, L_SELECT_AVERAGE, NULL, NULL, &imin, &imax);
+ pixcmapGetColor(cmap, imin, &rmin, &gmin, &bmin);
+ pixcmapGetColor(cmap, imax, &rmax, &gmax, &bmax);
+ nc = pixcmapGetCount(cmap);
+
+ /* Assign colors to the two classes. The histogram is
+ * initialized to 0, so any colors not found when computing
+ * the sampled histogram will get zero weight in minfract. */
+ if ((lut = (l_int32 *)LEPT_CALLOC(nc, sizeof(l_int32))) == NULL)
+ return (PIX *)ERROR_PTR("calloc fail for lut", procName, NULL);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ factor = L_MAX(1, (l_int32)sqrt((l_float64)(w * h) / 50000. + 0.5));
+ na1 = pixGetCmapHistogram(pixs, factor);
+ na2 = numaNormalizeHistogram(na1, 1.0);
+ minfract = 0.0;
+ for (i = 0; i < nc; i++) {
+ numaGetFValue(na2, i, &ifract);
+ pixcmapGetDistanceToColor(cmap, i, rmin, gmin, bmin, &dmin);
+ pixcmapGetDistanceToColor(cmap, i, rmax, gmax, bmax, &dmax);
+ if (dmin < dmax) { /* closer to dark extreme value */
+ lut[i] = 1; /* black pixel in 1 bpp image */
+ minfract += ifract;
+ }
+ }
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+
+ /* Generate the output binarized image */
+ pix1 = pixConvertTo8(pixs, 1);
+ pixd = pixCreate(w, h, 1);
+ data1 = pixGetData(pix1);
+ datad = pixGetData(pixd);
+ wpl1 = pixGetWpl(pix1);
+ wpld = pixGetWpl(pixd);
+ for (i = 0; i < h; i++) {
+ line1 = data1 + i * wpl1;
+ lined = datad + i * wpld;
+ for (j = 0; j < w; j++) {
+ index = GET_DATA_BYTE(line1, j);
+ if (lut[index] == 1) SET_DATA_BIT(lined, j);
+ }
+ }
+ pixDestroy(&pix1);
+ LEPT_FREE(lut);
+
+ /* We expect minfract (the dark colors) to be less than 0.5.
+ * If that is not the case, invert pixd. */
+ if (minfract > 0.5) {
+ L_INFO("minfract = %5.3f; inverting\n", procName, minfract);
+ pixInvert(pixd, pixd);
+ }
+
+ return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ * Quantization for relatively small number of colors in source *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixQuantizeIfFewColors()
+ *
+ * \param[in] pixs 8 bpp gray or 32 bpp rgb
+ * \param[in] maxcolors max number of colors allowed to be returned
+ * from pixColorsForQuantization();
+ * use 0 for default
+ * \param[in] mingraycolors min number of gray levels that a grayscale
+ * image is quantized to; use 0 for default
+ * \param[in] octlevel for octcube quantization: 3 or 4
+ * \param[out] ppixd 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.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvert16To8()
+ *
+ * \param[in] pixs 16 bpp
+ * \param[in] type L_LS_BYTE, L_MS_BYTE, L_AUTO_BYTE, L_CLIP_TO_FF
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) With L_AUTO_BYTE, if the max pixel value is greater than 255,
+ * use the MSB; otherwise, use the LSB.
+ * (2) With L_CLIP_TO_FF, use min(pixel-value, 0xff) for each
+ * 16-bit src pixel.
+ * </pre>
+ */
+PIX *
+pixConvert16To8(PIX *pixs,
+ l_int32 type)
+{
+l_uint16 dword;
+l_int32 w, h, wpls, wpld, i, j, val, use_lsb;
+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_AUTO_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);
+
+ if (type == L_AUTO_BYTE) {
+ use_lsb = TRUE;
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ for (j = 0; j < wpls; j++) {
+ val = GET_DATA_TWO_BYTES(lines, j);
+ if (val > 255) {
+ use_lsb = FALSE;
+ break;
+ }
+ }
+ if (!use_lsb) break;
+ }
+ type = (use_lsb) ? L_LS_BYTE : L_MS_BYTE;
+ }
+
+ /* 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
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertGrayToFalseColor()
+ *
+ * \param[in] pixs 8 or 16 bpp grayscale
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixConvertGrayToFalseColor(PIX *pixs,
+ l_float32 gamma)
+{
+l_int32 d;
+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);
+
+ cmap = pixcmapGrayToFalseColor(gamma);
+ pixSetColormap(pixd, cmap);
+ pixCopyResolution(pixd, pixs);
+ pixCopyInputFormat(pixd, pixs);
+ return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ * Unpacking conversion from 1 bpp to 2, 4, 8, 16 and 32 bpp *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixUnpackBinary()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] depth of destination: 2, 4, 8, 16 or 32 bpp
+ * \param[in] 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
+ *
+ * <pre>
+ * Notes:
+ * (1) This function calls special cases of pixConvert1To*(),
+ * for 2, 4, 8, 16 and 32 bpp destinations.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvert1To16()
+ *
+ * \param[in] pixd [optional] 16 bpp, can be null
+ * \param[in] pixs 1 bpp
+ * \param[in] val0 16 bit value to be used for 0s in pixs
+ * \param[in] val1 16 bit value to be used for 1s in pixs
+ * \return pixd 16 bpp
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+ tab = (l_uint32 *)LEPT_CALLOC(4, sizeof(l_uint32));
+ 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;
+}
+
+
+/*!
+ * \brief pixConvert1To32()
+ *
+ * \param[in] pixd [optional] 32 bpp, can be null
+ * \param[in] pixs 1 bpp
+ * \param[in] val0 32 bit value to be used for 0s in pixs
+ * \param[in] val1 32 bit value to be used for 1s in pixs
+ * \return pixd 32 bpp
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvert1To2Cmap()
+ *
+ * \param[in] pixs 1 bpp
+ * \return pixd 2 bpp, cmapped
+ *
+ * <pre>
+ * Notes:
+ * (1) Input 0 is mapped to (255, 255, 255); 1 is mapped to (0, 0, 0)
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvert1To2()
+ *
+ * \param[in] pixd [optional] 2 bpp, can be null
+ * \param[in] pixs 1 bpp
+ * \param[in] val0 2 bit value to be used for 0s in pixs
+ * \param[in] val1 2 bit value to be used for 1s in pixs
+ * \return pixd 2 bpp
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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 */
+ tab = (l_uint16 *)LEPT_CALLOC(256, sizeof(l_uint16));
+ 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvert1To4Cmap()
+ *
+ * \param[in] pixs 1 bpp
+ * \return pixd 4 bpp, cmapped
+ *
+ * <pre>
+ * Notes:
+ * (1) Input 0 is mapped to (255, 255, 255); 1 is mapped to (0, 0, 0)
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvert1To4()
+ *
+ * \param[in] pixd [optional] 4 bpp, can be null
+ * \param[in] pixs 1 bpp
+ * \param[in] val0 4 bit value to be used for 0s in pixs
+ * \param[in] val1 4 bit value to be used for 1s in pixs
+ * \return pixd 4 bpp
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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 */
+ tab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32));
+ 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvert1To8Cmap()
+ *
+ * \param[in] pixs 1 bpp
+ * \return pixd 8 bpp, cmapped
+ *
+ * <pre>
+ * Notes:
+ * (1) Input 0 is mapped to (255, 255, 255); 1 is mapped to (0, 0, 0)
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvert1To8()
+ *
+ * \param[in] pixd [optional] 8 bpp, can be null
+ * \param[in] pixs 1 bpp
+ * \param[in] val0 8 bit value to be used for 0s in pixs
+ * \param[in] val1 8 bit value to be used for 1s in pixs
+ * \return pixd 8 bpp
+ *
+ * <pre>
+ * 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,
+ * use pixConvert1To8Cmap().
+ * </pre>
+ */
+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);
+ pixSetPadBits(pixs, 0);
+
+ /* Use a table to convert 4 src bits at a time */
+ tab = (l_uint32 *)LEPT_CALLOC(16, sizeof(l_uint32));
+ val[0] = val0;
+ val[1] = val1;
+ for (index = 0; index < 16; index++) {
+ tab[index] = ((l_uint32)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;
+}
+
+
+/*!
+ * \brief pixConvert2To8()
+ *
+ * \param[in] pixs 2 bpp
+ * \param[in] val0 8 bit value to be used for 00 in pixs
+ * \param[in] val1 8 bit value to be used for 01 in pixs
+ * \param[in] val2 8 bit value to be used for 10 in pixs
+ * \param[in] val3 8 bit value to be used for 11 in pixs
+ * \param[in] cmapflag TRUE if pixd is to have a colormap; FALSE otherwise
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, byte;
+l_uint32 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);
+ pixSetPadBits(pixs, 0);
+ 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 */
+ if (cmaps) { /* use the existing colormap from pixs */
+ cmapd = pixcmapConvertTo8(cmaps);
+ } else { /* make a colormap from the input values */
+ cmapd = pixcmapCreate(8);
+ 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 */
+ tab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32));
+ 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;
+}
+
+
+/*!
+ * \brief pixConvert4To8()
+ *
+ * \param[in] pixs 4 bpp
+ * \param[in] cmapflag TRUE if pixd is to have a colormap; FALSE otherwise
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixConvert4To8(PIX *pixs,
+ l_int32 cmapflag)
+{
+l_int32 w, h, i, j, wpls, wpld, 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 */
+ if (cmaps) { /* use the existing colormap from pixs */
+ cmapd = pixcmapConvertTo8(cmaps);
+ } else { /* make a colormap with a linear trc */
+ cmapd = pixcmapCreate(8);
+ 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvert8To16()
+ *
+ * \param[in] pixs 8 bpp; colormap removed to gray
+ * \param[in] leftshift number of bits: 0 is no shift;
+ * 8 replicates in MSB and LSB of dest
+ * \return pixd 16 bpp, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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 2 bpp *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertTo2()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 24, 32 bpp; colormap OK but will be removed
+ * \return pixd 2 bpp, or NULL on error
+ *
+ * <pre>
+ * 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; the result is always gray.
+ * (3) If the input image has 2 bpp and no colormap, the operation is
+ * lossless and a copy is returned.
+ * </pre>
+ */
+PIX *
+pixConvertTo2(PIX *pixs)
+{
+l_int32 d;
+PIX *pix1, *pix2, *pix3, *pixd;
+
+ PROCNAME("pixConvertTo2");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+ d = pixGetDepth(pixs);
+ if (d != 1 && d != 2 && d != 4 && d != 8 && d != 24 && d != 32)
+ return (PIX *)ERROR_PTR("depth not {1,2,4,8,24,32}", procName, NULL);
+
+ if (pixGetColormap(pixs) != NULL) {
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+ d = pixGetDepth(pix1);
+ } else {
+ pix1 = pixCopy(NULL, pixs);
+ }
+ if (d == 24 || d == 32)
+ pix2 = pixConvertTo8(pix1, FALSE);
+ else
+ pix2 = pixClone(pix1);
+ pixDestroy(&pix1);
+ if (d == 1) {
+ pixd = pixConvert1To2(NULL, pix2, 3, 0);
+ } else if (d == 2) {
+ pixd = pixClone(pix2);
+ } else if (d == 4) {
+ pix3 = pixConvert4To8(pix2, FALSE); /* unpack to 8 */
+ pixd = pixConvert8To2(pix3);
+ pixDestroy(&pix3);
+ } else { /* d == 8 */
+ pixd = pixConvert8To2(pix2);
+ }
+ pixDestroy(&pix2);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixConvert8To2()
+ *
+ * \param[in] pix 8 bpp; colormap OK
+ * \return pixd 2 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Any existing colormap is removed to gray.
+ * </pre>
+ */
+PIX *
+pixConvert8To2(PIX *pix)
+{
+l_int32 i, j, w, h, wpls, wpld;
+l_uint32 word;
+l_uint32 *datas, *lines, *datad, *lined;
+PIX *pixs, *pixd;
+
+ PROCNAME("pixConvert8To2");
+
+ if (!pix || pixGetDepth(pix) != 8)
+ return (PIX *)ERROR_PTR("pix undefined or not 8 bpp", procName, NULL);
+
+ if (pixGetColormap(pix) != NULL)
+ pixs = pixRemoveColormap(pix, REMOVE_CMAP_TO_GRAYSCALE);
+ else
+ pixs = pixClone(pix);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+ pixd = pixCreate(w, h, 2);
+ datad = pixGetData(pixd);
+ wpld = pixGetWpl(pixd);
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ lined = datad + i * wpld;
+ for (j = 0; j < wpls; j++) { /* march through 4 pixels at a time */
+ word = lines[j] & 0xc0c0c0c0; /* top 2 bits of each byte */
+ word = (word >> 24) | ((word & 0xff0000) >> 18) |
+ ((word & 0xff00) >> 12) | ((word & 0xff) >> 6);
+ SET_DATA_BYTE(lined, j, word); /* only LS byte is filled */
+ }
+ }
+ pixDestroy(&pixs);
+ return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ * Top-level conversion to 4 bpp *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertTo4()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 24, 32 bpp; colormap OK but will be removed
+ * \return pixd 4 bpp, or NULL on error
+ *
+ * <pre>
+ * 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; the result is always gray.
+ * (3) If the input image has 4 bpp and no colormap, the operation is
+ * lossless and a copy is returned.
+ * </pre>
+ */
+PIX *
+pixConvertTo4(PIX *pixs)
+{
+l_int32 d;
+PIX *pix1, *pix2, *pix3, *pixd;
+
+ PROCNAME("pixConvertTo4");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+ d = pixGetDepth(pixs);
+ if (d != 1 && d != 2 && d != 4 && d != 8 && d != 24 && d != 32)
+ return (PIX *)ERROR_PTR("depth not {1,2,4,8,24,32}", procName, NULL);
+
+ if (pixGetColormap(pixs) != NULL) {
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+ d = pixGetDepth(pix1);
+ } else {
+ pix1 = pixCopy(NULL, pixs);
+ }
+ if (d == 24 || d == 32)
+ pix2 = pixConvertTo8(pix1, FALSE);
+ else
+ pix2 = pixClone(pix1);
+ pixDestroy(&pix1);
+ if (d == 1) {
+ pixd = pixConvert1To4(NULL, pix2, 15, 0);
+ } else if (d == 2) {
+ pix3 = pixConvert2To8(pix2, 0, 0x55, 0xaa, 0xff, FALSE);
+ pixd = pixConvert8To4(pix3);
+ pixDestroy(&pix3);
+ } else if (d == 4) {
+ pixd = pixClone(pix2);
+ } else { /* d == 8 */
+ pixd = pixConvert8To4(pix2);
+ }
+ pixDestroy(&pix2);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixConvert8To4()
+ *
+ * \param[in] pix 8 bpp; colormap OK
+ * \return pixd 4 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Any existing colormap is removed to gray.
+ * </pre>
+ */
+PIX *
+pixConvert8To4(PIX *pix)
+{
+l_int32 i, j, w, h, wpls, wpld, val;
+l_uint32 *datas, *lines, *datad, *lined;
+PIX *pixs, *pixd;
+
+ PROCNAME("pixConvert8To4");
+
+ if (!pix || pixGetDepth(pix) != 8)
+ return (PIX *)ERROR_PTR("pix undefined or not 8 bpp", procName, NULL);
+
+ if (pixGetColormap(pix) != NULL)
+ pixs = pixRemoveColormap(pix, REMOVE_CMAP_TO_GRAYSCALE);
+ else
+ pixs = pixClone(pix);
+ pixGetDimensions(pixs, &w, &h, NULL);
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+ pixd = pixCreate(w, h, 4);
+ 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);
+ val = val >> 4; /* take top 4 bits */
+ SET_DATA_QBIT(lined, j, val);
+ }
+ }
+ pixDestroy(&pixs);
+ return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ * Top-level conversion to 1 bpp *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertTo1Adaptive()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 24 or 32 bpp
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a top-level function, that uses default values for
+ * adaptive thresholding, if necessary. Otherwise, it is the same as
+ * pixConvertTo1(), which uses a global threshold for binarization.
+ * </pre>
+ */
+PIX *
+pixConvertTo1Adaptive(PIX *pixs)
+{
+l_int32 d, color0, color1, rval, gval, bval;
+PIX *pix1, *pix2, *pixd;
+PIXCMAP *cmap;
+
+ PROCNAME("pixConvertTo1Adaptive");
+
+ 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 != 24 && d != 32)
+ return (PIX *)ERROR_PTR("depth not {1,2,4,8,16,24,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 */
+ pix1 = pixConvertTo8(pixs, FALSE);
+ pix2 = pixBackgroundNormSimple(pix1, NULL, NULL);
+ pixd = pixThresholdToBinary(pix2, 180);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixConvertTo1()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 24 or 32 bpp
+ * \param[in] threshold for final binarization, relative to 8 bpp
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 != 24 && d != 32)
+ return (PIX *)ERROR_PTR("depth not {1,2,4,8,16,24,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;
+}
+
+
+/*!
+ * \brief pixConvertTo1BySampling()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 24 or 32 bpp
+ * \param[in] factor subsampling factor; integer >= 1
+ * \param[in] threshold for final binarization, relative to 8 bpp
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a quick and dirty, top-level converter.
+ * (2) See pixConvertTo1() for default values.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertTo8()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 24 or 32 bpp
+ * \param[in] cmapflag TRUE if pixd is to have a colormap; FALSE otherwise
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * 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 24 bpp or 32 bpp RGB, this is converted to gray.
+ * For color quantization, you must specify the type explicitly,
+ * using the color quantization code.
+ * </pre>
+ */
+PIX *
+pixConvertTo8(PIX *pixs,
+ l_int32 cmapflag)
+{
+l_int32 d;
+PIX *pix1, *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 != 24 && d != 32)
+ return (PIX *)ERROR_PTR("depth not {1,2,4,8,16,24,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 if (d == 24) {
+ pix1 = pixConvert24To32(pixs);
+ pixd = pixConvertRGBToLuminance(pix1);
+ if (cmapflag)
+ pixAddGrayColormap8(pixd);
+ pixDestroy(&pix1);
+ return pixd;
+ } else { /* d == 32 */
+ pixd = pixConvertRGBToLuminance(pixs);
+ if (cmapflag)
+ pixAddGrayColormap8(pixd);
+ return pixd;
+ }
+}
+
+
+/*!
+ * \brief pixConvertTo8BySampling()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16 or 32 bpp
+ * \param[in] factor submsampling factor; integer >= 1
+ * \param[in] cmapflag TRUE if pixd is to have a colormap; FALSE otherwise
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a fast, quick/dirty, top-level converter.
+ * (2) See pixConvertTo8() for default values.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvertTo8Colormap()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16 or 32 bpp
+ * \param[in] dither 1 to dither if necessary; 0 otherwise
+ * \return pixd 8 bpp, cmapped, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixConvertTo8Colormap(PIX *pixs,
+ l_int32 dither)
+{
+l_int32 d;
+
+ PROCNAME("pixConvertTo8Colormap");
+
+ 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertTo16()
+ *
+ * \param[in] 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertTo32()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 24 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
+ *
+ * <pre>
+ * Notes:
+ * (1) Never returns a clone of pixs.
+ * </pre>
+ */
+PIX *
+pixConvertTo32(PIX *pixs)
+{
+l_int32 d;
+PIX *pix1, *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) {
+ pix1 = pixConvert2To8(pixs, 0, 85, 170, 255, TRUE);
+ pixd = pixConvert8To32(pix1);
+ pixDestroy(&pix1);
+ return pixd;
+ } else if (d == 4) {
+ pix1 = pixConvert4To8(pixs, TRUE);
+ pixd = pixConvert8To32(pix1);
+ pixDestroy(&pix1);
+ return pixd;
+ } else if (d == 8) {
+ return pixConvert8To32(pixs);
+ } else if (d == 16) {
+ pix1 = pixConvert16To8(pixs, L_MS_BYTE);
+ pixd = pixConvert8To32(pix1);
+ pixDestroy(&pix1);
+ 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);
+ }
+}
+
+
+/*!
+ * \brief pixConvertTo32BySampling()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 24 or 32 bpp
+ * \param[in] factor submsampling factor; integer >= 1
+ * \return pixd 32 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a fast, quick/dirty, top-level converter.
+ * (2) See pixConvertTo32() for default values.
+ * </pre>
+ */
+PIX *
+pixConvertTo32BySampling(PIX *pixs,
+ l_int32 factor)
+{
+l_float32 scalefactor;
+PIX *pix1, *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;
+ pix1 = pixScaleBySampling(pixs, scalefactor, scalefactor);
+ pixd = pixConvertTo32(pix1);
+
+ pixDestroy(&pix1);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixConvert8To32()
+ *
+ * \param[in] pixs 8 bpp
+ * \return 32 bpp rgb pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If there is no colormap, replicates the gray value
+ * into the 3 MSB of the dest pixel.
+ * </pre>
+ */
+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);
+
+ 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);
+
+ /* Replication table gray --> rgb */
+ tab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32));
+ for (i = 0; i < 256; i++)
+ tab[i] = ((l_uint32)i << 24) | (i << 16) | (i << 8);
+
+ /* Replicate 1 --> 4 bytes (alpha byte not set) */
+ 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertTo8Or32()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, with or without colormap;
+ * or 32 bpp rgb
+ * \param[in] copyflag L_CLONE or L_COPY
+ * \param[in] warnflag 1 to issue warning if colormap is removed; else 0
+ * \return pixd 8 bpp grayscale or 32 bpp rgb, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (copyflag != L_CLONE && copyflag != L_COPY)
+ return (PIX *)ERROR_PTR("invalid copyflag", 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 == L_CLONE)
+ pixd = pixClone(pixs);
+ else /* copyflag == L_COPY */
+ 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvert24To32()
+ *
+ * \param[in] pixs 24 bpp rgb
+ * \return pixd 32 bpp rgb, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvert32To24()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \return pixd 24 bpp rgb, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixconvert24To32().
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvert32To16()
+ *
+ * \param[in] pixs 32 bpp, single component
+ * \param[in] type L_LS_TWO_BYTES, L_MS_TWO_BYTES, L_CLIP_TO_FFFF
+ * \return pixd 16 bpp , or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The data in pixs is typically used for labelling.
+ * It is an array of l_uint32 values, not rgb or rgba.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvert32To8()
+ *
+ * \param[in] pixs 32 bpp, single component
+ * \param[in] type16 L_LS_TWO_BYTES, L_MS_TWO_BYTES, L_CLIP_TO_FFFF
+ * \param[in] 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixRemoveAlpha()
+ *
+ * \param[in] pixs any depth
+ * \return pixd if 32 bpp rgba, pixs blended over a white background;
+ * a clone of pixs otherwise, and NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a wrapper on pixAlphaBlendUniform()
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixAddAlphaTo1bpp()
+ *
+ * \param[in] pixd [optional] 1 bpp, can be null or equal to pixs
+ * \param[in] pixs 1 bpp
+ * \return pixd 1 bpp with colormap and non-opaque alpha,
+ * or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertLossless()
+ *
+ * \param[in] pixs 1, 2, 4, 8 bpp, not cmapped
+ * \param[in] d destination depth: 2, 4 or 8
+ * \return pixd 2, 4 or 8 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ }
+ break;
+ 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertForPSWrap()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp
+ * \return pixd 1, 8, or 32 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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:
+ lept_stderr("depth not in {1, 2, 4, 8, 16, 32}");
+ return NULL;
+ }
+
+ return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ * Scaling conversion to subpixel RGB *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixConvertToSubpixelRGB()
+ *
+ * \param[in] pixs 8 bpp grayscale, 32 bpp rgb, or colormapped
+ * \param[in] scalex, scaley anisotropic scaling permitted between
+ * source and destination
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixConvertToSubpixelRGB(PIX *pixs,
+ l_float32 scalex,
+ l_float32 scaley,
+ l_int32 order)
+{
+l_int32 d;
+PIX *pix1, *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 ((pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC)) == NULL)
+ return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+
+ d = pixGetDepth(pix1);
+ pixd = NULL;
+ if (d == 8)
+ pixd = pixConvertGrayToSubpixelRGB(pix1, scalex, scaley, order);
+ else if (d == 32)
+ pixd = pixConvertColorToSubpixelRGB(pix1, scalex, scaley, order);
+ else
+ L_ERROR("invalid depth %d\n", procName, d);
+
+ pixDestroy(&pix1);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixConvertGrayToSubpixelRGB()
+ *
+ * \param[in] pixs 8 bpp or colormapped
+ * \param[in] scalex, scaley
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixConvertColorToSubpixelRGB()
+ *
+ * \param[in] pixs 32 bpp or colormapped
+ * \param[in] scalex, scaley
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Setting neutral point for min/max boost conversion to gray *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_setNeutralBoostVal()
+ *
+ * \param[in] val between 1 and 255; typical value is 180
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) This raises or lowers the selected min or max RGB component value,
+ * depending on if that component is above or below this value.
+ * </pre>
+ */
+void
+l_setNeutralBoostVal(l_int32 val)
+{
+ PROCNAME("l_setNeutralBoostVal");
+
+ if (val <= 0) {
+ L_ERROR("invalid reference value for neutral boost\n", procName);
+ return;
+ }
+ var_NEUTRAL_BOOST_VAL = val;
+}
diff --git a/leptonica/src/pixlabel.c b/leptonica/src/pixlabel.c
new file mode 100644
index 00000000..576ea55a
--- /dev/null
+++ b/leptonica/src/pixlabel.c
@@ -0,0 +1,637 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pixlabel.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+/*-----------------------------------------------------------------------*
+ * Label pixels by an index for connected component membership *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixConnCompTransform()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] connect connectivity: 4 or 8
+ * \param[in] depth of pixd: 8 or 16 bpp; use 0 for auto determination
+ * \return pixd 8, 16 or 32 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixConnCompAreaTransform()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] connect connectivity: 4 or 8
+ * \return pixd 32 bpp, 1 spp, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief pixConnCompIncrInit()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] conn connectivity: 4 or 8
+ * \param[out] ppixd 32 bpp, with c.c. labelled
+ * \param[out] pptaa with pixel locations indexed by c.c.
+ * \param[out] pncc initial number of c.c.
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 labeled 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 = ptaaIndexLabeledPixels(pixd, &ncc)) == NULL)
+ return ERROR_INT("ptaa not made", procName, 1);
+ *pptaa = ptaa;
+ *pncc = ncc;
+ return 0;
+}
+
+
+/*!
+ * \brief pixConnCompIncrAdd()
+ *
+ * \param[in] pixs 32 bpp, with pixels labeled by c.c.
+ * \param[in] ptaa with each pta of pixel locations indexed by c.c.
+ * \param[out] pncc number of c.c
+ * \param[in] x,y location of added pixel
+ * \param[in] 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
+ *
+ * <pre>
+ * Notes:
+ * (1) This adds a pixel and updates the labeled 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
+ * relabeled 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.
+ * </pre>
+ */
+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;
+ LEPT_FREE(neigh);
+ 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)
+ lept_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) {
+ lept_stderr("nvals = %d: neigh = (%d,%d)\n", nvals,
+ neigh[0], neigh[1]);
+ }
+ } else if (nvals == 3) {
+ if (debug >= 1 && debug <= 3) {
+ lept_stderr("nvals = %d: neigh = (%d,%d,%d)\n", nvals,
+ neigh[0], neigh[1], neigh[2]);
+ }
+ } else { /* nvals == 4 */
+ if (debug >= 1 && debug <= 4) {
+ lept_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 relabeled pixel locations */
+ *pncc -= 1;
+ ptaDestroy(&ptaa->pta[neigh[i]]);
+ ptaDestroy(&ptas); /* the clone */
+ }
+ ptaDestroy(&ptad); /* the clone */
+ LEPT_FREE(neigh);
+ return 0;
+}
+
+
+/*!
+ * \brief pixGetSortedNeighborValues()
+ *
+ * \param[in] pixs 8, 16 or 32 bpp, with pixels labeled by c.c.
+ * \param[in] x, y location of pixel
+ * \param[in] conn 4 or 8 connected neighbors
+ * \param[out] pneigh array of integers, to be filled with
+ * the values of the neighbors, if any
+ * \param[out] pnvals the number of unique neighbor values found
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixLocToColorTransform()
+ *
+ * \param[in] pixs 1 bpp
+ * \return pixd 32 bpp rgb, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/pixtiling.c b/leptonica/src/pixtiling.c
new file mode 100644
index 00000000..fe6b67e8
--- /dev/null
+++ b/leptonica/src/pixtiling.c
@@ -0,0 +1,422 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pixtiling.c
+ * <pre>
+ *
+ * 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 = pixCreateTemplate(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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*!
+ * \brief pixTilingCreate()
+ *
+ * \param[in] pixs pix to be tiled; any depth; colormap OK
+ * \param[in] nx number of tiles across image
+ * \param[in] ny number of tiles down image
+ * \param[in] w desired width of each tile
+ * \param[in] h desired height of each tile
+ * \param[in] xoverlap overlap into neighboring tiles on each side
+ * \param[in] yoverlap overlap into neighboring tiles above and below
+ * \return pixtiling, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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);
+ }
+
+ pt = (PIXTILING *)LEPT_CALLOC(1, sizeof(PIXTILING));
+ 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;
+}
+
+
+/*!
+ * \brief pixTilingDestroy()
+ *
+ * \param[in,out] ppt 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;
+}
+
+
+/*!
+ * \brief pixTilingGetCount()
+ *
+ * \param[in] pt pixtiling
+ * \param[out] pnx [optional] nx; can be null
+ * \param[out] pny [optional] ny; can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixTilingGetSize()
+ *
+ * \param[in] pt pixtiling
+ * \param[out] pw [optional] tile width; can be null
+ * \param[out] ph [optional] tile height; can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixTilingGetTile()
+ *
+ * \param[in] pt pixtiling
+ * \param[in] i tile row index
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixTilingNoStripOnPaint()
+ *
+ * \param[in] pt pixtiling
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+pixTilingNoStripOnPaint(PIXTILING *pt)
+{
+ PROCNAME("pixTilingNoStripOnPaint");
+
+ if (!pt)
+ return ERROR_INT("pt not defined", procName, 1);
+ pt->strip = FALSE;
+ return 0;
+}
+
+
+/*!
+ * \brief pixTilingPaintTile()
+ *
+ * \param[in] pixd dest: paint tile onto this, without overlap
+ * \param[in] i tile row index
+ * \param[in] j tile column index
+ * \param[in] pixs source: tile to be painted from
+ * \param[in] pt pixtiling struct
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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/leptonica/src/pngio.c b/leptonica/src/pngio.c
new file mode 100644
index 00000000..e18aed13
--- /dev/null
+++ b/leptonica/src/pngio.c
@@ -0,0 +1,2135 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ - Copyright (C) 2017 Milner Technologies, Inc.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pngio.c
+ * <pre>
+ *
+ * Reading png through stream
+ * PIX *pixReadStreamPng()
+ *
+ * Reading png header
+ * l_int32 readHeaderPng()
+ * l_int32 freadHeaderPng()
+ * l_int32 readHeaderMemPng()
+ *
+ * Reading png metadata
+ * l_int32 fgetPngResolution()
+ * l_int32 isPngInterlaced()
+ * l_int32 fgetPngColormapInfo()
+ *
+ * Writing png through stream
+ * l_int32 pixWritePng() [ special top level ]
+ * l_int32 pixWriteStreamPng()
+ * l_int32 pixSetZlibCompression()
+ *
+ * Set flag for special read mode
+ * void l_pngSetReadStrip16To8()
+ *
+ * Low-level memio utility (thanks to T. D. Hintz)
+ * static void memio_png_write_data()
+ * static void memio_png_flush()
+ * static void memio_png_read_data()
+ * static void memio_free()
+ *
+ * Reading png from memory
+ * PIX *pixReadMemPng()
+ *
+ * Writing png to memory
+ * 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.
+ *
+ * Thanks to a memory buffering utility contributed by T. D. Hintz,
+ * encoding png directly into memory (and decoding from memory)
+ * is now enabled without the use of any temp files. Unlike with webp,
+ * it is necessary to preserve the stream interface to enable writing
+ * pixa to memory. So there are two independent but very similar
+ * implementations of png reading and writing.
+ * </pre>
+ */
+
+#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 through stream *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixReadStreamPng()
+ *
+ * \param[in] fp file stream
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+PIX *
+pixReadStreamPng(FILE *fp)
+{
+l_uint8 byte;
+l_int32 i, j, k, index, ncolors, bitval, rval, gval, bval, valid;
+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, 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, *pix1;
+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.
+ * - Do not use PNG_TRANSFORM_EXPAND, which would
+ * expand all images with bpp < 8 to 8 bpp.
+ * - Strip 16 --> 8 if reading 16-bit gray+alpha
+ * ---------------------------------------------------------- */
+ /* 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 != 1 && bit_depth != 8) {
+ L_ERROR("spp = %d and bps = %d != 8\n"
+ "turn on 16 --> 8 stripping\n", procName, spp, bit_depth);
+ png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
+ return (PIX *)ERROR_PTR("not implemented for this image",
+ procName, NULL);
+ }
+
+ cmap = 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);
+ }
+ }
+
+ if ((pix = pixCreate(w, h, d)) == NULL) {
+ pixcmapDestroy(&cmap);
+ 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);
+ pixSetSpp(pix, spp);
+ if (pixSetColormap(pix, cmap)) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("invalid colormap", procName, NULL);
+ }
+
+ 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 == 3) /* set to opaque; some readers are buggy */
+ SET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL, 255);
+ else /* 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
+ lept_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) {
+ lept_stderr("(r,g,b,a) = (%d,%d,%d,%d)\n",
+ rval, gval, bval, trans[i]);
+ } else {
+ lept_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++) {
+ lept_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 {
+ L_INFO("removing opaque cmap from 1 bpp\n", procName);
+ pix1 = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+ pixDestroy(&pix);
+ pix = pix1;
+ }
+ }
+
+ 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);
+
+ /* Final validity check on the colormap */
+ if ((cmap = pixGetColormap(pix)) != NULL) {
+ pixcmapIsValid(cmap, pix, &valid);
+ if (!valid) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("colormap is not valid", procName, NULL);
+ }
+ }
+
+ pixSetPadBits(pix, 0);
+ return pix;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Reading png header *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief readHeaderPng()
+ *
+ * \param[in] filename
+ * \param[out] pw [optional]
+ * \param[out] ph [optional]
+ * \param[out] pbps [optional] bits/sample
+ * \param[out] pspp [optional] samples/pixel
+ * \param[out] piscmap [optional]
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief freadHeaderPng()
+ *
+ * \param[in] fp file stream
+ * \param[out] pw [optional]
+ * \param[out] ph [optional]
+ * \param[out] pbps [optional] bits/sample
+ * \param[out] pspp [optional] samples/pixel
+ * \param[out] piscmap [optional]
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See readHeaderPng(). We only need the first 40 bytes in the file.
+ * </pre>
+ */
+l_ok
+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[40];
+
+ 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 (fread(data, 1, 40, fp) != 40)
+ return ERROR_INT("error reading data", procName, 1);
+ ret = readHeaderMemPng(data, 40, pw, ph, pbps, pspp, piscmap);
+ return ret;
+}
+
+
+/*!
+ * \brief readHeaderMemPng()
+ *
+ * \param[in] data
+ * \param[in] size 40 bytes is sufficient
+ * \param[out] pw [optional]
+ * \param[out] ph [optional]
+ * \param[out] pbps [optional] bits/sample
+ * \param[out] pspp [optional] samples/pixel
+ * \param[out] piscmap [optional] input NULL to ignore
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, w, h, 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;
+ w = convertOnLittleEnd32(pword[4]);
+ h = convertOnLittleEnd32(pword[5]);
+ if (w < 1 || h < 1)
+ return ERROR_INT("invalid w or h", procName, 1);
+ 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 (bps < 1 || bps > 16) {
+ L_ERROR("invalid bps = %d\n", procName, bps);
+ return 1;
+ }
+ if (pw) *pw = w;
+ if (ph) *ph = h;
+ if (pbps) *pbps = bps;
+ if (pspp) *pspp = spp;
+ if (piscmap) {
+ if (colortype & 1) /* palette */
+ *piscmap = 1;
+ else
+ *piscmap = 0;
+ }
+
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Reading png metadata *
+ *---------------------------------------------------------------------*/
+/*
+ * fgetPngResolution()
+ *
+ * Input: fp (file 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_info(png_ptr, info_ptr);
+
+ 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;
+}
+
+
+/*!
+ * \brief isPngInterlaced()
+ *
+ * \param[in] filename
+ * \param[out] pinterlaced 1 if interlaced png; 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ fclose(fp);
+ return ERROR_INT("data not read", procName, 1);
+ }
+ fclose(fp);
+
+ *pinterlaced = (buf[28] == 0) ? 0 : 1;
+ return 0;
+}
+
+
+/*
+ * \brief fgetPngColormapInfo()
+ *
+ * \param[in] fp file stream opened for read
+ * \param[out] pcmap optional; use NULL to skip
+ * \param[out] ptransparency optional; 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_ok
+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_info(png_ptr, info_ptr);
+ 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 through stream *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixWritePng()
+ *
+ * \param[in] filename
+ * \param[in] pix
+ * \param[in] gamma
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Special version for writing png with a specified gamma.
+ * When using pixWrite(), no field is given for gamma.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixWriteStreamPng()
+ *
+ * \param[in] fp file stream
+ * \param[in] pix
+ * \param[in] gamma use 0.0 if gamma is not defined
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+pixWriteStreamPng(FILE *fp,
+ PIX *pix,
+ l_float32 gamma)
+{
+char commentstring[] = "Comment";
+l_int32 i, j, k, wpl, d, spp, cmflag, opaque, ncolors, compval, valid;
+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 *pix1;
+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);
+
+ w = pixGetWidth(pix);
+ h = pixGetHeight(pix);
+ d = pixGetDepth(pix);
+ spp = pixGetSpp(pix);
+
+ /* A cmap validity check should prevent low-level colormap errors. */
+ if ((cmap = pixGetColormap(pix))) {
+ cmflag = 1;
+ pixcmapIsValid(cmap, pix, &valid);
+ if (!valid)
+ return ERROR_INT("colormap is not valid", procName, 1);
+ } else {
+ cmflag = 0;
+ }
+ pixSetPadBits(pix, 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
+ lept_stderr("cmflag = %d, bit_depth = %d, color_type = %d\n",
+ cmflag, bit_depth, color_type);
+#endif /* DEBUG_WRITE */
+
+ /* Allocate the 2 png 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 */
+ pix1 = NULL;
+ row_pointers = NULL;
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ LEPT_FREE(row_pointers);
+ pixDestroy(&pix1);
+ 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);
+
+ 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) {
+ /* Make and save the palette */
+ ncolors = pixcmapGetCount(cmap);
+ palette = (png_colorp)LEPT_CALLOC(ncolors, sizeof(png_color));
+ pixcmapToArrays(cmap, &rmap, &gmap, &bmap, &amap);
+ 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];
+ }
+ LEPT_FREE(rmap);
+ LEPT_FREE(gmap);
+ LEPT_FREE(bmap);
+ LEPT_FREE(amap);
+ png_set_PLTE(png_ptr, info_ptr, palette, (int)ncolors);
+ LEPT_FREE(palette);
+
+ pixcmapIsOpaque(cmap, &opaque);
+ if (!opaque) /* alpha channel has some transparency; assume valid */
+ png_set_tRNS(png_ptr, info_ptr, (png_bytep)alpha,
+ (int)ncolors, NULL);
+ }
+
+ /* 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) {
+ pix1 = pixInvert(NULL, pix);
+ pixEndianByteSwap(pix1);
+ } else {
+ pix1 = pixEndianByteSwapNew(pix);
+ }
+ if (!pix1) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ return ERROR_INT("pix1 not made", procName, 1);
+ }
+
+ /* Make and assign array of image row pointers */
+ row_pointers = (png_bytep *)LEPT_CALLOC(h, sizeof(png_bytep));
+ wpl = pixGetWpl(pix1);
+ data = pixGetData(pix1);
+ 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);
+ LEPT_FREE(row_pointers);
+ pixDestroy(&pix1);
+ 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 */
+ rowbuffer = (png_bytep)LEPT_CALLOC(w, 4);
+ 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);
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ return 0;
+}
+
+
+/*!
+ * \brief pixSetZlibCompression()
+ *
+ * \param[in] pix
+ * \param[in] compval zlib compression value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Set flag for stripping 16 bits on reading *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_pngSetReadStrip16To8()
+ *
+ * \param[in] 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;
+}
+
+
+/*-------------------------------------------------------------------------*
+ * Memio utility *
+ * libpng read/write callback replacements for performing memory I/O *
+ * *
+ * Copyright (C) 2017 Milner Technologies, Inc. This content is a *
+ * component of leptonica and is provided under the terms of the *
+ * Leptonica license. *
+ *-------------------------------------------------------------------------*/
+
+ /*! A node in a linked list of memory buffers that hold I/O content */
+struct MemIOData
+{
+ char* m_Buffer; /*!< pointer to this node's I/O content */
+ l_int32 m_Count; /*!< number of I/O content bytes read or written */
+ l_int32 m_Size; /*!< allocated size of m_buffer */
+ struct MemIOData *m_Next; /*!< pointer to the next node in the list; */
+ /*!< zero if this is the last node */
+ struct MemIOData *m_Last; /*!< pointer to the last node in the linked */
+ /*!< list. The last node is where new */
+ /*!< content is written. */
+};
+typedef struct MemIOData MEMIODATA;
+
+static void memio_png_write_data(png_structp png_ptr, png_bytep data,
+ png_size_t length);
+static void memio_png_flush(MEMIODATA* pthing);
+static void memio_png_read_data(png_structp png_ptr, png_bytep outBytes,
+ png_size_t byteCountToRead);
+static void memio_free(MEMIODATA* pthing);
+
+static const l_int32 MEMIO_BUFFER_SIZE = 8192; /*! buffer alloc size */
+
+/*
+ * \brief memio_png_write_data()
+ *
+ * \param[in] png_ptr
+ * \param[in] data
+ * \param[in] len size of array data in bytes
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a libpng callback for writing an image into a
+ * linked list of memory buffers.
+ * </pre>
+ */
+static void
+memio_png_write_data(png_structp png_ptr,
+ png_bytep data,
+ png_size_t len)
+{
+MEMIODATA *thing, *last;
+l_int32 written = 0;
+l_int32 remainingSpace, remainingToWrite;
+
+ thing = (struct MemIOData*)png_get_io_ptr(png_ptr);
+ last = (struct MemIOData*)thing->m_Last;
+ if (last->m_Buffer == NULL) {
+ if (len > MEMIO_BUFFER_SIZE) {
+ last->m_Buffer = (char *)LEPT_MALLOC(len);
+ memcpy(last->m_Buffer, data, len);
+ last->m_Size = last->m_Count = len;
+ return;
+ }
+
+ last->m_Buffer = (char *)LEPT_MALLOC(MEMIO_BUFFER_SIZE);
+ last->m_Size = MEMIO_BUFFER_SIZE;
+ }
+
+ while (written < len) {
+ if (last->m_Count == last->m_Size) {
+ MEMIODATA* next = (MEMIODATA *)LEPT_MALLOC(sizeof(MEMIODATA));
+ next->m_Next = NULL;
+ next->m_Count = 0;
+ next->m_Last = next;
+
+ last->m_Next = next;
+ last = thing->m_Last = next;
+
+ last->m_Buffer = (char *)LEPT_MALLOC(MEMIO_BUFFER_SIZE);
+ last->m_Size = MEMIO_BUFFER_SIZE;
+ }
+
+ remainingSpace = last->m_Size - last->m_Count;
+ remainingToWrite = len - written;
+ if (remainingSpace < remainingToWrite) {
+ memcpy(last->m_Buffer + last->m_Count, data + written,
+ remainingSpace);
+ written += remainingSpace;
+ last->m_Count += remainingSpace;
+ } else {
+ memcpy(last->m_Buffer + last->m_Count, data + written,
+ remainingToWrite);
+ written += remainingToWrite;
+ last->m_Count += remainingToWrite;
+ }
+ }
+}
+
+
+/*
+ * \brief memio_png_flush()
+ *
+ * \param[in] pthing
+ *
+ * <pre>
+ * Notes:
+ * (1) This consolidates write buffers into a single buffer at the
+ * haed of the link list of buffers.
+ * </pre>
+ */
+static void
+memio_png_flush(MEMIODATA *pthing)
+{
+l_int32 amount = 0;
+l_int32 copied = 0;
+MEMIODATA *buffer = 0;
+char *data = 0;
+
+ /* If the data is in one buffer, give the buffer to the user. */
+ if (pthing->m_Next == NULL) return;
+
+ /* Consolidate multiple buffers into one new one; add the buffer
+ * sizes together. */
+ amount = pthing->m_Count;
+ buffer = pthing->m_Next;
+ while (buffer != NULL) {
+ amount += buffer->m_Count;
+ buffer = buffer->m_Next;
+ }
+
+ /* Copy data to a new buffer. */
+ data = (char *)LEPT_MALLOC(amount);
+ memcpy(data, pthing->m_Buffer, pthing->m_Count);
+ copied = pthing->m_Count;
+
+ LEPT_FREE(pthing->m_Buffer);
+ pthing->m_Buffer = NULL;
+
+ /* Don't delete original "thing" because we don't control it. */
+ buffer = pthing->m_Next;
+ pthing->m_Next = NULL;
+ while (buffer != NULL && copied < amount) {
+ MEMIODATA* old;
+ memcpy(data + copied, buffer->m_Buffer, buffer->m_Count);
+ copied += buffer->m_Count;
+
+ old = buffer;
+ buffer = buffer->m_Next;
+
+ LEPT_FREE(old->m_Buffer);
+ LEPT_FREE(old);
+ }
+
+ pthing->m_Buffer = data;
+ pthing->m_Count = copied;
+ pthing->m_Size = amount;
+ return;
+}
+
+
+/*
+ * \brief memio_png_read_data()
+ *
+ * \param[in] png_ptr
+ * \param[in] outBytes
+ * \param[in] byteCountToRead
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a libpng callback that reads an image from a single
+ * memory buffer.
+ * </pre>
+ */
+static void
+memio_png_read_data(png_structp png_ptr,
+ png_bytep outBytes,
+ png_size_t byteCountToRead)
+{
+MEMIODATA *thing;
+
+ thing = (MEMIODATA *)png_get_io_ptr(png_ptr);
+ if (byteCountToRead > (thing->m_Size - thing->m_Count)) {
+ png_error(png_ptr, "read error in memio_png_read_data");
+ }
+ memcpy(outBytes, thing->m_Buffer + thing->m_Count, byteCountToRead);
+ thing->m_Count += byteCountToRead;
+}
+
+
+/*
+ * \brief memio_free()
+ *
+ * \param[in] pthing
+ *
+ * <pre>
+ * Notes:
+ * (1) This frees all the write buffers in the linked list. It must
+ * be done before exiting the pixWriteMemPng().
+ * </pre>
+ */
+static void
+memio_free(MEMIODATA* pthing)
+{
+MEMIODATA *buffer, *old;
+
+ if (pthing->m_Buffer != NULL)
+ LEPT_FREE(pthing->m_Buffer);
+
+ pthing->m_Buffer = NULL;
+ buffer = pthing->m_Next;
+ while (buffer != NULL) {
+ old = buffer;
+ buffer = buffer->m_Next;
+
+ if (old->m_Buffer != NULL)
+ LEPT_FREE(old->m_Buffer);
+ LEPT_FREE(old);
+ }
+}
+
+
+/*---------------------------------------------------------------------*
+ * Reading png from memory *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixReadMemPng()
+ *
+ * \param[in] filedata png compressed data in memory
+ * \param[in] filesize number of bytes in data
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixReastreamPng().
+ * </pre>
+ */
+PIX *
+pixReadMemPng(const l_uint8 *filedata,
+ size_t filesize)
+{
+l_uint8 byte;
+l_int32 i, j, k, index, ncolors, bitval, rval, gval, bval, valid;
+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, 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 */
+MEMIODATA state;
+PIX *pix, *pix1;
+PIXCMAP *cmap;
+
+ PROCNAME("pixReadMemPng");
+
+ if (!filedata)
+ return (PIX *)ERROR_PTR("filedata not defined", procName, NULL);
+ if (filesize < 1)
+ return (PIX *)ERROR_PTR("invalid filesize", procName, NULL);
+
+ state.m_Next = 0;
+ state.m_Count = 0;
+ state.m_Last = &state;
+ state.m_Buffer = (char*)filedata;
+ state.m_Size = filesize;
+ 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_set_read_fn(png_ptr, &state, memio_png_read_data);
+
+ /* ---------------------------------------------------------- *
+ * 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) {
+ lept_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);
+ }
+
+ cmap = 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);
+ }
+ }
+
+ if ((pix = pixCreate(w, h, d)) == NULL) {
+ pixcmapDestroy(&cmap);
+ png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
+ pixcmapDestroy(&cmap);
+ return (PIX *)ERROR_PTR("pix not made", procName, NULL);
+ }
+ pixSetInputFormat(pix, IFF_PNG);
+ wpl = pixGetWpl(pix);
+ data = pixGetData(pix);
+ pixSetSpp(pix, spp);
+ if (pixSetColormap(pix, cmap)) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("invalid colormap", procName, NULL);
+ }
+
+ 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
+ lept_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) {
+ lept_stderr("(r,g,b,a) = (%d,%d,%d,%d)\n",
+ rval, gval, bval, trans[i]);
+ } else {
+ lept_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++) {
+ lept_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 {
+ pix1 = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+ pixDestroy(&pix);
+ pix = pix1;
+ }
+ }
+
+ 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);
+
+ /* Final validity check on the colormap */
+ if ((cmap = pixGetColormap(pix)) != NULL) {
+ pixcmapIsValid(cmap, pix, &valid);
+ if (!valid) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("colormap is not valid", procName, NULL);
+ }
+ }
+
+ pixSetPadBits(pix, 0);
+ return pix;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Writing png to memory *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixWriteMemPng()
+ *
+ * \param[out] pfiledata png encoded data of pix
+ * \param[out] pfilesize size of png encoded data
+ * \param[in] pix
+ * \param[in] gamma use 0.0 if gamma is not defined
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixWriteStreamPng()
+ * </pre>
+ */
+l_ok
+pixWriteMemPng(l_uint8 **pfiledata,
+ size_t *pfilesize,
+ PIX *pix,
+ l_float32 gamma)
+{
+char commentstring[] = "Comment";
+l_int32 i, j, k, wpl, d, spp, cmflag, opaque, ncolors, compval, valid;
+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, xres, yres;
+png_bytep rowbuffer;
+png_structp png_ptr;
+png_infop info_ptr;
+png_colorp palette;
+PIX *pix1;
+PIXCMAP *cmap;
+char *text;
+MEMIODATA state;
+
+ PROCNAME("pixWriteMemPng");
+
+ if (pfiledata) *pfiledata = NULL;
+ if (pfilesize) *pfilesize = 0;
+ if (!pfiledata)
+ return ERROR_INT("&filedata not defined", procName, 1);
+ if (!pfilesize)
+ return ERROR_INT("&filesize not defined", procName, 1);
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+
+ state.m_Buffer = 0;
+ state.m_Size = 0;
+ state.m_Next = 0;
+ state.m_Count = 0;
+ state.m_Last = &state;
+
+ w = pixGetWidth(pix);
+ h = pixGetHeight(pix);
+ d = pixGetDepth(pix);
+ spp = pixGetSpp(pix);
+
+ /* A cmap validity check should prevent low-level colormap errors. */
+ if ((cmap = pixGetColormap(pix))) {
+ cmflag = 1;
+ pixcmapIsValid(cmap, pix, &valid);
+ if (!valid)
+ return ERROR_INT("colormap is not valid", procName, 1);
+ } else {
+ cmflag = 0;
+ }
+
+ pixSetPadBits(pix, 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
+ lept_stderr("cmflag = %d, bit_depth = %d, color_type = %d\n",
+ cmflag, bit_depth, color_type);
+#endif /* DEBUG_WRITE */
+
+ /* 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 */
+ pix1 = NULL;
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ pixDestroy(&pix1);
+ return ERROR_INT("internal png error", procName, 1);
+ }
+
+ png_set_write_fn(png_ptr, &state, memio_png_write_data,
+ (png_flush_ptr)NULL);
+
+ /* 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);
+
+ 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) {
+ /* Make and save the palette */
+ ncolors = pixcmapGetCount(cmap);
+ palette = (png_colorp)LEPT_CALLOC(ncolors, sizeof(png_color));
+ pixcmapToArrays(cmap, &rmap, &gmap, &bmap, &amap);
+ 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];
+ }
+ LEPT_FREE(rmap);
+ LEPT_FREE(gmap);
+ LEPT_FREE(bmap);
+ LEPT_FREE(amap);
+ png_set_PLTE(png_ptr, info_ptr, palette, (int)ncolors);
+ LEPT_FREE(palette);
+
+ pixcmapIsOpaque(cmap, &opaque);
+ if (!opaque) /* alpha channel has some transparency; assume valid */
+ png_set_tRNS(png_ptr, info_ptr, (png_bytep)alpha,
+ (int)ncolors, NULL);
+ }
+
+ /* 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) {
+ pix1 = pixInvert(NULL, pix);
+ pixEndianByteSwap(pix1);
+ } else {
+ pix1 = pixEndianByteSwapNew(pix);
+ }
+ if (!pix1) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ memio_free(&state);
+ return ERROR_INT("pix1 not made", procName, 1);
+ }
+
+ /* Transfer the data */
+ wpl = pixGetWpl(pix1);
+ data = pixGetData(pix1);
+ for (i = 0; i < h; i++)
+ png_write_row(png_ptr, (png_bytep)(data + i * wpl));
+ png_write_end(png_ptr, info_ptr);
+
+ pixDestroy(&pix1);
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ memio_png_flush(&state);
+ *pfiledata = (l_uint8 *)state.m_Buffer;
+ state.m_Buffer = 0;
+ *pfilesize = state.m_Count;
+ memio_free(&state);
+ 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 */
+ rowbuffer = (png_bytep)LEPT_CALLOC(w, 4);
+ 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);
+
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ memio_png_flush(&state);
+ *pfiledata = (l_uint8 *)state.m_Buffer;
+ state.m_Buffer = 0;
+ *pfilesize = state.m_Count;
+ memio_free(&state);
+ return 0;
+}
+
+/* --------------------------------------------*/
+#endif /* HAVE_LIBPNG */
+/* --------------------------------------------*/
diff --git a/leptonica/src/pngiostub.c b/leptonica/src/pngiostub.c
new file mode 100644
index 00000000..f3c8bed9
--- /dev/null
+++ b/leptonica/src/pngiostub.c
@@ -0,0 +1,143 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pngiostub.c
+ * <pre>
+ *
+ * Stubs for pngio.c functions
+ * </pre>
+ */
+
+#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_ok 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_ok 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_ok 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_ok isPngInterlaced(const char *filename, l_int32 *pinterlaced)
+{
+ return ERROR_INT("function not present", "isPngInterlaced", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok fgetPngColormapInfo(FILE *fp, PIXCMAP **pcmap, l_int32 *ptransparency)
+{
+ return ERROR_INT("function not present", "fgetPngColormapInfo", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWritePng(const char *filename, PIX *pix, l_float32 gamma)
+{
+ return ERROR_INT("function not present", "pixWritePng", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWriteStreamPng(FILE *fp, PIX *pix, l_float32 gamma)
+{
+ return ERROR_INT("function not present", "pixWriteStreamPng", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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 *filedata, size_t filesize)
+{
+ return (PIX * )ERROR_PTR("function not present", "pixReadMemPng", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWriteMemPng(l_uint8 **pfiledata, size_t *pfilesize, PIX *pix,
+ l_float32 gamma)
+{
+ return ERROR_INT("function not present", "pixWriteMemPng", 1);
+}
+
+/* --------------------------------------------*/
+#endif /* !HAVE_LIBPNG */
+/* --------------------------------------------*/
diff --git a/leptonica/src/pnmio.c b/leptonica/src/pnmio.c
new file mode 100644
index 00000000..838cf54d
--- /dev/null
+++ b/leptonica/src/pnmio.c
@@ -0,0 +1,1534 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pnmio.c
+ * <pre>
+ *
+ * Stream interface
+ * PIX *pixReadStreamPnm()
+ * l_int32 readHeaderPnm()
+ * l_int32 freadHeaderPnm()
+ * l_int32 pixWriteStreamPnm()
+ * l_int32 pixWriteStreamAsciiPnm()
+ * l_int32 pixWriteStreamPam()
+ *
+ * Read/write to memory
+ * PIX *pixReadMemPnm()
+ * l_int32 readHeaderMemPnm()
+ * l_int32 pixWriteMemPnm()
+ * l_int32 pixWriteMemPam()
+ *
+ * Local helpers
+ * static l_int32 pnmReadNextAsciiValue();
+ * static l_int32 pnmReadNextNumber();
+ * static l_int32 pnmReadNextString();
+ * 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().
+ *
+ * The P7 format is new. It introduced a header with multiple
+ * lines containing distinct tags for the various fields.
+ * See: http://netpbm.sourceforge.net/doc/pam.html
+ *
+ * WIDTH <int> ; mandatory, exactly once
+ * HEIGHT <int> ; mandatory, exactly once
+ * DEPTH <int> ; mandatory, exactly once,
+ * ; its meaning is equivalent to spp
+ * MAXVAL <int> ; mandatory, one of 1, 3, 15, 255 or 65535
+ * TUPLTYPE <string> ; optional; BLACKANDWHITE, GRAYSCALE, RGB
+ * ; and optional suffix _ALPHA, e.g. RGB_ALPHA
+ * ENDHDR ; mandatory, last header line
+ *
+ * Reading BLACKANDWHITE_ALPHA and GRAYSCALE_ALPHA, which have a DEPTH
+ * value of 2, is supported. The original image is converted to a Pix
+ * with 32-bpp and alpha channel (spp == 4).
+ *
+ * Writing P7 format is currently selected for 32-bpp with alpha
+ * channel, i.e. for Pix which have spp == 4, using pixWriteStreamPam().
+ * Jürgen Buchmüller provided the implementation for the P7 (pam) format.
+ * </pre>
+ */
+
+#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 pnmReadNextString(FILE *fp, char *buff, l_int32 size);
+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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixReadStreamPnm()
+ *
+ * \param[in] fp file stream opened for read
+ * \return pix, or NULL on error
+ */
+PIX *
+pixReadStreamPnm(FILE *fp)
+{
+l_uint8 val8, rval8, gval8, bval8, aval8, mask8;
+l_uint16 val16, rval16, gval16, bval16, aval16;
+l_int32 w, h, d, bps, spp, 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, &bps, &spp))
+ return (PIX *)ERROR_PTR("header read failed", procName, NULL);
+ if (bps < 1 || bps > 16)
+ return (PIX *)ERROR_PTR("invalid bps", procName, NULL);
+ if (spp < 1 || spp > 4)
+ return (PIX *)ERROR_PTR("invalid spp", 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);
+
+ /* If type == 6 and bps == 16, we use the code in type 7
+ * to read 6 bytes/pixel from the input file. */
+ if (type == 6 && bps == 16)
+ type = 7;
+
+ switch (type) {
+ case 1:
+ case 2:
+ /* Old "ASCII" binary or gray format */
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ if (pnmReadNextAsciiValue(fp, &val)) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read abend", procName, NULL);
+ }
+ pixSetPixel(pix, j, i, val);
+ }
+ }
+ break;
+
+ case 3:
+ /* Old "ASCII" rgb format */
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ if (pnmReadNextAsciiValue(fp, &rval)) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read abend", procName, NULL);
+ }
+ if (pnmReadNextAsciiValue(fp, &gval)) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read abend", procName, NULL);
+ }
+ if (pnmReadNextAsciiValue(fp, &bval)) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read abend", procName, NULL);
+ }
+ composeRGBPixel(rval, gval, bval, &rgbval);
+ pixSetPixel(pix, j, i, rgbval);
+ }
+ }
+ break;
+
+ case 4:
+ /* "raw" format for 1 bpp */
+ 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) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error in 4", procName, NULL);
+ }
+ SET_DATA_BYTE(line, j, val8);
+ }
+ }
+ break;
+
+ case 5:
+ /* "raw" format for grayscale */
+ 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) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("error in 5", procName, NULL);
+ }
+ 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) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("16 bpp error", procName, NULL);
+ }
+ SET_DATA_TWO_BYTES(line, j, val16);
+ }
+ }
+ }
+ break;
+
+ case 6:
+ /* "raw" format, type == 6; 8 bps, rgb */
+ for (i = 0; i < h; i++) {
+ line = data + i * wpl;
+ for (j = 0; j < wpl; j++) {
+ if (fread(&rval8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 6",
+ procName, NULL);
+ }
+ if (fread(&gval8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 6",
+ procName, NULL);
+ }
+ if (fread(&bval8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 6",
+ procName, NULL);
+ }
+ composeRGBPixel(rval8, gval8, bval8, &rgbval);
+ line[j] = rgbval;
+ }
+ }
+ break;
+
+ case 7:
+ /* "arbitrary" format; type == 7; */
+ if (bps != 16) {
+ mask8 = (1 << bps) - 1;
+ switch (spp) {
+ case 1: /* 1, 2, 4, 8 bpp grayscale */
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ if (fread(&val8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ val8 = val8 & mask8;
+ if (bps == 1) val8 ^= 1; /* white-is-1 photometry */
+ pixSetPixel(pix, j, i, val8);
+ }
+ }
+ break;
+
+ case 2: /* 1, 2, 4, 8 bpp grayscale + alpha */
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ if (fread(&val8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ if (fread(&aval8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ val8 = val8 & mask8;
+ aval8 = aval8 & mask8;
+ composeRGBAPixel(val8, val8, val8, aval8, &rgbval);
+ pixSetPixel(pix, j, i, rgbval);
+ }
+ }
+ pixSetSpp(pix, 4);
+ break;
+
+ case 3: /* rgb */
+ for (i = 0; i < h; i++) {
+ line = data + i * wpl;
+ for (j = 0; j < wpl; j++) {
+ if (fread(&rval8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ if (fread(&gval8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ if (fread(&bval8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ rval8 = rval8 & mask8;
+ gval8 = gval8 & mask8;
+ bval8 = bval8 & mask8;
+ composeRGBPixel(rval8, gval8, bval8, &rgbval);
+ line[j] = rgbval;
+ }
+ }
+ break;
+
+ case 4: /* rgba */
+ for (i = 0; i < h; i++) {
+ line = data + i * wpl;
+ for (j = 0; j < wpl; j++) {
+ if (fread(&rval8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ if (fread(&gval8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ if (fread(&bval8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ if (fread(&aval8, 1, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ rval8 = rval8 & mask8;
+ gval8 = gval8 & mask8;
+ bval8 = bval8 & mask8;
+ aval8 = aval8 & mask8;
+ composeRGBAPixel(rval8, gval8, bval8, aval8, &rgbval);
+ line[j] = rgbval;
+ }
+ }
+ pixSetSpp(pix, 4);
+ break;
+ }
+ } else { /* bps == 16 */
+ /* I have only seen one example that is type 6, 16 bps.
+ * It was 3 spp (rgb), and the 8 bps of real data was stored
+ * in the second byte. In the following, I make the wild
+ * assumption that for all 16 bpp pnm/pam files, we can
+ * take the second byte. */
+ switch (spp) {
+ case 1: /* 16 bps grayscale */
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ if (fread(&val16, 2, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ val8 = val16 & 0xff;
+ pixSetPixel(pix, j, i, val8);
+ }
+ }
+ break;
+
+ case 2: /* 16 bps grayscale + alpha */
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ if (fread(&val16, 2, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ if (fread(&aval16, 2, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ val8 = val16 & 0xff;
+ aval8 = aval16 & 0xff;
+ composeRGBAPixel(val8, val8, val8, aval8, &rgbval);
+ pixSetPixel(pix, j, i, rgbval);
+ }
+ }
+ pixSetSpp(pix, 4);
+ break;
+
+ case 3: /* 16bps rgb */
+ for (i = 0; i < h; i++) {
+ line = data + i * wpl;
+ for (j = 0; j < wpl; j++) {
+ if (fread(&rval16, 2, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ if (fread(&gval16, 2, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ if (fread(&bval16, 2, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ rval8 = rval16 & 0xff;
+ gval8 = gval16 & 0xff;
+ bval8 = bval16 & 0xff;
+ composeRGBPixel(rval8, gval8, bval8, &rgbval);
+ line[j] = rgbval;
+ }
+ }
+ break;
+
+ case 4: /* 16bps rgba */
+ for (i = 0; i < h; i++) {
+ line = data + i * wpl;
+ for (j = 0; j < wpl; j++) {
+ if (fread(&rval16, 2, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ if (fread(&gval16, 2, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ if (fread(&bval16, 2, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ if (fread(&aval16, 2, 1, fp) != 1) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("read error type 7",
+ procName, NULL);
+ }
+ rval8 = rval16 & 0xff;
+ gval8 = gval16 & 0xff;
+ bval8 = bval16 & 0xff;
+ aval8 = aval16 & 0xff;
+ composeRGBAPixel(rval8, gval8, bval8, aval8, &rgbval);
+ line[j] = rgbval;
+ }
+ }
+ pixSetSpp(pix, 4);
+ break;
+ }
+ }
+ break;
+ }
+ return pix;
+}
+
+
+/*!
+ * \brief readHeaderPnm()
+ *
+ * \param[in] filename
+ * \param[out] pw [optional]
+ * \param[out] ph [optional]
+ * \param[out] pd [optional]
+ * \param[out] ptype [optional] pnm type
+ * \param[out] pbps [optional] bits/sample
+ * \param[out] pspp [optional] samples/pixel
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief freadHeaderPnm()
+ *
+ * \param[in] fp file stream opened for read
+ * \param[out] pw [optional]
+ * \param[out] ph [optional]
+ * \param[out] pd [optional]
+ * \param[out] ptype [optional] pnm type
+ * \param[out] pbps [optional] bits/sample
+ * \param[out] pspp [optional] samples/pixel
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+freadHeaderPnm(FILE *fp,
+ l_int32 *pw,
+ l_int32 *ph,
+ l_int32 *pd,
+ l_int32 *ptype,
+ l_int32 *pbps,
+ l_int32 *pspp)
+{
+char tag[16], tupltype[32];
+l_int32 i, w, h, d, bps, spp, type;
+l_int32 maxval;
+l_int32 ch;
+
+ 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 > 7)
+ return ERROR_INT("invalid pnm file", procName, 1);
+
+ if (pnmSkipCommentLines(fp))
+ return ERROR_INT("no data in file", procName, 1);
+
+ if (type == 7) {
+ w = h = d = bps = spp = maxval = 0;
+ for (i = 0; i < 10; i++) { /* limit to 10 lines of this header */
+ if (pnmReadNextString(fp, tag, sizeof(tag)))
+ return ERROR_INT("found no next tag", procName, 1);
+ if (!strcmp(tag, "WIDTH")) {
+ if (pnmReadNextNumber(fp, &w))
+ return ERROR_INT("failed reading width", procName, 1);
+ continue;
+ }
+ if (!strcmp(tag, "HEIGHT")) {
+ if (pnmReadNextNumber(fp, &h))
+ return ERROR_INT("failed reading height", procName, 1);
+ continue;
+ }
+ if (!strcmp(tag, "DEPTH")) {
+ if (pnmReadNextNumber(fp, &spp))
+ return ERROR_INT("failed reading depth", procName, 1);
+ continue;
+ }
+ if (!strcmp(tag, "MAXVAL")) {
+ if (pnmReadNextNumber(fp, &maxval))
+ return ERROR_INT("failed reading maxval", procName, 1);
+ continue;
+ }
+ if (!strcmp(tag, "TUPLTYPE")) {
+ if (pnmReadNextString(fp, tupltype, sizeof(tupltype)))
+ return ERROR_INT("failed reading tuple type", procName, 1);
+ continue;
+ }
+ if (!strcmp(tag, "ENDHDR")) {
+ if ('\n' != (ch = fgetc(fp)))
+ return ERROR_INT("missing LF after ENDHDR", procName, 1);
+ break;
+ }
+ }
+ 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;
+ }
+ if (maxval == 1) {
+ d = bps = 1;
+ } else if (maxval == 3) {
+ d = bps = 2;
+ } else if (maxval == 15) {
+ d = bps = 4;
+ } else if (maxval == 255) {
+ d = bps = 8;
+ } else if (maxval == 0xffff) {
+ d = bps = 16;
+ } else {
+ L_INFO("invalid maxval = %d\n", procName, maxval);
+ return 1;
+ }
+ switch (spp) {
+ case 1:
+ /* d and bps are already set */
+ break;
+ case 2:
+ case 3:
+ case 4:
+ /* create a 32 bpp Pix */
+ d = 32;
+ break;
+ default:
+ L_INFO("invalid depth = %d\n", procName, spp);
+ return 1;
+ }
+ } else {
+
+ 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;
+ spp = 1;
+ bps = 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 {
+ lept_stderr("maxval = %d\n", maxval);
+ return ERROR_INT("invalid maxval", procName, 1);
+ }
+ bps = d;
+ spp = 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 && maxval != 0xffff) {
+ L_ERROR("unexpected maxval = %d\n", procName, maxval);
+ return 1;
+ }
+ bps = (maxval == 255) ? 8 : 16;
+ d = 32;
+ spp = 3;
+ }
+ }
+ if (pw) *pw = w;
+ if (ph) *ph = h;
+ if (pd) *pd = d;
+ if (ptype) *ptype = type;
+ if (pbps) *pbps = bps;
+ if (pspp) *pspp = spp;
+ return 0;
+}
+
+
+/*!
+ * \brief pixWriteStreamPnm()
+ *
+ * \param[in] fp file stream opened for write
+ * \param[in] pix
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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 (d == 32 && pixGetSpp(pix) == 4)
+ return pixWriteStreamPam(fp, pix);
+
+ /* 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;
+}
+
+
+/*!
+ * \brief pixWriteStreamAsciiPnm()
+ *
+ * \param[in] fp file stream opened for write
+ * \param[in] 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_ok
+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) {
+ snprintf(buffer, sizeof(buffer), "%1d ", val);
+ fwrite(buffer, 1, 2, fp);
+ count += 2;
+ } else if (ds == 4) {
+ snprintf(buffer, sizeof(buffer), "%2d ", val);
+ fwrite(buffer, 1, 3, fp);
+ count += 3;
+ } else if (ds == 8) {
+ snprintf(buffer, sizeof(buffer), "%3d ", val);
+ fwrite(buffer, 1, 4, fp);
+ count += 4;
+ } else { /* ds == 16 */
+ snprintf(buffer, sizeof(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++) {
+ snprintf(buffer, sizeof(buffer), "%3d ", cval[k]);
+ fwrite(buffer, 1, 4, fp);
+ count += 4;
+ if (count >= 60) {
+ fputc('\n', fp);
+ count = 0;
+ }
+ }
+ }
+ }
+ }
+
+ pixDestroy(&pixs);
+ return 0;
+}
+
+
+/*!
+ * \brief pixWriteStreamPam()
+ *
+ * \param[in] fp file stream opened for write
+ * \param[in] pix
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This writes arbitrary PAM (P7) packed format.
+ * (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).
+ * </pre>
+ */
+l_ok
+pixWriteStreamPam(FILE *fp,
+ PIX *pix)
+{
+l_uint8 val8;
+l_uint8 pel[8];
+l_uint16 val16;
+l_int32 h, w, d, ds, i, j;
+l_int32 wpls, spps, filebpl, writeerror, maxval;
+l_uint32 *pword, *datas, *lines;
+PIX *pixs;
+
+ PROCNAME("pixWriteStreamPam");
+
+ 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);
+ spps = pixGetSpp(pixs);
+ if (ds < 24)
+ maxval = (1 << ds) - 1;
+ else
+ maxval = 255;
+
+ writeerror = 0;
+ fprintf(fp, "P7\n# Arbitrary PAM file written by leptonica "
+ "(www.leptonica.com)\n");
+ fprintf(fp, "WIDTH %d\n", w);
+ fprintf(fp, "HEIGHT %d\n", h);
+ fprintf(fp, "DEPTH %d\n", spps);
+ fprintf(fp, "MAXVAL %d\n", maxval);
+ if (spps == 1 && ds == 1)
+ fprintf(fp, "TUPLTYPE BLACKANDWHITE\n");
+ else if (spps == 1)
+ fprintf(fp, "TUPLTYPE GRAYSCALE\n");
+ else if (spps == 3)
+ fprintf(fp, "TUPLTYPE RGB\n");
+ else if (spps == 4)
+ fprintf(fp, "TUPLTYPE RGB_ALPHA\n");
+ fprintf(fp, "ENDHDR\n");
+
+ switch (d) {
+ case 1:
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ for (j = 0; j < w; j++) {
+ val8 = GET_DATA_BIT(lines, j);
+ val8 ^= 1; /* pam apparently uses white-is-1 photometry */
+ if (fwrite(&val8, 1, 1, fp) != 1)
+ writeerror = 1;
+ }
+ }
+ break;
+
+ case 2:
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ for (j = 0; j < w; j++) {
+ val8 = GET_DATA_DIBIT(lines, j);
+ if (fwrite(&val8, 1, 1, fp) != 1)
+ writeerror = 1;
+ }
+ }
+ break;
+
+ case 4:
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ for (j = 0; j < w; j++) {
+ val8 = GET_DATA_QBIT(lines, j);
+ if (fwrite(&val8, 1, 1, fp) != 1)
+ writeerror = 1;
+ }
+ }
+ break;
+
+ case 8:
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ for (j = 0; j < w; j++) {
+ val8 = GET_DATA_BYTE(lines, j);
+ if (fwrite(&val8, 1, 1, fp) != 1)
+ writeerror = 1;
+ }
+ }
+ break;
+
+ case 16:
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ for (j = 0; j < w; j++) {
+ val16 = GET_DATA_TWO_BYTES(lines, j);
+ if (fwrite(&val16, 2, 1, fp) != 1)
+ writeerror = 1;
+ }
+ }
+ break;
+
+ case 24:
+ filebpl = 3 * w;
+ for (i = 0; i < h; i++) {
+ lines = datas + i * wpls;
+ if (fwrite(lines, 1, filebpl, fp) != filebpl)
+ writeerror = 1;
+ }
+ break;
+
+ case 32:
+ switch (spps) {
+ case 3:
+ 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;
+ }
+ }
+ break;
+ case 4:
+ 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);
+ pel[3] = GET_DATA_BYTE(pword, L_ALPHA_CHANNEL);
+ if (fwrite(pel, 1, 4, fp) != 4)
+ writeerror = 1;
+ }
+ }
+ break;
+ }
+ break;
+ }
+
+ pixDestroy(&pixs);
+ if (writeerror)
+ return ERROR_INT("image write fail", procName, 1);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Read/write to memory *
+ *---------------------------------------------------------------------*/
+
+/*!
+ * \brief pixReadMemPnm()
+ *
+ * \param[in] data const; pnm-encoded
+ * \param[in] size of data
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The %size byte of %data must be a null character.
+ * </pre>
+ */
+PIX *
+pixReadMemPnm(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+PIX *pix;
+
+ PROCNAME("pixReadMemPnm");
+
+ if (!data)
+ return (PIX *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (PIX *)ERROR_PTR("stream not opened", procName, NULL);
+ pix = pixReadStreamPnm(fp);
+ fclose(fp);
+ if (!pix) L_ERROR("pix not read\n", procName);
+ return pix;
+}
+
+
+/*!
+ * \brief readHeaderMemPnm()
+ *
+ * \param[in] data const; pnm-encoded
+ * \param[in] size of data
+ * \param[out] pw [optional]
+ * \param[out] ph [optional]
+ * \param[out] pd [optional]
+ * \param[out] ptype [optional] pnm type
+ * \param[out] pbps [optional] bits/sample
+ * \param[out] pspp [optional] samples/pixel
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+readHeaderMemPnm(const l_uint8 *data,
+ 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 (!data)
+ return ERROR_INT("data not defined", procName, 1);
+
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = freadHeaderPnm(fp, pw, ph, pd, ptype, pbps, pspp);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("header data read failed", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixWriteMemPnm()
+ *
+ * \param[out] pdata data of PNM image
+ * \param[out] psize size of returned data
+ * \param[in] pix
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixWriteStreamPnm() for usage. This version writes to
+ * memory instead of to a file stream.
+ * </pre>
+ */
+l_ok
+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_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = pixWriteStreamPnm(fp, pix);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*!
+ * \brief pixWriteMemPam()
+ *
+ * \param[out] pdata data of PAM image
+ * \param[out] psize size of returned data
+ * \param[in] pix
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixWriteStreamPnm() for usage. This version writes to
+ * memory instead of to a file stream.
+ * </pre>
+ */
+l_ok
+pixWriteMemPam(l_uint8 **pdata,
+ size_t *psize,
+ PIX *pix)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("pixWriteMemPam");
+
+ 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 = pixWriteStreamPam(fp, pix);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = pixWriteStreamPam(fp, pix);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+
+/*--------------------------------------------------------------------*
+ * Static helpers *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pnmReadNextAsciiValue()
+ *
+ * Return: 0 if OK, 1 on error or EOF.
+ *
+ * Notes:
+ * (1) This reads the next sample value in ASCII from 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 and non-numeric characters */
+ if ((c = fgetc(fp)) == EOF)
+ return 1;
+ } while (!isdigit(c));
+
+ fseek(fp, -1L, SEEK_CUR); /* back up one byte */
+ ignore = fscanf(fp, "%d", pval);
+ return 0;
+}
+
+
+/*!
+ * \brief pnmReadNextNumber()
+ *
+ * \param[in] fp file stream
+ * \param[out] pval value as an integer
+ * \return 0 if OK, 1 on error or EOF.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static l_int32
+pnmReadNextNumber(FILE *fp,
+ l_int32 *pval)
+{
+char buf[8];
+l_int32 i, c, foundws;
+
+ 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;
+}
+
+/*!
+ * \brief pnmReadNextString()
+ *
+ * \param[in] fp file stream
+ * \param[out] buff pointer to the string buffer
+ * \param[in] size max. number of charactes in buffer
+ * \return 0 if OK, 1 on error or EOF.
+ *
+ * <pre>
+ * Notes:
+ * (1) This reads the next set of alphanumeric chars,
+ * returning the string and swallowing the trailing
+ * whitespace characters.
+ * This is needed to read header lines, which precede
+ * the P7 format binary data.
+ * </pre>
+ */
+static l_int32
+pnmReadNextString(FILE *fp,
+ char *buff,
+ l_int32 size)
+{
+l_int32 i, c;
+
+ PROCNAME("pnmReadNextString");
+
+ if (!buff)
+ return ERROR_INT("buff not defined", procName, 1);
+ *buff = '\0';
+ if (!fp)
+ return ERROR_INT("stream not open", procName, 1);
+ if (size <= 0)
+ return ERROR_INT("size is too small", procName, 1);
+
+ do { /* skip whitespace */
+ if ((c = fgetc(fp)) == EOF)
+ return ERROR_INT("end of file reached", procName, 1);
+ } while (c == ' ' || c == '\t' || c == '\n' || c == '\r');
+
+ /* Comment lines are allowed to appear
+ * anywhere in the header lines */
+ if (c == '#') {
+ do { /* each line starting with '#' */
+ do { /* this entire line */
+ if ((c = fgetc(fp)) == EOF)
+ return ERROR_INT("end of file reached", procName, 1);
+ } while (c != '\n');
+ if ((c = fgetc(fp)) == EOF)
+ return ERROR_INT("end of file reached", procName, 1);
+ } while (c == '#');
+ }
+
+ /* The next string ends when there is
+ * a whitespace character following. */
+ for (i = 0; i < size - 1; i++) {
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+ break;
+ buff[i] = c;
+ if ((c = fgetc(fp)) == EOF)
+ return ERROR_INT("end of file reached", procName, 1);
+ }
+ buff[i] = '\0';
+
+ /* Back up one byte */
+ fseek(fp, -1L, SEEK_CUR);
+ if (i >= size - 1)
+ return ERROR_INT("buff size too small", procName, 1);
+
+ /* Skip over trailing spaces and tabs */
+ for (;;) {
+ if ((c = fgetc(fp)) == EOF)
+ return ERROR_INT("end of file reached", procName, 1);
+ if (c != ' ' && c != '\t')
+ break;
+ }
+
+ /* Back up one byte */
+ fseek(fp, -1L, SEEK_CUR);
+ return 0;
+}
+
+
+/*!
+ * \brief 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/leptonica/src/pnmiostub.c b/leptonica/src/pnmiostub.c
new file mode 100644
index 00000000..2238755c
--- /dev/null
+++ b/leptonica/src/pnmiostub.c
@@ -0,0 +1,120 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file pnmiostub.c
+ * <pre>
+ *
+ * Stubs for pnmio.c functions
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if !USE_PNMIO /* defined in environ.h */
+/* --------------------------------------------*/
+
+PIX * pixReadStreamPnm(FILE *fp)
+{
+ return (PIX * )ERROR_PTR("function not present", "pixReadStreamPnm", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok 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_ok pixWriteStreamPnm(FILE *fp, PIX *pix)
+{
+ return ERROR_INT("function not present", "pixWriteStreamPnm", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWriteStreamAsciiPnm(FILE *fp, PIX *pix)
+{
+ return ERROR_INT("function not present", "pixWriteStreamAsciiPnm", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWriteStreamPam(FILE *fp, PIX *pix)
+{
+ return ERROR_INT("function not present", "pixWriteStreamPam", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadMemPnm(const l_uint8 *cdata, size_t size)
+{
+ return (PIX * )ERROR_PTR("function not present", "pixReadMemPnm", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok pixWriteMemPnm(l_uint8 **pdata, size_t *psize, PIX *pix)
+{
+ return ERROR_INT("function not present", "pixWriteMemPnm", 1);
+}
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWriteMemPam(l_uint8 **pdata, size_t *psize, PIX *pix)
+{
+ return ERROR_INT("function not present", "pixWriteMemPam", 1);
+}
+
+
+/* --------------------------------------------*/
+#endif /* !USE_PNMIO */
+/* --------------------------------------------*/
diff --git a/leptonica/src/projective.c b/leptonica/src/projective.c
new file mode 100644
index 00000000..15af0161
--- /dev/null
+++ b/leptonica/src/projective.c
@@ -0,0 +1,932 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file projective.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+extern l_float32 AlphaMaskBorderVals[2];
+
+/*------------------------------------------------------------n
+ * Sampled projective image transformation *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixProjectiveSampledPta()
+ *
+ * \param[in] pixs all depths
+ * \param[in] ptad 4 pts of final coordinate space
+ * \param[in] ptas 4 pts of initial coordinate space
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixProjectiveSampled()
+ *
+ * \param[in] pixs all depths
+ * \param[in] vc vector of 8 coefficients for projective transform
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixProjectivePta()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] ptad 4 pts of final coordinate space
+ * \param[in] ptas 4 pts of initial coordinate space
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Brings in either black or white pixels from the boundary
+ * (2) Removes any existing colormap, if necessary, before transforming
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixProjective()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] vc vector of 8 coefficients for projective transform
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Brings in either black or white pixels from the boundary
+ * (2) Removes any existing colormap, if necessary, before transforming
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixProjectivePtaColor()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] ptad 4 pts of final coordinate space
+ * \param[in] ptas 4 pts of initial coordinate space
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixProjectiveColor()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] vc vector of 8 coefficients for projective transform
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixProjectivePtaGray()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] ptad 4 pts of final coordinate space
+ * \param[in] ptas 4 pts of initial coordinate space
+ * \param[in] 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;
+}
+
+
+
+/*!
+ * \brief pixProjectiveGray()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] vc vector of 8 coefficients for projective transform
+ * \param[in] 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixProjectivePtaWithAlpha()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] ptad 4 pts of final coordinate space
+ * \param[in] ptas 4 pts of initial coordinate space
+ * \param[in] pixg [optional] 8 bpp, for alpha channel, can be null
+ * \param[in] fract between 0.0 and 1.0, with 0.0 fully transparent
+ * and 1.0 fully opaque
+ * \param[in] border of pixels added to capture transformed source pixels
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief getProjectiveXformCoeffs()
+ *
+ * \param[in] ptas source 4 points; unprimed
+ * \param[in] ptad transformed 4 points; primed
+ * \param[out] pvc 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_ok
+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);
+
+ b = (l_float32 *)LEPT_CALLOC(8, sizeof(l_float32));
+ *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++)
+ a[i] = (l_float32 *)LEPT_CALLOC(8, sizeof(l_float32));
+ 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;
+}
+
+
+/*!
+ * \brief projectiveXformSampledPt()
+ *
+ * \param[in] vc vector of 8 coefficients
+ * \param[in] x, y initial point
+ * \param[out] pxp, pyp transformed point
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds the nearest pixel coordinates of the transformed point.
+ * (2) It does not check ptrs for returned data!
+ * </pre>
+ */
+l_ok
+projectiveXformSampledPt(l_float32 *vc,
+ l_int32 x,
+ l_int32 y,
+ l_int32 *pxp,
+ l_int32 *pyp)
+{
+l_float32 factor;
+l_float64 denom;
+
+ PROCNAME("projectiveXformSampledPt");
+
+ if (!vc)
+ return ERROR_INT("vc not defined", procName, 1);
+
+ if ((denom = vc[6] * x + vc[7] * y + 1.0) == 0.0)
+ return ERROR_INT("denom = 0.0", procName, 1);
+ factor = 1.0 / denom;
+ *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;
+}
+
+
+/*!
+ * \brief projectiveXformPt()
+ *
+ * \param[in] vc vector of 8 coefficients
+ * \param[in] x, y initial point
+ * \param[out] pxp, pyp transformed point
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This computes the floating point location of the transformed point.
+ * (2) It does not check ptrs for returned data!
+ * </pre>
+ */
+l_ok
+projectiveXformPt(l_float32 *vc,
+ l_int32 x,
+ l_int32 y,
+ l_float32 *pxp,
+ l_float32 *pyp)
+{
+l_float32 factor;
+l_float64 denom;
+
+ PROCNAME("projectiveXformPt");
+
+ if (!vc)
+ return ERROR_INT("vc not defined", procName, 1);
+
+ if ((denom = vc[6] * x + vc[7] * y + 1.0) == 0.0)
+ return ERROR_INT("denom = 0.0", procName, 1);
+ factor = 1.0 / denom;
+ *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/leptonica/src/psio1.c b/leptonica/src/psio1.c
new file mode 100644
index 00000000..bf825a97
--- /dev/null
+++ b/leptonica/src/psio1.c
@@ -0,0 +1,1077 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file psio1.c
+ * <pre>
+ *
+ * |=============================================================|
+ * | Important note |
+ * |=============================================================|
+ * | Some of these functions require I/O libraries such as |
+ * | libtiff, libjpeg, and libz. If you do not have these |
+ * | libraries, some calls will fail. |
+ * | |
+ * | You can manually deactivate all PostScript writing by |
+ * | setting this in environ.h: |
+ * | \code |
+ * | #define USE_PSIO 0 |
+ * | \endcode |
+ * | 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()
+ * l_int32 pixWriteCompressedToPS()
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if USE_PSIO /* defined in environ.h */
+ /* --------------------------------------------*/
+
+/*-------------------------------------------------------------*
+ * Convert files in a directory to PS *
+ *-------------------------------------------------------------*/
+/*
+ * \brief convertFilesToPS()
+ *
+ * \param[in] dirin input directory
+ * \param[in] substr [optional] substring filter on filenames; can be NULL
+ * \param[in] res typ. 300 or 600 ppi
+ * \param[in] fileout output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*
+
+ * \brief sarrayConvertFilesToPS()
+ *
+ * \param[in] sarray of full path names
+ * \param[in] res typ. 300 or 600 ppi
+ * \param[in] fileout output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See convertFilesToPS()
+ * </pre>
+ */
+l_ok
+sarrayConvertFilesToPS(SARRAY *sa,
+ l_int32 res,
+ const char *fileout)
+{
+char *fname;
+l_int32 i, nfiles, index, 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);
+ 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, &index);
+ }
+
+ return 0;
+}
+
+
+/*
+ * \brief convertFilesFittedToPS()
+ *
+ * \param[in] dirin input directory
+ * \param[in] substr [optional] substring filter on filenames; can be NULL)
+ * \param[in] xpts desired size in printer points; use 0 for default
+ * \param[in] ypts desired size in printer points; use 0 for default
+ * \param[in] fileout output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*
+ * \brief sarrayConvertFilesFittedToPS()
+ *
+ * \param[in] sarray of full path names
+ * \param[in] xpts desired size in printer points; use 0 for default
+ * \param[in] ypts desired size in printer points; use 0 for default
+ * \param[in] fileout output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See convertFilesFittedToPS()
+ * </pre>
+ */
+l_ok
+sarrayConvertFilesFittedToPS(SARRAY *sa,
+ l_float32 xpts,
+ l_float32 ypts,
+ const char *fileout)
+{
+char *fname;
+l_int32 ret, i, w, h, nfiles, index, 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);
+ 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, &index);
+ }
+
+ return 0;
+}
+
+
+/*
+ * \brief writeImageCompressedToPSFile()
+ *
+ * \param[in] filein input image file
+ * \param[in] fileout output ps file
+ * \param[in] res output printer resolution
+ * \param[in,out] pindex index of image in output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 %index = 0. %index is incremented
+ * if the page is successfully written. It is used to decide
+ * whether to write (index == 0) or append (index > 0) to the file.
+ * </pre>
+ */
+l_ok
+writeImageCompressedToPSFile(const char *filein,
+ const char *fileout,
+ l_int32 res,
+ l_int32 *pindex)
+{
+const char *op;
+l_int32 format, retval;
+
+ PROCNAME("writeImageCompressedToPSFile");
+
+ if (!pindex)
+ return ERROR_INT("&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 = (*pindex == 0) ? "w" : "a";
+ if (format == IFF_JFIF_JPEG) {
+ retval = convertJpegToPS(filein, fileout, op, 0, 0,
+ res, 1.0, *pindex + 1, TRUE);
+ } else if (format == IFF_TIFF_G4) {
+ retval = convertG4ToPS(filein, fileout, op, 0, 0,
+ res, 1.0, *pindex + 1, FALSE, TRUE);
+ } else { /* all other image formats */
+ retval = convertFlateToPS(filein, fileout, op, 0, 0,
+ res, 1.0, *pindex + 1, TRUE);
+ }
+ if (retval == 0) (*pindex)++;
+
+ return retval;
+}
+
+
+/*-------------------------------------------------------------*
+ * Convert mixed text/image files to PS *
+ *-------------------------------------------------------------*/
+/*
+ * \brief convertSegmentedPagesToPS()
+ *
+ * \param[in] pagedir input page image directory
+ * \param[in] pagestr [optional] substring filter on page filenames;
+ * can be NULL
+ * \param[in] page_numpre number of characters in page name before number
+ * \param[in] maskdir input mask image directory
+ * \param[in] maskstr [optional] substring filter on mask filenames;
+ * can be NULL
+ * \param[in] mask_numpre number of characters in mask name before number
+ * \param[in] numpost number of characters in names after number
+ * \param[in] maxnum only consider page numbers up to this value
+ * \param[in] textscale scale of text output relative to pixs
+ * \param[in] imagescale scale of image output relative to pixs
+ * \param[in] threshold for binarization; typ. about 190; 0 for default
+ * \param[in] fileout output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, "");
+ 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;
+}
+
+
+/*
+ * \brief pixWriteSegmentedPageToPS()
+ *
+ * \param[in] pixs all depths; colormap ok
+ * \param[in] pixm [optional] 1 bpp segmentation mask over image region
+ * \param[in] textscale scale of text output relative to pixs
+ * \param[in] imagescale scale of image output relative to pixs
+ * \param[in] threshold for binarization; typ. about 190; 0 for default
+ * \param[in] pageno page number in set; use 1 for new output file
+ * \param[in] fileout output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, L_CLONE, 0); /* clone if possible */
+
+ /* 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;
+}
+
+
+/*
+ * \brief pixWriteMixedToPS()
+ *
+ * \param[in] pixb [optional] 1 bpp mask; typically for text
+ * \param[in] pixc [optional] 8 or 32 bpp image regions
+ * \param[in] scale scale factor for rendering pixb, relative to pixc;
+ * typ. 4.0
+ * \param[in] pageno page number in set; use 1 for new output file
+ * \param[in] fileout output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * <pre>
+ */
+l_ok
+pixWriteMixedToPS(PIX *pixb,
+ PIX *pixc,
+ l_float32 scale,
+ l_int32 pageno,
+ const char *fileout)
+{
+char *tname;
+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 */
+ if (pixc) {
+ tname = l_makeTempFilename();
+ pixWrite(tname, pixc, IFF_JFIF_JPEG);
+ endpage = (pixb) ? FALSE : TRUE;
+ op = (pageno <= 1) ? "w" : "a";
+ ret = convertJpegToPS(tname, fileout, op, 0, 0, resc, 1.0,
+ pageno, endpage);
+ lept_rmfile(tname);
+ LEPT_FREE(tname);
+ 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) {
+ tname = l_makeTempFilename();
+ pixWrite(tname, pixb, IFF_TIFF_G4);
+ op = (pageno <= 1 && !pixc) ? "w" : "a";
+ maskop = (pixc) ? 1 : 0;
+ ret = convertG4ToPS(tname, fileout, op, 0, 0, resb, 1.0,
+ pageno, maskop, 1);
+ lept_rmfile(tname);
+ LEPT_FREE(tname);
+ if (ret)
+ return ERROR_INT("tiff data not written", procName, 1);
+ }
+
+ return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ * Convert any image file to PS for embedding *
+ *-------------------------------------------------------------*/
+/*
+ * \brief convertToPSEmbed()
+ *
+ * \param[in] filein input image file, any format
+ * \param[in] fileout output ps file
+ * \param[in] level PostScript compression: 1 (uncompressed), 2 or 3
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+convertToPSEmbed(const char *filein,
+ const char *fileout,
+ l_int32 level)
+{
+char *tname;
+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, L_MS_BYTE);
+ else
+ pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+ pixDestroy(&pixs);
+ if (!pix)
+ return ERROR_INT("converted pix not made", procName, 1);
+
+ d = pixGetDepth(pix);
+ tname = l_makeTempFilename();
+ if (d == 1) {
+ if (pixWrite(tname, pix, IFF_TIFF_G4)) {
+ LEPT_FREE(tname);
+ pixDestroy(&pix);
+ return ERROR_INT("g4 tiff not written", procName, 1);
+ }
+ convertG4ToPSEmbed(tname, fileout);
+ } else {
+ if (pixWrite(tname, pix, IFF_JFIF_JPEG)) {
+ LEPT_FREE(tname);
+ pixDestroy(&pix);
+ return ERROR_INT("jpeg not written", procName, 1);
+ }
+ convertJpegToPSEmbed(tname, fileout);
+ }
+
+ lept_rmfile(tname);
+ LEPT_FREE(tname);
+ pixDestroy(&pix);
+ return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ * Write all images in a pixa out to PS *
+ *-------------------------------------------------------------*/
+/*
+ * \brief pixaWriteCompressedToPS()
+ *
+ * \param[in] pixa any set of images
+ * \param[in] fileout output ps file
+ * \param[in] res resolution for the set of input images
+ * \param[in] level PostScript compression capability: 2 or 3
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates a PostScript file of multiple page images,
+ * all with bounding boxes.
+ * (2) See pixWriteCompressedToPS() for details.
+ * (3) To generate a pdf from %fileout, use:
+ * ps2pdf <infile.ps> <outfile.pdf>
+ * </pre>
+ */
+l_ok
+pixaWriteCompressedToPS(PIXA *pixa,
+ const char *fileout,
+ l_int32 res,
+ l_int32 level)
+{
+l_int32 i, n, index, ret;
+PIX *pix;
+
+ 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;
+ }
+
+ index = 0;
+ n = pixaGetCount(pixa);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixa, i, L_CLONE);
+ ret = pixWriteCompressedToPS(pix, fileout, res, level, &index);
+ if (ret) L_ERROR("PS string not written for image %d\n", procName, i);
+ pixDestroy(&pix);
+ }
+ return 0;
+}
+
+
+/*
+ * \brief pixWriteCompressedToPS()
+ *
+ * \param[in] pix any depth; colormap OK
+ * \param[in] fileout output ps file
+ * \param[in] res of input image
+ * \param[in] level PostScript compression capability: 2 or 3
+ * \param[in,out] pindex index of image in output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates a PostScript string for %pix, and writes it
+ * to a file, with a bounding box.
+ * (2) *pindex keeps track of the number of images that have been
+ * written to %fileout. If this is the first image to be
+ * converted, set *pindex == 0 before passing it in. If the
+ * PostScript string is successfully generated, this will increment
+ * *pindex. If *pindex > 0, the PostScript string will be
+ * appended to %fileout.
+ * (3) PostScript level 2 enables lossless tiffg4 and lossy jpeg
+ * compression. Level 3 adds lossless flate (essentially gzip)
+ * compression.
+ * * For images with a colormap, lossless flate is often better in
+ * both quality and size than jpeg.
+ * * The decision for images without a colormap affects compression
+ * efficiency: %level2 (jpeg) is usually better than %level3 (flate)
+ * * Because jpeg does not handle 16 bpp, if %level == 2, the image
+ * is converted to 8 bpp (using MSB) and compressed with jpeg,
+ * cmap + level2: jpeg
+ * cmap + level3: flate
+ * 1 bpp: tiffg4
+ * 2 or 4 bpp + level2: jpeg
+ * 2 or 4 bpp + level3: flate
+ * 8 bpp + level2: jpeg
+ * 8 bpp + level3: flate
+ * 16 bpp + level2: jpeg [converted to 8 bpp, with warning]
+ * 16 bpp + level3: flate
+ * 32 bpp + level2: jpeg
+ * 32 bpp + level3: flate
+ * </pre>
+ */
+l_ok
+pixWriteCompressedToPS(PIX *pix,
+ const char *fileout,
+ l_int32 res,
+ l_int32 level,
+ l_int32 *pindex)
+{
+char *tname;
+l_int32 writeout, d;
+PIX *pixt;
+PIXCMAP *cmap;
+
+ PROCNAME("pixWriteCompressedToPS");
+
+ if (!pix)
+ return ERROR_INT("pix 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;
+ }
+ if (!pindex)
+ return ERROR_INT("&index not defined", procName, 1);
+
+ tname = l_makeTempFilename();
+ writeout = TRUE;
+ d = pixGetDepth(pix);
+ cmap = pixGetColormap(pix);
+ if (d == 1) {
+ if (pixWrite(tname, pix, IFF_TIFF_G4))
+ writeout = FALSE;
+ } else if (level == 3) {
+ if (pixWrite(tname, pix, IFF_PNG))
+ writeout = FALSE;
+ } else { /* level == 2 */
+ if (cmap) {
+ pixt = pixConvertForPSWrap(pix);
+ if (pixWrite(tname, pixt, IFF_JFIF_JPEG))
+ writeout = FALSE;
+ pixDestroy(&pixt);
+ } else if (d == 16) {
+ L_WARNING("d = 16; converting to 8 bpp for jpeg\n", procName);
+ pixt = pixConvert16To8(pix, L_MS_BYTE);
+ if (pixWrite(tname, pixt, IFF_JFIF_JPEG))
+ writeout = FALSE;
+ pixDestroy(&pixt);
+ } else if (d == 2 || d == 4) {
+ pixt = pixConvertTo8(pix, 0);
+ if (pixWrite(tname, pixt, IFF_JFIF_JPEG))
+ writeout = FALSE;
+ pixDestroy(&pixt);
+ } else if (d == 8 || d == 32) {
+ if (pixWrite(tname, pix, IFF_JFIF_JPEG))
+ writeout = FALSE;
+ } else { /* shouldn't happen */
+ L_ERROR("invalid depth with level 2: %d\n", procName, d);
+ writeout = FALSE;
+ }
+ }
+
+ if (writeout)
+ writeImageCompressedToPSFile(tname, fileout, res, pindex);
+
+ if (lept_rmfile(tname) != 0)
+ L_ERROR("temp file %s was not deleted\n", procName, tname);
+ LEPT_FREE(tname);
+ return (writeout) ? 0 : 1;
+}
+
+/* --------------------------------------------*/
+#endif /* USE_PSIO */
+/* --------------------------------------------*/
diff --git a/leptonica/src/psio1stub.c b/leptonica/src/psio1stub.c
new file mode 100644
index 00000000..f36b5e0d
--- /dev/null
+++ b/leptonica/src/psio1stub.c
@@ -0,0 +1,137 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file psio1stub.c
+ * <pre>
+ *
+ * Stubs for psio1.c functions
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if !USE_PSIO /* defined in environ.h */
+/* --------------------------------------------*/
+
+l_ok convertFilesToPS(const char *dirin, const char *substr,
+ l_int32 res, const char *fileout)
+{
+ return ERROR_INT("function not present", "convertFilesToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok sarrayConvertFilesToPS(SARRAY *sa, l_int32 res, const char *fileout)
+{
+ return ERROR_INT("function not present", "sarrayConvertFilesToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok sarrayConvertFilesFittedToPS(SARRAY *sa, l_float32 xpts,
+ l_float32 ypts, const char *fileout)
+{
+ return ERROR_INT("function not present", "sarrayConvertFilesFittedToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok writeImageCompressedToPSFile(const char *filein, const char *fileout,
+ l_int32 res, l_int32 *pindex)
+{
+ return ERROR_INT("function not present", "writeImageCompressedToPSFile", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok 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_ok pixWriteMixedToPS(PIX *pixb, PIX *pixc, l_float32 scale,
+ l_int32 pageno, const char *fileout)
+{
+ return ERROR_INT("function not present", "pixWriteMixedToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok convertToPSEmbed(const char *filein, const char *fileout, l_int32 level)
+{
+ return ERROR_INT("function not present", "convertToPSEmbed", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixaWriteCompressedToPS(PIXA *pixa, const char *fileout,
+ l_int32 res, l_int32 level)
+{
+ return ERROR_INT("function not present", "pixaWriteCompressedtoPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWriteCompressedToPS(PIX *pix, const char *fileout, l_int32 res,
+ l_int32 level, l_int32 *pindex)
+{
+ return ERROR_INT("function not present", "pixWriteCompressedtoPS", 1);
+}
+
+/* --------------------------------------------*/
+#endif /* !USE_PSIO */
+/* --------------------------------------------*/
diff --git a/leptonica/src/psio2.c b/leptonica/src/psio2.c
new file mode 100644
index 00000000..a249a0d7
--- /dev/null
+++ b/leptonica/src/psio2.c
@@ -0,0 +1,2044 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file psio2.c
+ * <pre>
+ *
+ * |=============================================================|
+ * | Important note |
+ * |=============================================================|
+ * | Some of these functions require I/O libraries such as |
+ * | libtiff, libjpeg, and libz. If you do not have these |
+ * | libraries, some calls will fail. |
+ * | |
+ * | You can manually deactivate all PostScript writing by |
+ * | setting this in environ.h: |
+ * | \code |
+ * | #define USE_PSIO 0 |
+ * | \endcode |
+ * | 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()
+ * static void getScaledParametersPS()
+ * static l_int32 convertByteToHexAscii()
+ *
+ * For jpeg compressed images (use dct compression)
+ * l_int32 convertJpegToPSEmbed()
+ * l_int32 convertJpegToPS()
+ * static l_int32 convertJpegToPSString()
+ * static char *generateJpegPS()
+ *
+ * For g4 fax compressed images (use ccitt g4 compression)
+ * l_int32 convertG4ToPSEmbed()
+ * l_int32 convertG4ToPS()
+ * static l_int32 convertG4ToPSString()
+ * static char *generateG4PS()
+ *
+ * For multipage tiff images
+ * l_int32 convertTiffMultipageToPS()
+ *
+ * For flate (gzip) compressed images (e.g., png)
+ * l_int32 convertFlateToPSEmbed()
+ * l_int32 convertFlateToPS()
+ * static l_int32 convertFlateToPSString()
+ * static char *generateFlatePS()
+ *
+ * Write to memory
+ * l_int32 pixWriteMemPS()
+ *
+ * Converting resolution
+ * l_int32 getResLetterPage()
+ * static l_int32 getResA4Page()
+ *
+ * Setting flag for writing bounding box hint
+ * void l_psWriteBoundingBox()
+ *
+ * See psio1.c for higher-level functions and their usage.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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;
+
+#define Bufsize 512
+static const l_int32 DefaultInputRes = 300; /* typical scan res, ppi */
+static const l_int32 MinRes = 5;
+static const l_int32 MaxRes = 3000;
+
+ /* For computing resolution that fills page to desired amount */
+static const l_int32 LetterWidth = 612; /* points */
+static const l_int32 LetterHeight = 792; /* points */
+static const l_int32 A4Width = 595; /* points */
+static const l_int32 A4Height = 842; /* points */
+static const l_float32 DefaultFillFraction = 0.95f;
+
+#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. */
+
+ /* Static helper functions */
+static 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);
+static void convertByteToHexAscii(l_uint8 byteval, char *pnib1, char *pnib2);
+static l_ok 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);
+static 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);
+static l_ok 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);
+static 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);
+static l_ok 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);
+static 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);
+
+
+/*-------------------------------------------------------------*
+ * For uncompressed images *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixWritePSEmbed()
+ *
+ * \param[in] filein input file, all depths, colormap OK
+ * \param[in] fileout output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+pixWritePSEmbed(const char *filein,
+ const char *fileout)
+{
+l_int32 w, h, ret;
+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);
+ ret = pixWriteStreamPS(fp, pix, NULL, 0, scale);
+ fclose(fp);
+
+ pixDestroy(&pix);
+ return ret;
+}
+
+
+/*!
+ * \brief pixWriteStreamPS()
+ *
+ * \param[in] fp file stream
+ * \param[in] pix
+ * \param[in] box [optional]
+ * \param[in] res can use 0 for default of 300 ppi
+ * \param[in] scale to prevent scaling, use either 1.0 or 0.0
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+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);
+
+ if ((outstr = pixWriteStringPS(pixc, box, res, scale)) == NULL) {
+ pixDestroy(&pixc);
+ return (l_int32)ERROR_INT("outstr not made", procName, 1);
+ }
+ length = strlen(outstr);
+ fwrite(outstr, 1, length, fp);
+ LEPT_FREE(outstr);
+ pixDestroy(&pixc);
+ return 0;
+}
+
+
+/*!
+ * \brief pixWriteStringPS()
+ *
+ * \param[in] pixs all depths, colormap OK
+ * \param[in] box bounding box; can be NULL
+ * \param[in] res resolution, in printer ppi. Use 0 for default 300 ppi.
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ *
+ * %scale: 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.
+ *
+ * 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)
+ * </pre>
+ */
+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);
+ pixDestroy(&pix);
+ if (!outstr)
+ return (char *)ERROR_PTR("outstr not made", procName, NULL);
+ return outstr;
+}
+
+
+/*!
+ * \brief generateUncompressedPS()
+ *
+ * \param[in] hexdata
+ * \param[in] w, h raster image size in pixels
+ * \param[in] d image depth in bpp; rgb is 32
+ * \param[in] psbpl raster bytes/line, when packed to the byte boundary
+ * \param[in] bps bits/sample: either 1 or 8
+ * \param[in] xpt, ypt location of LL corner of image, in pts, relative
+ * to the PostScript origin (0,0) at the LL corner
+ * of the page
+ * \param[in] wpt, hpt rendered image size in pts
+ * \param[in] boxflag 1 to print out bounding box hint; 0 to skip
+ * \return PS string, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Low-level function.
+ * </pre>
+ */
+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[Bufsize];
+SARRAY *sa;
+
+ PROCNAME("generateUncompressedPS");
+
+ if (!hexdata)
+ return (char *)ERROR_PTR("hexdata not defined", procName, NULL);
+
+ sa = sarrayCreate(0);
+ sarrayAddString(sa, "%!Adobe-PS", L_COPY);
+ if (boxflag == 0) {
+ snprintf(bigbuf, sizeof(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, "gsave", L_COPY);
+ }
+
+ if (d == 1)
+ sarrayAddString(sa,
+ "{1 exch sub} settransfer %invert binary", L_COPY);
+
+ snprintf(bigbuf, sizeof(bigbuf),
+ "/bpl %d string def %%bpl as a string", psbpl);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf),
+ "%7.2f %7.2f translate %%set image origin in pts", xpt, ypt);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf),
+ "%7.2f %7.2f scale %%set image size in pts", wpt, hpt);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf),
+ "%d %d %d %%image dimensions in pixels", w, h, bps);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ snprintf(bigbuf, sizeof(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,
+ "{currentfile bpl readhexstring pop} image", L_COPY);
+ else /* d == 32 */
+ sarrayAddString(sa,
+ "{currentfile bpl readhexstring pop} false 3 colorimage",
+ L_COPY);
+ } else { /* boxflag == 1 */
+ if (d == 1 || d == 8)
+ sarrayAddString(sa,
+ "{currentfile bpl readhexstring pop} bind image", L_COPY);
+ else /* d == 32 */
+ sarrayAddString(sa,
+ "{currentfile bpl readhexstring pop} bind false 3 colorimage",
+ L_COPY);
+ }
+
+ sarrayAddString(sa, hexdata, L_INSERT);
+
+ if (boxflag == 0)
+ sarrayAddString(sa, "\nshowpage", L_COPY);
+ else /* boxflag == 1 */
+ sarrayAddString(sa, "\ngrestore", L_COPY);
+
+ outstr = sarrayToString(sa, 1);
+ sarrayDestroy(&sa);
+ if (!outstr) L_ERROR("outstr not made\n", procName);
+ return outstr;
+}
+
+
+/*!
+ * \brief getScaledParametersPS()
+ *
+ * \param[in] box [optional] location of image in mils; x,y is LL corner
+ * \param[in] wpix pix width in pixels
+ * \param[in] hpix pix height in pixels
+ * \param[in] res of printer; use 0 for default
+ * \param[in] scale use 1.0 or 0.0 for no scaling
+ * \param[out] pxpt location of llx in pts
+ * \param[out] pypt location of lly in pts
+ * \param[out] pwpt image width in pts
+ * \param[out] phpt image height in pts
+ * \return void no arg checking
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static 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 = DefaultInputRes;
+ 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 < MinRes || res > MaxRes) {
+ L_WARNING("res %d out of bounds; using default res; no scaling\n",
+ procName, res);
+ res = DefaultInputRes;
+ 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;
+}
+
+
+/*!
+ * \brief convertByteToHexAscii()
+ *
+ * \param[in] byteval input byte
+ * \param[out] pnib1, pnib2 two hex ascii characters
+ * \return void
+ */
+static 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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief convertJpegToPSEmbed()
+ *
+ * \param[in] filein input jpeg file
+ * \param[in] fileout output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+convertJpegToPSEmbed(const char *filein,
+ const char *fileout)
+{
+char *outstr;
+l_int32 w, h, nbytes, ret;
+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(NULL, cid, xpt, ypt, wpt, hpt, 1, 1);
+ l_CIDataDestroy(&cid);
+ if (!outstr)
+ return ERROR_INT("outstr not made", procName, 1);
+ nbytes = strlen(outstr);
+
+ ret = l_binaryWrite(fileout, "w", outstr, nbytes);
+ LEPT_FREE(outstr);
+ if (ret) L_ERROR("ps string not written to file\n", procName);
+ return ret;
+}
+
+
+/*!
+ * \brief convertJpegToPS()
+ *
+ * \param[in] filein input jpeg file
+ * \param[in] fileout output ps file
+ * \param[in] operation "w" for write; "a" for append
+ * \param[in] x, y location of LL corner of image, in pixels, relative
+ * to the PostScript origin (0,0) at the LL corner
+ * of the page
+ * \param[in] res resolution of the input image, in ppi;
+ * use 0 for default
+ * \param[in] scale scaling by printer; use 0.0 or 1.0 for no scaling
+ * \param[in] pageno page number; must start with 1; you can use 0
+ * if there is only one page
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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)) {
+ LEPT_FREE(outstr);
+ return ERROR_INT("ps string not written to file", procName, 1);
+ }
+
+ LEPT_FREE(outstr);
+ return 0;
+}
+
+
+/*!
+ * \brief convertJpegToPSString()
+ *
+ * Generates PS string in jpeg format from jpeg file
+ *
+ * \param[in] filein input jpeg file
+ * \param[out] poutstr PS string
+ * \param[out] pnbytes number of bytes in PS string
+ * \param[in] x, y location of LL corner of image, in pixels, relative
+ * to the PostScript origin (0,0) at the LL corner
+ * of the page
+ * \param[in] res resolution of the input image, in ppi;
+ * use 0 for default
+ * \param[in] scale scaling by printer; use 0.0 or 1.0 for no scaling
+ * \param[in] pageno page number; must start with 1; you can use 0
+ * if there is only one page
+ * \param[in] 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
+ *
+ * <pre>
+ * Notes:
+ * (1) For usage, see convertJpegToPS()
+ * </pre>
+ */
+static l_ok
+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 = DefaultInputRes;
+ }
+
+ /* Get scaled location in pts */
+ if (scale == 0.0)
+ scale = 1.0;
+ 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
+ lept_stderr("w = %d, h = %d, bps = %d, spp = %d\n",
+ cid->w, cid->h, cid->bps, cid->spp);
+ lept_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);
+ lept_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(NULL, cid, xpt, ypt, wpt, hpt, pageno, endpage);
+ l_CIDataDestroy(&cid);
+ if (!outstr)
+ return ERROR_INT("outstr not made", procName, 1);
+ *poutstr = outstr;
+ *pnbytes = strlen(outstr);
+ return 0;
+}
+
+
+/*!
+ * \brief generateJpegPS()
+ *
+ * \param[in] filein [optional] input jpeg filename; can be null
+ * \param[in] cid jpeg compressed image data
+ * \param[in] xpt, ypt location of LL corner of image, in pts, relative
+ * to the PostScript origin (0,0) at the LL corner
+ * of the page
+ * \param[in] wpt, hpt rendered image size in pts
+ * \param[in] pageno page number; must start with 1; you can use 0
+ * if there is only one page.
+ * \param[in] 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
+ *
+ * <pre>
+ * Notes:
+ * (1) Low-level function.
+ * </pre>
+ */
+static 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[Bufsize];
+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;
+
+ sa = sarrayCreate(50);
+ sarrayAddString(sa, "%!PS-Adobe-3.0", L_COPY);
+ sarrayAddString(sa, "%%Creator: leptonica", L_COPY);
+ if (filein)
+ snprintf(bigbuf, sizeof(bigbuf), "%%%%Title: %s", filein);
+ else
+ snprintf(bigbuf, sizeof(bigbuf), "%%%%Title: Jpeg compressed PS");
+ sarrayAddString(sa, bigbuf, L_COPY);
+ sarrayAddString(sa, "%%DocumentData: Clean7Bit", L_COPY);
+
+ if (var_PS_WRITE_BOUNDING_BOX == 1) {
+ snprintf(bigbuf, sizeof(bigbuf),
+ "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f",
+ xpt, ypt, xpt + wpt, ypt + hpt);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ }
+
+ sarrayAddString(sa, "%%LanguageLevel: 2", L_COPY);
+ sarrayAddString(sa, "%%EndComments", L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), "%%%%Page: %d %d", pageno, pageno);
+ sarrayAddString(sa, bigbuf, L_COPY);
+
+ sarrayAddString(sa, "save", L_COPY);
+ sarrayAddString(sa,
+ "/RawData currentfile /ASCII85Decode filter def", L_COPY);
+ sarrayAddString(sa, "/Data RawData << >> /DCTDecode filter def", L_COPY);
+
+ snprintf(bigbuf, sizeof(bigbuf),
+ "%7.2f %7.2f translate %%set image origin in pts", xpt, ypt);
+ sarrayAddString(sa, bigbuf, L_COPY);
+
+ snprintf(bigbuf, sizeof(bigbuf),
+ "%7.2f %7.2f scale %%set image size in pts", wpt, hpt);
+ sarrayAddString(sa, bigbuf, L_COPY);
+
+ if (spp == 1)
+ sarrayAddString(sa, "/DeviceGray setcolorspace", L_COPY);
+ else if (spp == 3)
+ sarrayAddString(sa, "/DeviceRGB setcolorspace", L_COPY);
+ else /*spp == 4 */
+ sarrayAddString(sa, "/DeviceCMYK setcolorspace", L_COPY);
+
+ sarrayAddString(sa, "{ << /ImageType 1", L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), " /Width %d", w);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), " /Height %d", h);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf),
+ " /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ sarrayAddString(sa, " /DataSource Data", L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), " /BitsPerComponent %d", bps);
+ sarrayAddString(sa, bigbuf, L_COPY);
+
+ if (spp == 1)
+ sarrayAddString(sa, " /Decode [0 1]", L_COPY);
+ else if (spp == 3)
+ sarrayAddString(sa, " /Decode [0 1 0 1 0 1]", L_COPY);
+ else /* spp == 4 */
+ sarrayAddString(sa, " /Decode [0 1 0 1 0 1 0 1]", L_COPY);
+
+ sarrayAddString(sa, " >> image", L_COPY);
+ sarrayAddString(sa, " Data closefile", L_COPY);
+ sarrayAddString(sa, " RawData flushfile", L_COPY);
+ if (endpage == TRUE)
+ sarrayAddString(sa, " showpage", L_COPY);
+ sarrayAddString(sa, " restore", L_COPY);
+ sarrayAddString(sa, "} 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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief convertG4ToPSEmbed()
+ *
+ * \param[in] filein input tiff file
+ * \param[in] fileout output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+convertG4ToPSEmbed(const char *filein,
+ const char *fileout)
+{
+char *outstr;
+l_int32 w, h, nbytes, ret;
+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(NULL, cid, xpt, ypt, wpt, hpt, 1, 1, 1);
+ l_CIDataDestroy(&cid);
+ if (!outstr)
+ return ERROR_INT("outstr not made", procName, 1);
+ nbytes = strlen(outstr);
+
+ ret = l_binaryWrite(fileout, "w", outstr, nbytes);
+ LEPT_FREE(outstr);
+ if (ret) L_ERROR("ps string not written to file\n", procName);
+ return ret;
+}
+
+
+/*!
+ * \brief convertG4ToPS()
+ *
+ * \param[in] filein input tiff g4 file
+ * \param[in] fileout output ps file
+ * \param[in] operation "w" for write; "a" for append
+ * \param[in] x, y location of LL corner of image, in pixels, relative
+ * to the PostScript origin (0,0) at the LL corner
+ * of the page
+ * \param[in] res resolution of the input image, in ppi; typ. values
+ * are 300 and 600; use 0 for automatic determination
+ * based on image size
+ * \param[in] scale scaling by printer; use 0.0 or 1.0 for no scaling
+ * \param[in] pageno page number; must start with 1; you can use 0
+ * if there is only one page.
+ * \param[in] maskflag boolean: use TRUE if just painting through fg;
+ * FALSE if painting both fg and bg.
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, ret;
+
+ 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);
+
+ ret = l_binaryWrite(fileout, operation, outstr, nbytes);
+ LEPT_FREE(outstr);
+ if (ret)
+ return ERROR_INT("ps string not written to file", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief convertG4ToPSString()
+ *
+ * \param[in] filein input tiff g4 file
+ * \param[out] poutstr PS string
+ * \param[out] pnbytes number of bytes in PS string
+ * \param[in] x, y location of LL corner of image, in pixels, relative
+ * to the PostScript origin (0,0) at the LL corner
+ * of the page
+ * \param[in] res resolution of the input image, in ppi; typ. values
+ * are 300 and 600; use 0 for automatic determination
+ * based on image size
+ * \param[in] scale scaling by printer; use 0.0 or 1.0 for no scaling
+ * \param[in] pageno page number; must start with 1; you can use 0
+ * if there is only one page.
+ * \param[in] maskflag boolean: use TRUE if just painting through fg;
+ * FALSE if painting both fg and bg.
+ * \param[in] 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
+ *
+ * <pre>
+ * Notes:
+ * (1) Generates PS string in G4 compressed tiff format from G4 tiff file.
+ * (2) For usage, see convertG4ToPS().
+ * </pre>
+ */
+static l_ok
+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
+ lept_stderr("w = %d, h = %d, minisblack = %d\n",
+ cid->w, cid->h, cid->minisblack);
+ lept_stderr("comp bytes = %ld, nbytes85 = %ld\n",
+ (unsigned long)cid->nbytescomp, (unsigned long)cid->nbytes85);
+ lept_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(NULL, cid, xpt, ypt, wpt, hpt,
+ maskflag, pageno, endpage);
+ l_CIDataDestroy(&cid);
+ if (!outstr)
+ return ERROR_INT("outstr not made", procName, 1);
+ *poutstr = outstr;
+ *pnbytes = strlen(outstr);
+ return 0;
+}
+
+
+/*!
+ * \brief generateG4PS()
+ *
+ * \param[in] filein [optional] input tiff g4 file; can be null
+ * \param[in] cid g4 compressed image data
+ * \param[in] xpt, ypt location of LL corner of image, in pts, relative
+ * to the PostScript origin (0,0) at the LL corner
+ * of the page
+ * \param[in] wpt, hpt rendered image size in pts
+ * \param[in] maskflag boolean: use TRUE if just painting through fg;
+ * FALSE if painting both fg and bg.
+ * \param[in] pageno page number; must start with 1; you can use 0
+ * if there is only one page.
+ * \param[in] 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
+ *
+ * <pre>
+ * Notes:
+ * (1) Low-level function.
+ * </pre>
+ */
+static 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[Bufsize];
+SARRAY *sa;
+
+ PROCNAME("generateG4PS");
+
+ if (!cid)
+ return (char *)ERROR_PTR("g4 data not defined", procName, NULL);
+ w = cid->w;
+ h = cid->h;
+
+ sa = sarrayCreate(50);
+ sarrayAddString(sa, "%!PS-Adobe-3.0", L_COPY);
+ sarrayAddString(sa, "%%Creator: leptonica", L_COPY);
+ if (filein)
+ snprintf(bigbuf, sizeof(bigbuf), "%%%%Title: %s", filein);
+ else
+ snprintf(bigbuf, sizeof(bigbuf), "%%%%Title: G4 compressed PS");
+ sarrayAddString(sa, bigbuf, L_COPY);
+ sarrayAddString(sa, "%%DocumentData: Clean7Bit", L_COPY);
+
+ if (var_PS_WRITE_BOUNDING_BOX == 1) {
+ snprintf(bigbuf, sizeof(bigbuf),
+ "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f",
+ xpt, ypt, xpt + wpt, ypt + hpt);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ }
+
+ sarrayAddString(sa, "%%LanguageLevel: 2", L_COPY);
+ sarrayAddString(sa, "%%EndComments", L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), "%%%%Page: %d %d", pageno, pageno);
+ sarrayAddString(sa, bigbuf, L_COPY);
+
+ sarrayAddString(sa, "save", L_COPY);
+ sarrayAddString(sa, "100 dict begin", L_COPY);
+
+ snprintf(bigbuf, sizeof(bigbuf),
+ "%7.2f %7.2f translate %%set image origin in pts", xpt, ypt);
+ sarrayAddString(sa, bigbuf, L_COPY);
+
+ snprintf(bigbuf, sizeof(bigbuf),
+ "%7.2f %7.2f scale %%set image size in pts", wpt, hpt);
+ sarrayAddString(sa, bigbuf, L_COPY);
+
+ sarrayAddString(sa, "/DeviceGray setcolorspace", L_COPY);
+
+ sarrayAddString(sa, "{", L_COPY);
+ sarrayAddString(sa,
+ " /RawData currentfile /ASCII85Decode filter def", L_COPY);
+ sarrayAddString(sa, " << ", L_COPY);
+ sarrayAddString(sa, " /ImageType 1", L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), " /Width %d", w);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), " /Height %d", h);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf),
+ " /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ sarrayAddString(sa, " /BitsPerComponent 1", L_COPY);
+ sarrayAddString(sa, " /Interpolate true", L_COPY);
+ if (cid->minisblack)
+ sarrayAddString(sa, " /Decode [1 0]", L_COPY);
+ else /* miniswhite; typical for 1 bpp */
+ sarrayAddString(sa, " /Decode [0 1]", L_COPY);
+ sarrayAddString(sa, " /DataSource RawData", L_COPY);
+ sarrayAddString(sa, " <<", L_COPY);
+ sarrayAddString(sa, " /K -1", L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), " /Columns %d", w);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), " /Rows %d", h);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ sarrayAddString(sa, " >> /CCITTFaxDecode filter", L_COPY);
+ if (maskflag == TRUE) /* just paint through the fg */
+ sarrayAddString(sa, " >> imagemask", L_COPY);
+ else /* Paint full image */
+ sarrayAddString(sa, " >> image", L_COPY);
+ sarrayAddString(sa, " RawData flushfile", L_COPY);
+ if (endpage == TRUE)
+ sarrayAddString(sa, " showpage", L_COPY);
+ sarrayAddString(sa, "}", L_COPY);
+
+ sarrayAddString(sa, "%%BeginData:", L_COPY);
+ sarrayAddString(sa, "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, "%%EndData", L_COPY);
+ sarrayAddString(sa, "end", L_COPY);
+ sarrayAddString(sa, "restore", L_COPY);
+
+ outstr = sarrayToString(sa, 1);
+ sarrayDestroy(&sa);
+ cid->data85 = NULL; /* it has been transferred and destroyed */
+ return outstr;
+}
+
+
+/*-------------------------------------------------------------*
+ * For tiff multipage files *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief convertTiffMultipageToPS()
+ *
+ * \param[in] filein input tiff multipage file
+ * \param[in] fileout output ps file
+ * \param[in] fillfract factor for filling 8.5 x 11 inch page;
+ * use 0.0 for DefaultFillFraction
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+convertTiffMultipageToPS(const char *filein,
+ const char *fileout,
+ l_float32 fillfract)
+{
+char *tempfile;
+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 (fillfract == 0.0)
+ fillfract = DefaultFillFraction;
+
+ for (i = 0; i < npages; i++) {
+ if ((pix = pixReadTiff(filein, i)) == NULL)
+ return ERROR_INT("pix not made", procName, 1);
+
+ pixGetDimensions(pix, &w, &h, NULL);
+ if (w == 1728 && h < w) /* it's a std res fax */
+ pixs = pixScale(pix, 1.0, 2.0);
+ else
+ pixs = pixClone(pix);
+
+ tempfile = l_makeTempFilename();
+ pixWrite(tempfile, pixs, IFF_TIFF_G4);
+ scale = L_MIN(fillfract * 2550 / w, fillfract * 3300 / h);
+ if (i == 0)
+ convertG4ToPS(tempfile, fileout, "w", 0, 0, 300, scale,
+ i + 1, FALSE, TRUE);
+ else
+ convertG4ToPS(tempfile, fileout, "a", 0, 0, 300, scale,
+ i + 1, FALSE, TRUE);
+ lept_rmfile(tempfile);
+ LEPT_FREE(tempfile);
+ pixDestroy(&pix);
+ pixDestroy(&pixs);
+ }
+
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * For flate (gzip) compressed images (e.g., png) *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief convertFlateToPSEmbed()
+ *
+ * \param[in] filein input file -- any format
+ * \param[in] fileout output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+convertFlateToPSEmbed(const char *filein,
+ const char *fileout)
+{
+char *outstr;
+l_int32 w, h, nbytes, ret;
+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(NULL, cid, xpt, ypt, wpt, hpt, 1, 1);
+ l_CIDataDestroy(&cid);
+ if (!outstr)
+ return ERROR_INT("outstr not made", procName, 1);
+ nbytes = strlen(outstr);
+
+ ret = l_binaryWrite(fileout, "w", outstr, nbytes);
+ LEPT_FREE(outstr);
+ if (ret) L_ERROR("ps string not written to file\n", procName);
+ return ret;
+}
+
+
+/*!
+ * \brief convertFlateToPS()
+ *
+ * \param[in] filein input file -- any format
+ * \param[in] fileout output ps file
+ * \param[in] operation "w" for write; "a" for append
+ * \param[in] x, y location of LL corner of image, in pixels, relative
+ * to the PostScript origin (0,0) at the LL corner
+ * of the page
+ * \param[in] res resolution of the input image, in ppi;
+ * use 0 for default
+ * \param[in] scale scaling by printer; use 0.0 or 1.0 for no scaling
+ * \param[in] pageno page number; must start with 1; you can use 0
+ * if there is only one page.
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, ret;
+
+ 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);
+
+ ret = l_binaryWrite(fileout, operation, outstr, nbytes);
+ LEPT_FREE(outstr);
+ if (ret) L_ERROR("ps string not written to file\n", procName);
+ return ret;
+}
+
+
+/*!
+ * \brief convertFlateToPSString()
+ *
+ * Generates level 3 PS string in flate compressed format.
+ *
+ * \param[in] filein input image file
+ * \param[out] poutstr PS string
+ * \param[out] pnbytes number of bytes in PS string
+ * \param[in] x, y location of LL corner of image, in pixels, relative
+ * to the PostScript origin (0,0) at the LL corner
+ * of the page
+ * \param[in] res resolution of the input image, in ppi;
+ * use 0 for default
+ * \param[in] scale scaling by printer; use 0.0 or 1.0 for no scaling
+ * \param[in] pageno page number; must start with 1; you can use 0
+ * if there is only one page.
+ * \param[in] 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
+ *
+ * <pre>
+ * 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()
+ * </pre>
+ */
+static l_ok
+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 = DefaultInputRes;
+ }
+ 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
+ lept_stderr("w = %d, h = %d, bps = %d, spp = %d\n",
+ cid->w, cid->h, cid->bps, cid->spp);
+ lept_stderr("uncomp bytes = %ld, comp bytes = %ld, nbytes85 = %ld\n",
+ (unsigned long)cid->nbytes, (unsigned long)cid->nbytescomp,
+ (unsigned long)cid->nbytes85);
+ lept_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(NULL, cid, xpt, ypt, wpt, hpt, pageno, endpage);
+ l_CIDataDestroy(&cid);
+ if (!outstr)
+ return ERROR_INT("outstr not made", procName, 1);
+ *poutstr = outstr;
+ *pnbytes = strlen(outstr);
+ return 0;
+}
+
+
+/*!
+ * \brief generateFlatePS()
+ *
+ * \param[in] filein [optional] input filename; can be null
+ * \param[in] cid flate compressed image data
+ * \param[in] xpt, ypt location of LL corner of image, in pts, relative
+ * to the PostScript origin (0,0) at the LL corner
+ * of the page
+ * \param[in] wpt, hpt rendered image size in pts
+ * \param[in] pageno page number; must start with 1; you can use 0
+ * if there is only one page
+ * \param[in] 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
+ */
+static 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[Bufsize];
+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;
+
+ sa = sarrayCreate(50);
+ sarrayAddString(sa, "%!PS-Adobe-3.0 EPSF-3.0", L_COPY);
+ sarrayAddString(sa, "%%Creator: leptonica", L_COPY);
+ if (filein)
+ snprintf(bigbuf, sizeof(bigbuf), "%%%%Title: %s", filein);
+ else
+ snprintf(bigbuf, sizeof(bigbuf), "%%%%Title: Flate compressed PS");
+ sarrayAddString(sa, bigbuf, L_COPY);
+ sarrayAddString(sa, "%%DocumentData: Clean7Bit", L_COPY);
+
+ if (var_PS_WRITE_BOUNDING_BOX == 1) {
+ snprintf(bigbuf, sizeof(bigbuf),
+ "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f",
+ xpt, ypt, xpt + wpt, ypt + hpt);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ }
+
+ sarrayAddString(sa, "%%LanguageLevel: 3", L_COPY);
+ sarrayAddString(sa, "%%EndComments", L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), "%%%%Page: %d %d", pageno, pageno);
+ sarrayAddString(sa, bigbuf, L_COPY);
+
+ sarrayAddString(sa, "save", L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf),
+ "%7.2f %7.2f translate %%set image origin in pts", xpt, ypt);
+ sarrayAddString(sa, bigbuf, L_COPY);
+
+ snprintf(bigbuf, sizeof(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) {
+ snprintf(bigbuf, sizeof(bigbuf),
+ "[ /Indexed /DeviceRGB %d %%set colormap type/size",
+ cid->ncolors - 1);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ sarrayAddString(sa, " <~", L_COPY);
+ sarrayAddString(sa, cid->cmapdata85, L_INSERT);
+ sarrayAddString(sa, " ] setcolorspace", L_COPY);
+ } else if (spp == 1) {
+ sarrayAddString(sa, "/DeviceGray setcolorspace", L_COPY);
+ } else { /* spp == 3 */
+ sarrayAddString(sa, "/DeviceRGB setcolorspace", L_COPY);
+ }
+
+ sarrayAddString(sa,
+ "/RawData currentfile /ASCII85Decode filter def", L_COPY);
+ sarrayAddString(sa,
+ "/Data RawData << >> /FlateDecode filter def", L_COPY);
+
+ sarrayAddString(sa, "{ << /ImageType 1", L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), " /Width %d", w);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), " /Height %d", h);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf), " /BitsPerComponent %d", bps);
+ sarrayAddString(sa, bigbuf, L_COPY);
+ snprintf(bigbuf, sizeof(bigbuf),
+ " /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h);
+ sarrayAddString(sa, bigbuf, L_COPY);
+
+ if (cid->cmapdata85) {
+ sarrayAddString(sa, " /Decode [0 255]", L_COPY);
+ } else if (spp == 1) {
+ if (bps == 1) /* miniswhite photometry */
+ sarrayAddString(sa, " /Decode [1 0]", L_COPY);
+ else /* bps > 1 */
+ sarrayAddString(sa, " /Decode [0 1]", L_COPY);
+ } else { /* spp == 3 */
+ sarrayAddString(sa, " /Decode [0 1 0 1 0 1]", L_COPY);
+ }
+
+ sarrayAddString(sa, " /DataSource Data", L_COPY);
+ sarrayAddString(sa, " >> image", L_COPY);
+ sarrayAddString(sa, " Data closefile", L_COPY);
+ sarrayAddString(sa, " RawData flushfile", L_COPY);
+ if (endpage == TRUE)
+ sarrayAddString(sa, " showpage", L_COPY);
+ sarrayAddString(sa, " restore", L_COPY);
+ sarrayAddString(sa, "} 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixWriteMemPS()
+ *
+ * \param[out] pdata data of tiff compressed image
+ * \param[out] psize size of returned data
+ * \param[in] pix
+ * \param[in] box [optional]
+ * \param[in] res can use 0 for default of 300 ppi
+ * \param[in] scale to prevent scaling, use either 1.0 or 0.0
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixWriteStringPS() for usage.
+ * (2) This is just a wrapper for pixWriteStringPS(), which
+ * writes uncompressed image data to memory.
+ * </pre>
+ */
+l_ok
+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 *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief getResLetterPage()
+ *
+ * \param[in] w image width, pixels
+ * \param[in] h image height, pixels
+ * \param[in] fillfract fraction in linear dimension of full page,
+ * not to be exceeded; use 0 for default
+ * \return resolution
+ */
+l_int32
+getResLetterPage(l_int32 w,
+ l_int32 h,
+ l_float32 fillfract)
+{
+l_int32 resw, resh, res;
+
+ if (fillfract == 0.0)
+ fillfract = DefaultFillFraction;
+ resw = (l_int32)((w * 72.) / (LetterWidth * fillfract));
+ resh = (l_int32)((h * 72.) / (LetterHeight * fillfract));
+ res = L_MAX(resw, resh);
+ return res;
+}
+
+
+/*!
+ * \brief getResA4Page()
+ *
+ * \param[in] w image width, pixels
+ * \param[in] h image height, pixels
+ * \param[in] fillfract fraction in linear dimension of full page,
+ * not to be exceeded; use 0 for default
+ * \return resolution
+ */
+l_int32
+getResA4Page(l_int32 w,
+ l_int32 h,
+ l_float32 fillfract)
+{
+l_int32 resw, resh, res;
+
+ if (fillfract == 0.0)
+ fillfract = DefaultFillFraction;
+ resw = (l_int32)((w * 72.) / (A4Width * fillfract));
+ resh = (l_int32)((h * 72.) / (A4Height * 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/leptonica/src/psio2stub.c b/leptonica/src/psio2stub.c
new file mode 100644
index 00000000..8c96777e
--- /dev/null
+++ b/leptonica/src/psio2stub.c
@@ -0,0 +1,160 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file psio2stub.c
+ * <pre>
+ *
+ * Stubs for psio2.c functions
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if !USE_PSIO /* defined in environ.h */
+/* --------------------------------------------*/
+
+l_ok pixWritePSEmbed(const char *filein, const char *fileout)
+{
+ return ERROR_INT("function not present", "pixWritePSEmbed", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok convertJpegToPSEmbed(const char *filein, const char *fileout)
+{
+ return ERROR_INT("function not present", "convertJpegToPSEmbed", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok convertG4ToPSEmbed(const char *filein, const char *fileout)
+{
+ return ERROR_INT("function not present", "convertG4ToPSEmbed", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok convertTiffMultipageToPS(const char *filein, const char *fileout,
+ l_float32 fillfract)
+{
+ return ERROR_INT("function not present", "convertTiffMultipageToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok convertFlateToPSEmbed(const char *filein, const char *fileout)
+{
+ return ERROR_INT("function not present", "convertFlateToPSEmbed", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok 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);
+}
+
+/* ----------------------------------------------------------------------*/
+
+void l_psWriteBoundingBox(l_int32 flag)
+{
+ L_ERROR("function not present\n", "l_psWriteBoundingBox");
+ return;
+}
+
+/* --------------------------------------------*/
+#endif /* !USE_PSIO */
+/* --------------------------------------------*/
diff --git a/leptonica/src/ptabasic.c b/leptonica/src/ptabasic.c
new file mode 100644
index 00000000..25b23a70
--- /dev/null
+++ b/leptonica/src/ptabasic.c
@@ -0,0 +1,1607 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file ptabasic.c
+ * <pre>
+ *
+ * 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()
+ * PTA *ptaReadMem()
+ * l_int32 ptaWriteDebug()
+ * l_int32 ptaWrite()
+ * l_int32 ptaWriteStream()
+ * l_int32 ptaWriteMem()
+ *
+ * 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()
+ * PTAA *ptaaReadMem()
+ * l_int32 ptaaWrite()
+ * l_int32 ptaaWriteStream()
+ * l_int32 ptaaWriteMem()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_uint32 MaxArraySize = 100000000; /* 100 million */
+static const l_uint32 MaxPtrArraySize = 10000000; /* 10 million */
+static const l_int32 InitialArraySize = 50; /*!< n'importe quoi */
+
+ /* Static functions */
+static l_int32 ptaExtendArrays(PTA *pta);
+static l_int32 ptaaExtendArray(PTAA *ptaa);
+
+/*---------------------------------------------------------------------*
+ * Pta creation, destruction, copy, clone *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaCreate()
+ *
+ * \param[in] n initial array sizes
+ * \return pta, or NULL on error.
+ */
+PTA *
+ptaCreate(l_int32 n)
+{
+PTA *pta;
+
+ PROCNAME("ptaCreate");
+
+ if (n <= 0 || n > MaxArraySize)
+ n = InitialArraySize;
+
+ pta = (PTA *)LEPT_CALLOC(1, sizeof(PTA));
+ pta->n = 0;
+ pta->nalloc = n;
+ ptaChangeRefcount(pta, 1); /* sets to 1 */
+ pta->x = (l_float32 *)LEPT_CALLOC(n, sizeof(l_float32));
+ pta->y = (l_float32 *)LEPT_CALLOC(n, sizeof(l_float32));
+ if (!pta->x || !pta->y) {
+ ptaDestroy(&pta);
+ return (PTA *)ERROR_PTR("x and y arrays not both made", procName, NULL);
+ }
+
+ return pta;
+}
+
+
+/*!
+ * \brief ptaCreateFromNuma()
+ *
+ * \param[in] nax [optional] can be null
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaDestroy()
+ *
+ * \param[in,out] ppta will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Decrements the ref count and, if 0, destroys the pta.
+ * (2) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaCopy()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaCopyRange()
+ *
+ * \param[in] ptas
+ * \param[in] istart starting index in ptas
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaClone()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaEmpty()
+ *
+ * \param[in] pta
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * This only resets the Pta::n field, for reuse
+ * </pre>
+ */
+l_ok
+ptaEmpty(PTA *pta)
+{
+ PROCNAME("ptaEmpty");
+
+ if (!pta)
+ return ERROR_INT("ptad not defined", procName, 1);
+ pta->n = 0;
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pta array extension *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaAddPt()
+ *
+ * \param[in] pta
+ * \param[in] x, y
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ if (ptaExtendArrays(pta))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+
+ pta->x[n] = x;
+ pta->y[n] = y;
+ pta->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief ptaExtendArrays()
+ *
+ * \param[in] pta
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The max number of points is 100M.
+ * </pre>
+ */
+static l_int32
+ptaExtendArrays(PTA *pta)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("ptaExtendArrays");
+
+ if (!pta)
+ return ERROR_INT("pta not defined", procName, 1);
+ if (pta->nalloc > MaxArraySize) /* belt & suspenders */
+ return ERROR_INT("pta has too many ptrs", procName, 1);
+ oldsize = pta->nalloc * sizeof(l_float32);
+ newsize = 2 * oldsize;
+ if (newsize > 4 * MaxArraySize) /* array of 100M floats */
+ return ERROR_INT("newsize > 400 MB; too large", procName, 1);
+
+ if ((pta->x = (l_float32 *)reallocNew((void **)&pta->x,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new x array not returned", procName, 1);
+ if ((pta->y = (l_float32 *)reallocNew((void **)&pta->y,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new y array not returned", procName, 1);
+
+ pta->nalloc *= 2;
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Pta insertion and removal *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaInsertPt()
+ *
+ * \param[in] pta
+ * \param[in] index at which pt is to be inserted
+ * \param[in] x, y point values
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n);
+ return 1;
+ }
+
+ if (n > pta->nalloc) {
+ if (ptaExtendArrays(pta))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ 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;
+}
+
+
+/*!
+ * \brief ptaRemovePt()
+ *
+ * \param[in] pta
+ * \param[in] index of point to be removed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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) {
+ L_ERROR("index %d not in [0,...,%d]\n", procName, index, n - 1);
+ return 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;
+}
+
+
+/*!
+ * \brief ptaGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaGetPt()
+ *
+ * \param[in] pta
+ * \param[in] index into arrays
+ * \param[out] px [optional] float x value
+ * \param[out] py [optional] float y value
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptaGetIPt()
+ *
+ * \param[in] pta
+ * \param[in] index into arrays
+ * \param[out] px [optional] integer x value
+ * \param[out] py [optional] integer y value
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptaSetPt()
+ *
+ * \param[in] pta
+ * \param[in] index into arrays
+ * \param[in] x, y
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptaGetArrays()
+ *
+ * \param[in] pta
+ * \param[out] pnax [optional] numa of x array
+ * \param[out] pnay [optional] numa of y array
+ * \return 0 if OK; 1 on error or if pta is empty
+ *
+ * <pre>
+ * Notes:
+ * (1) This copies the internal arrays into new Numas.
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaRead()
+ *
+ * \param[in] 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);
+ pta = ptaReadStream(fp);
+ fclose(fp);
+ if (!pta)
+ return (PTA *)ERROR_PTR("pta not read", procName, NULL);
+ return pta;
+}
+
+
+/*!
+ * \brief ptaReadStream()
+ *
+ * \param[in] fp file stream
+ * \return pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is OK for the pta to be empty (n == 0).
+ * </pre>
+
+ */
+PTA *
+ptaReadStream(FILE *fp)
+{
+char typestr[128]; /* hardcoded below in fscanf */
+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 = %127s\n", &n, typestr) != 2)
+ return (PTA *)ERROR_PTR("not a pta file", procName, NULL);
+ if (n < 0)
+ return (PTA *)ERROR_PTR("num pts <= 0", procName, NULL);
+ if (n > MaxArraySize)
+ return (PTA *)ERROR_PTR("too many pts", procName, NULL);
+ if (n == 0) L_INFO("the pta is empty\n", procName);
+
+ 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) {
+ ptaDestroy(&pta);
+ 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) {
+ ptaDestroy(&pta);
+ return (PTA *)ERROR_PTR("error reading ints", procName, NULL);
+ }
+ ptaAddPt(pta, ix, iy);
+ }
+ }
+
+ return pta;
+}
+
+
+/*!
+ * \brief ptaReadMem()
+ *
+ * \param[in] data serialization in ascii
+ * \param[in] size of data in bytes; can use strlen to get it
+ * \return pta, or NULL on error
+ */
+PTA *
+ptaReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+PTA *pta;
+
+ PROCNAME("ptaReadMem");
+
+ if (!data)
+ return (PTA *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (PTA *)ERROR_PTR("stream not opened", procName, NULL);
+
+ pta = ptaReadStream(fp);
+ fclose(fp);
+ if (!pta) L_ERROR("pta not read\n", procName);
+ return pta;
+}
+
+
+/*!
+ * \brief ptaWriteDebug()
+ *
+ * \param[in] filename
+ * \param[in] pta
+ * \param[in] type 0 for float values; 1 for integer values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Debug version, intended for use in the library when writing
+ * to files in a temp directory with names that are compiled in.
+ * This is used instead of ptaWrite() for all such library calls.
+ * (2) The global variable LeptDebugOK defaults to 0, and can be set
+ * or cleared by the function setLeptDebugOK().
+ * </pre>
+ */
+l_ok
+ptaWriteDebug(const char *filename,
+ PTA *pta,
+ l_int32 type)
+{
+ PROCNAME("ptaWriteDebug");
+
+ if (LeptDebugOK) {
+ return ptaWrite(filename, pta, type);
+ } else {
+ L_INFO("write to named temp file %s is disabled\n", procName, filename);
+ return 0;
+ }
+}
+
+
+/*!
+ * \brief ptaWrite()
+ *
+ * \param[in] filename
+ * \param[in] pta
+ * \param[in] type 0 for float values; 1 for integer values
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+ptaWrite(const char *filename,
+ PTA *pta,
+ l_int32 type)
+{
+l_int32 ret;
+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);
+ ret = ptaWriteStream(fp, pta, type);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("pta not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief ptaWriteStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] pta
+ * \param[in] type 0 for float values; 1 for integer values
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptaWriteMem()
+ *
+ * \param[out] pdata data of serialized pta; ascii
+ * \param[out] psize size of returned data
+ * \param[in] pta
+ * \param[in] type 0 for float values; 1 for integer values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a pta in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+ptaWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ PTA *pta,
+ l_int32 type)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("ptaWriteMem");
+
+ 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 (!pta)
+ return ERROR_INT("pta not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = ptaWriteStream(fp, pta, type);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = ptaWriteStream(fp, pta, type);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*---------------------------------------------------------------------*
+ * PTAA creation, destruction *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaaCreate()
+ *
+ * \param[in] n initial number of ptrs
+ * \return ptaa, or NULL on error
+ */
+PTAA *
+ptaaCreate(l_int32 n)
+{
+PTAA *ptaa;
+
+ PROCNAME("ptaaCreate");
+
+ if (n <= 0 || n > MaxPtrArraySize)
+ n = InitialArraySize;
+
+ ptaa = (PTAA *)LEPT_CALLOC(1, sizeof(PTAA));
+ ptaa->n = 0;
+ ptaa->nalloc = n;
+ if ((ptaa->pta = (PTA **)LEPT_CALLOC(n, sizeof(PTA *))) == NULL) {
+ ptaaDestroy(&ptaa);
+ return (PTAA *)ERROR_PTR("pta ptrs not made", procName, NULL);
+ }
+ return ptaa;
+}
+
+
+/*!
+ * \brief ptaaDestroy()
+ *
+ * \param[in,out] pptaa will be set to null before returning
+ * \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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * PTAA array extension *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaaAddPta()
+ *
+ * \param[in] ptaa
+ * \param[in] pta to be added
+ * \param[in] copyflag L_INSERT, L_COPY, L_CLONE
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ if (ptaaExtendArray(ptaa)) {
+ if (copyflag != L_INSERT)
+ ptaDestroy(&ptac);
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ }
+
+ ptaa->pta[n] = ptac;
+ ptaa->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief ptaaExtendArray()
+ *
+ * \param[in] ptaa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This doubles the pta ptr array size.
+ * (2) The max number of pta ptrs is 10M.
+ * </pre>
+ *
+ */
+static l_int32
+ptaaExtendArray(PTAA *ptaa)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("ptaaExtendArray");
+
+ if (!ptaa)
+ return ERROR_INT("ptaa not defined", procName, 1);
+ oldsize = ptaa->nalloc * sizeof(PTA *);
+ newsize = 2 * oldsize;
+ if (newsize > 8 * MaxPtrArraySize)
+ return ERROR_INT("newsize > 80 MB; too large", procName, 1);
+
+ if ((ptaa->pta = (PTA **)reallocNew((void **)&ptaa->pta,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+
+ ptaa->nalloc *= 2;
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Ptaa accessors *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaaGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaaGetPta()
+ *
+ * \param[in] ptaa
+ * \param[in] index to the i-th pta
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief ptaaGetPt()
+ *
+ * \param[in] ptaa
+ * \param[in] ipta to the i-th pta
+ * \param[in] jpt index to the j-th pt in the pta
+ * \param[out] px [optional] float x value
+ * \param[out] py [optional] float y value
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaaInitFull()
+ *
+ * \param[in] ptaa can have non-null ptrs in the ptr array
+ * \param[in] pta to be replicated into the entire ptr array
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptaaReplacePta()
+ *
+ * \param[in] ptaa
+ * \param[in] index to the index-th pta
+ * \param[in] pta insert and replace any existing one
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Any existing pta is destroyed, and the input one
+ * is inserted in its place.
+ * (2) If %index is invalid, return 1 (error)
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptaaAddPt()
+ *
+ * \param[in] ptaa
+ * \param[in] ipta to the i-th pta
+ * \param[in] x,y point coordinates
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptaaTruncate()
+ *
+ * \param[in] ptaa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaaRead()
+ *
+ * \param[in] 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);
+ ptaa = ptaaReadStream(fp);
+ fclose(fp);
+ if (!ptaa)
+ return (PTAA *)ERROR_PTR("ptaa not read", procName, NULL);
+ return ptaa;
+}
+
+
+/*!
+ * \brief ptaaReadStream()
+ *
+ * \param[in] fp file stream
+ * \return ptaa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) It is OK for the ptaa to be empty (n == 0).
+ * </pre>
+ */
+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 (n < 0)
+ return (PTAA *)ERROR_PTR("num pta ptrs <= 0", procName, NULL);
+ if (n > MaxPtrArraySize)
+ return (PTAA *)ERROR_PTR("too many pta ptrs", procName, NULL);
+ if (n == 0) L_INFO("the ptaa is empty\n", procName);
+
+ 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) {
+ ptaaDestroy(&ptaa);
+ return (PTAA *)ERROR_PTR("error reading pta", procName, NULL);
+ }
+ ptaaAddPta(ptaa, pta, L_INSERT);
+ }
+
+ return ptaa;
+}
+
+
+/*!
+ * \brief ptaaReadMem()
+ *
+ * \param[in] data serialization in ascii
+ * \param[in] size of data in bytes; can use strlen to get it
+ * \return ptaa, or NULL on error
+ */
+PTAA *
+ptaaReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+PTAA *ptaa;
+
+ PROCNAME("ptaaReadMem");
+
+ if (!data)
+ return (PTAA *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (PTAA *)ERROR_PTR("stream not opened", procName, NULL);
+
+ ptaa = ptaaReadStream(fp);
+ fclose(fp);
+ if (!ptaa) L_ERROR("ptaa not read\n", procName);
+ return ptaa;
+}
+
+
+/*!
+ * \brief ptaaWriteDebug()
+ *
+ * \param[in] filename
+ * \param[in] ptaa
+ * \param[in] type 0 for float values; 1 for integer values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Debug version, intended for use in the library when writing
+ * to files in a temp directory with names that are compiled in.
+ * This is used instead of ptaaWrite() for all such library calls.
+ * (2) The global variable LeptDebugOK defaults to 0, and can be set
+ * or cleared by the function setLeptDebugOK().
+ * </pre>
+ */
+l_ok
+ptaaWriteDebug(const char *filename,
+ PTAA *ptaa,
+ l_int32 type)
+{
+ PROCNAME("ptaaWriteDebug");
+
+ if (LeptDebugOK) {
+ return ptaaWrite(filename, ptaa, type);
+ } else {
+ L_INFO("write to named temp file %s is disabled\n", procName, filename);
+ return 0;
+ }
+}
+
+
+/*!
+ * \brief ptaaWrite()
+ *
+ * \param[in] filename
+ * \param[in] ptaa
+ * \param[in] type 0 for float values; 1 for integer values
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+ptaaWrite(const char *filename,
+ PTAA *ptaa,
+ l_int32 type)
+{
+l_int32 ret;
+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);
+ ret = ptaaWriteStream(fp, ptaa, type);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("ptaa not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief ptaaWriteStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] ptaa
+ * \param[in] type 0 for float values; 1 for integer values
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptaaWriteMem()
+ *
+ * \param[out] pdata data of serialized ptaa; ascii
+ * \param[out] psize size of returned data
+ * \param[in] ptaa
+ * \param[in] type 0 for float values; 1 for integer values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes %ptaa in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+ptaaWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ PTAA *ptaa,
+ l_int32 type)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("ptaaWriteMem");
+
+ 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 (!ptaa)
+ return ERROR_INT("ptaa not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = ptaaWriteStream(fp, ptaa, type);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = ptaaWriteStream(fp, ptaa, type);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
diff --git a/leptonica/src/ptafunc1.c b/leptonica/src/ptafunc1.c
new file mode 100644
index 00000000..bffa2ddd
--- /dev/null
+++ b/leptonica/src/ptafunc1.c
@@ -0,0 +1,2667 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file ptafunc1.c
+ * <pre>
+ *
+ * --------------------------------------
+ * This file has these Pta utilities:
+ * - simple rearrangements
+ * - geometric analysis
+ * - min/max and filtering
+ * - least squares fitting
+ * - interconversions with Pix and Numa
+ * - display into a pix
+ * --------------------------------------
+ *
+ * Simple rearrangements
+ * PTA *ptaSubsample()
+ * l_int32 ptaJoin()
+ * l_int32 ptaaJoin()
+ * PTA *ptaReverse()
+ * PTA *ptaTranspose()
+ * PTA *ptaCyclicPerm()
+ * PTA *ptaSelectRange()
+ *
+ * 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()
+ *
+ * Min/max and filtering
+ * l_int32 ptaGetMinMax()
+ * PTA *ptaSelectByValue()
+ * PTA *ptaCropToMask()
+ *
+ * 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 *ptaaIndexLabeledPixels()
+ * PTA *ptaGetNeighborPixLocs()
+ *
+ * Interconversion with Numa
+ * PTA *numaConvertToPta1()
+ * PTA *numaConvertToPta2()
+ * l_int32 ptaConvertToNuma()
+ *
+ * Display Pta and Ptaa
+ * PIX *pixDisplayPta()
+ * PIX *pixDisplayPtaaPattern()
+ * PIX *pixDisplayPtaPattern()
+ * PTA *ptaReplicatePattern()
+ * PIX *pixDisplayPtaa()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif /* M_PI */
+
+/*---------------------------------------------------------------------*
+ * Simple rearrangements *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaSubsample()
+ *
+ * \param[in] ptas
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaJoin()
+ *
+ * \param[in] ptad dest pta; add to this one
+ * \param[in] ptas source pta; add from this one
+ * \param[in] istart starting index in ptas
+ * \param[in] iend ending index in ptas; use -1 to cat all
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptaaJoin()
+ *
+ * \param[in] ptaad dest ptaa; add to this one
+ * \param[in] ptaas source ptaa; add from this one
+ * \param[in] istart starting index in ptaas
+ * \param[in] iend ending index in ptaas; use -1 to cat all
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptaReverse()
+ *
+ * \param[in] ptas
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaTranspose()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaCyclicPerm()
+ *
+ * \param[in] ptas
+ * \param[in] xs, ys start point; must be in ptas
+ * \return ptad cyclic permutation, starting and ending at (xs, ys,
+ * or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaSelectRange()
+ *
+ * \param[in] ptas
+ * \param[in] first use 0 to select from the beginning
+ * \param[in] last use -1 to select to the end
+ * \return ptad, or NULL on error
+ */
+PTA *
+ptaSelectRange(PTA *ptas,
+ l_int32 first,
+ l_int32 last)
+{
+l_int32 n, npt, i;
+l_float32 x, y;
+PTA *ptad;
+
+ PROCNAME("ptaSelectRange");
+
+ if (!ptas)
+ return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+ if ((n = ptaGetCount(ptas)) == 0) {
+ L_WARNING("ptas is empty\n", procName);
+ return ptaCopy(ptas);
+ }
+ first = L_MAX(0, first);
+ if (last < 0) last = n - 1;
+ if (first >= n)
+ return (PTA *)ERROR_PTR("invalid first", procName, NULL);
+ if (last >= n) {
+ L_WARNING("last = %d is beyond max index = %d; adjusting\n",
+ procName, last, n - 1);
+ last = n - 1;
+ }
+ if (first > last)
+ return (PTA *)ERROR_PTR("first > last", procName, NULL);
+
+ npt = last - first + 1;
+ ptad = ptaCreate(npt);
+ for (i = first; i <= last; i++) {
+ ptaGetPt(ptas, i, &x, &y);
+ ptaAddPt(ptad, x, y);
+ }
+ return ptad;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Geometric *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaGetBoundingRegion()
+ *
+ * \param[in] pta
+ * \return box, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief ptaGetRange()
+ *
+ * \param[in] pta
+ * \param[out] pminx [optional] min value of x
+ * \param[out] pmaxx [optional] max value of x
+ * \param[out] pminy [optional] min value of y
+ * \param[out] pmaxy [optional] max value of y
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptaGetInsideBox()
+ *
+ * \param[in] ptas input pts
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief pixFindCornerPixels()
+ *
+ * \param[in] pixs 1 bpp
+ * \return pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Finds the 4 corner-most pixels, as defined by a search
+ * inward from each corner, using a 45 degree line.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaContainsPt()
+ *
+ * \param[in] pta
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaTestIntersection()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaTransform()
+ *
+ * \param[in] ptas
+ * \param[in] shiftx, shifty
+ * \param[in] scalex, scaley
+ * \return pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Shift first, then scale.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaPtInsidePolygon()
+ *
+ * \param[in] pta vertices of a polygon
+ * \param[in] x, y point to be tested
+ * \param[out] pinside 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;
+}
+
+
+/*!
+ * \brief l_angleBetweenVectors()
+ *
+ * \param[in] x1, y1 end point of first vector
+ * \param[in] x2, y2 end point of second vector
+ * \return angle radians, or 0.0 on error
+ *
+ * <pre>
+ * 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].
+ * </pre>
+ */
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Min/max and filtering *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaGetMinMax()
+ *
+ * \param[in] pta
+ * \param[out] pxmin [optional] min of x
+ * \param[out] pymin [optional] min of y
+ * \param[out] pxmax [optional] max of x
+ * \param[out] pymax [optional] max of y
+ * \return 0 if OK, 1 on error. If pta is empty, requested
+ * values are returned as -1.0.
+ */
+l_ok
+ptaGetMinMax(PTA *pta,
+ l_float32 *pxmin,
+ l_float32 *pymin,
+ l_float32 *pxmax,
+ l_float32 *pymax)
+{
+l_int32 i, n;
+l_float32 x, y, xmin, ymin, xmax, ymax;
+
+ PROCNAME("ptaGetMinMax");
+
+ if (pxmin) *pxmin = -1.0;
+ if (pymin) *pymin = -1.0;
+ if (pxmax) *pxmax = -1.0;
+ if (pymax) *pymax = -1.0;
+ if (!pta)
+ return ERROR_INT("pta not defined", procName, 1);
+ if (!pxmin && !pxmax && !pymin && !pymax)
+ return ERROR_INT("no output requested", procName, 1);
+ if ((n = ptaGetCount(pta)) == 0) {
+ L_WARNING("pta is empty\n", procName);
+ return 0;
+ }
+
+ xmin = ymin = 1.0e20f;
+ xmax = ymax = -1.0e20f;
+ for (i = 0; i < n; i++) {
+ ptaGetPt(pta, i, &x, &y);
+ if (x < xmin) xmin = x;
+ if (y < ymin) ymin = y;
+ if (x > xmax) xmax = x;
+ if (y > ymax) ymax = y;
+ }
+ if (pxmin) *pxmin = xmin;
+ if (pymin) *pymin = ymin;
+ if (pxmax) *pxmax = xmax;
+ if (pymax) *pymax = ymax;
+ return 0;
+}
+
+
+/*!
+ * \brief ptaSelectByValue()
+ *
+ * \param[in] ptas
+ * \param[in] xth, yth threshold values
+ * \param[in] type L_SELECT_XVAL, L_SELECT_YVAL,
+ * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH
+ * \param[in] relation L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \return ptad filtered set, or NULL on error
+ */
+PTA *
+ptaSelectByValue(PTA *ptas,
+ l_float32 xth,
+ l_float32 yth,
+ l_int32 type,
+ l_int32 relation)
+{
+l_int32 i, n;
+l_float32 x, y;
+PTA *ptad;
+
+ PROCNAME("ptaSelectByValue");
+
+ if (!ptas)
+ return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+ if (ptaGetCount(ptas) == 0) {
+ L_WARNING("ptas is empty\n", procName);
+ return ptaCopy(ptas);
+ }
+ if (type != L_SELECT_XVAL && type != L_SELECT_YVAL &&
+ type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH)
+ return (PTA *)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 (PTA *)ERROR_PTR("invalid relation", procName, NULL);
+
+ n = ptaGetCount(ptas);
+ ptad = ptaCreate(n);
+ for (i = 0; i < n; i++) {
+ ptaGetPt(ptas, i, &x, &y);
+ if (type == L_SELECT_XVAL) {
+ if ((relation == L_SELECT_IF_LT && x < xth) ||
+ (relation == L_SELECT_IF_GT && x > xth) ||
+ (relation == L_SELECT_IF_LTE && x <= xth) ||
+ (relation == L_SELECT_IF_GTE && x >= xth))
+ ptaAddPt(ptad, x, y);
+ } else if (type == L_SELECT_YVAL) {
+ if ((relation == L_SELECT_IF_LT && y < yth) ||
+ (relation == L_SELECT_IF_GT && y > yth) ||
+ (relation == L_SELECT_IF_LTE && y <= yth) ||
+ (relation == L_SELECT_IF_GTE && y >= yth))
+ ptaAddPt(ptad, x, y);
+ } else if (type == L_SELECT_IF_EITHER) {
+ if (((relation == L_SELECT_IF_LT) && (x < xth || y < yth)) ||
+ ((relation == L_SELECT_IF_GT) && (x > xth || y > yth)) ||
+ ((relation == L_SELECT_IF_LTE) && (x <= xth || y <= yth)) ||
+ ((relation == L_SELECT_IF_GTE) && (x >= xth || y >= yth)))
+ ptaAddPt(ptad, x, y);
+ } else { /* L_SELECT_IF_BOTH */
+ if (((relation == L_SELECT_IF_LT) && (x < xth && y < yth)) ||
+ ((relation == L_SELECT_IF_GT) && (x > xth && y > yth)) ||
+ ((relation == L_SELECT_IF_LTE) && (x <= xth && y <= yth)) ||
+ ((relation == L_SELECT_IF_GTE) && (x >= xth && y >= yth)))
+ ptaAddPt(ptad, x, y);
+ }
+ }
+
+ return ptad;
+}
+
+
+/*!
+ * \brief ptaCropToMask()
+ *
+ * \param[in] ptas input pta
+ * \param[in] pixm 1 bpp mask
+ * \return ptad with only pts under the mask fg, or NULL on error
+ */
+PTA *
+ptaCropToMask(PTA *ptas,
+ PIX *pixm)
+{
+l_int32 i, n, x, y;
+l_uint32 val;
+PTA *ptad;
+
+ PROCNAME("ptaCropToMask");
+
+ if (!ptas)
+ return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+ if (!pixm || pixGetDepth(pixm) != 1)
+ return (PTA *)ERROR_PTR("pixm undefined or not 1 bpp", procName, NULL);
+ if (ptaGetCount(ptas) == 0) {
+ L_INFO("ptas is empty\n", procName);
+ return ptaCopy(ptas);
+ }
+
+ n = ptaGetCount(ptas);
+ ptad = ptaCreate(n);
+ for (i = 0; i < n; i++) {
+ ptaGetIPt(ptas, i, &x, &y);
+ pixGetPixel(pixm, x, y, &val);
+ if (val == 1)
+ ptaAddPt(ptad, x, y);
+ }
+ return ptad;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Least Squares Fit *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaGetLinearLSF()
+ *
+ * \param[in] pta
+ * \param[out] pa [optional] slope a of least square fit: y = ax + b
+ * \param[out] pb [optional] intercept b of least square fit
+ * \param[out] pnafit [optional] numa of least square fit
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * Get the nax array using ptaGetArrays(pta, &nax, NULL);
+ * </pre>
+ */
+l_ok
+ptaGetLinearLSF(PTA *pta,
+ l_float32 *pa,
+ l_float32 *pb,
+ NUMA **pnafit)
+{
+l_int32 n, i;
+l_float32 a, b, factor, sx, sy, sxx, sxy, val;
+l_float32 *xa, *ya;
+
+ PROCNAME("ptaGetLinearLSF");
+
+ if (pa) *pa = 0.0;
+ if (pb) *pb = 0.0;
+ if (pnafit) *pnafit = NULL;
+ if (!pa && !pb && !pnafit)
+ return ERROR_INT("no output requested", procName, 1);
+ 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;
+
+ a = factor * ((l_float32)n * sxy - sx * sy);
+ b = 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);
+ a = sxy / sxx;
+ b = 0.0;
+ } else { /* a = 0; horizontal line */
+ for (i = 0; i < n; i++)
+ sy += ya[i];
+ a = 0.0;
+ b = sy / (l_float32)n;
+ }
+
+ if (pnafit) {
+ *pnafit = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ val = a * xa[i] + b;
+ numaAddNumber(*pnafit, val);
+ }
+ }
+
+ if (pa) *pa = a;
+ if (pb) *pb = b;
+ return 0;
+}
+
+
+/*!
+ * \brief ptaGetQuadraticLSF()
+ *
+ * \param[in] pta
+ * \param[out] pa [optional] coeff a of LSF: y = ax^2 + bx + c
+ * \param[out] pb [optional] coeff b of LSF: y = ax^2 + bx + c
+ * \param[out] pc [optional] coeff c of LSF: y = ax^2 + bx + c
+ * \param[out] pnafit [optional] numa of least square fit
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * Get the nax array using ptaGetArrays(pta, &nax, NULL);
+ * </pre>
+ */
+l_ok
+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];
+
+ PROCNAME("ptaGetQuadraticLSF");
+
+ if (pa) *pa = 0.0;
+ if (pb) *pb = 0.0;
+ if (pc) *pc = 0.0;
+ if (pnafit) *pnafit = NULL;
+ if (!pa && !pb && !pc && !pnafit)
+ return ERROR_INT("no output requested", 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);
+
+ 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) {
+ *pnafit = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ x = xa[i];
+ y = g[0] * x * x + g[1] * x + g[2];
+ numaAddNumber(*pnafit, y);
+ }
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief ptaGetCubicLSF()
+ *
+ * \param[in] pta
+ * \param[out] pa [optional] coeff a of LSF: y = ax^3 + bx^2 + cx + d
+ * \param[out] pb [optional] coeff b of LSF
+ * \param[out] pc [optional] coeff c of LSF
+ * \param[out] pd [optional] coeff d of LSF
+ * \param[out] pnafit [optional] numa of least square fit
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * Get the nax array using ptaGetArrays(pta, &nax, NULL);
+ * </pre>
+ */
+l_ok
+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];
+
+ PROCNAME("ptaGetCubicLSF");
+
+ 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 (!pa && !pb && !pc && !pd && !pnafit)
+ return ERROR_INT("no output requested", procName, 1);
+ 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) {
+ *pnafit = numaCreate(n);
+ 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(*pnafit, y);
+ }
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief ptaGetQuarticLSF()
+ *
+ * \param[in] pta
+ * \param[out] pa [optional] coeff a of LSF:
+ * y = ax^4 + bx^3 + cx^2 + dx + e
+ * \param[out] pb [optional] coeff b of LSF
+ * \param[out] pc [optional] coeff c of LSF
+ * \param[out] pd [optional] coeff d of LSF
+ * \param[out] pe [optional] coeff e of LSF
+ * \param[out] pnafit [optional] numa of least square fit
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * Get the nax array using ptaGetArrays(pta, &nax, NULL);
+ * </pre>
+ */
+l_ok
+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];
+
+ PROCNAME("ptaGetQuarticLSF");
+
+ 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 (!pa && !pb && !pc && !pd && !pe && !pnafit)
+ return ERROR_INT("no output requested", procName, 1);
+ 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) {
+ *pnafit = numaCreate(n);
+ 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(*pnafit, y);
+ }
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief ptaNoisyLinearLSF()
+ *
+ * \param[in] pta
+ * \param[in] factor reject outliers with error greater than this
+ * number of medians; typically ~ 3
+ * \param[out] pptad [optional] with outliers removed
+ * \param[out] pa [optional] slope a of least square fit: y = ax + b
+ * \param[out] pb [optional] intercept b of least square fit
+ * \param[out] pmederr [optional] median error
+ * \param[out] pnafit [optional] numa of least square fit to ptad
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 (pptad) *pptad = NULL;
+ if (pa) *pa = 0.0;
+ if (pb) *pb = 0.0;
+ if (pmederr) *pmederr = 0.0;
+ if (pnafit) *pnafit = NULL;
+ if (!pptad && !pa && !pb && !pnafit)
+ return ERROR_INT("no output requested", procName, 1);
+ 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;
+}
+
+
+/*!
+ * \brief ptaNoisyQuadraticLSF()
+ *
+ * \param[in] pta
+ * \param[in] factor reject outliers with error greater than this
+ * number of medians; typically ~ 3
+ * \param[out] pptad [optional] with outliers removed
+ * \param[out] pa [optional] coeff a of LSF: y = ax^2 + bx + c
+ * \param[out] pb [optional] coeff b of LSF: y = ax^2 + bx + c
+ * \param[out] pc [optional] coeff c of LSF: y = ax^2 + bx + c
+ * \param[out] pmederr [optional] median error
+ * \param[out] pnafit [optional] numa of least square fit to ptad
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) *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 (!pptad && !pa && !pb && !pc && !pnafit)
+ return ERROR_INT("no output requested", procName, 1);
+ 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;
+}
+
+
+/*!
+ * \brief applyLinearFit()
+ *
+ * \param[in] a, b linear fit coefficients
+ * \param[in] x
+ * \param[out] py y = a * x + b
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief applyQuadraticFit()
+ *
+ * \param[in] a, b, c quadratic fit coefficients
+ * \param[in] x
+ * \param[out] py y = a * x^2 + b * x + c
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief applyCubicFit()
+ *
+ * \param[in] a, b, c, d cubic fit coefficients
+ * \param[in] x
+ * \param[out] py y = a * x^3 + b * x^2 + c * x + d
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief applyQuarticFit()
+ *
+ * \param[in] a, b, c, d, e quartic fit coefficients
+ * \param[in] x
+ * \param[out] py y = a * x^4 + b * x^3 + c * x^2 + d * x + e
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixPlotAlongPta()
+ *
+ * \param[in] pixs any depth
+ * \param[in] pta set of points on which to plot
+ * \param[in] outformat GPLOT_PNG, GPLOT_PS, GPLOT_EPS, GPLOT_LATEX
+ * \param[in] title [optional] for plot; can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a debugging function.
+ * (2) Removes existing colormaps and clips the pta to the input %pixs.
+ * (3) If the image is RGB, three separate plots are generated.
+ * </pre>
+ */
+l_ok
+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");
+
+ lept_mkdir("lept/plot");
+
+ 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_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);
+ }
+
+ snprintf(buffer, sizeof(buffer), "/tmp/lept/plot/%03d", count++);
+ rtitle = stringJoin("Red: ", title);
+ gplotSimple1(nar, outformat, buffer, rtitle);
+ snprintf(buffer, sizeof(buffer), "/tmp/lept/plot/%03d", count++);
+ gtitle = stringJoin("Green: ", title);
+ gplotSimple1(nag, outformat, buffer, gtitle);
+ snprintf(buffer, sizeof(buffer), "/tmp/lept/plot/%03d", 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);
+ }
+
+ snprintf(buffer, sizeof(buffer), "/tmp/lept/plot/%03d", count++);
+ gplotSimple1(na, outformat, buffer, title);
+ numaDestroy(&na);
+ }
+ pixDestroy(&pixt);
+ return 0;
+}
+
+
+/*!
+ * \brief ptaGetPixelsFromPix()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] box [optional] can be null
+ * \return pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Generates a pta of fg pixels in the pix, within the box.
+ * If box == NULL, it uses the entire pix.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGenerateFromPta()
+ *
+ * \param[in] pta
+ * \param[in] w, h of pix
+ * \return pix 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaGetBoundaryPixels()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] type L_BOUNDARY_FG, L_BOUNDARY_BG
+ * \return pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates a pta of either fg or bg boundary pixels.
+ * (2) See also pixGeneratePtaBoundary() for rendering of
+ * fg boundary pixels.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaaGetBoundaryPixels()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] type L_BOUNDARY_FG, L_BOUNDARY_BG
+ * \param[in] connectivity 4 or 8
+ * \param[out] pboxa [optional] bounding boxes of the c.c.
+ * \param[out] ppixa [optional] pixa of the c.c.
+ * \return ptaa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaaIndexLabeledPixels()
+ *
+ * \param[in] pixs 32 bpp, of indices of c.c.
+ * \param[out] pncc [optional] number of connected components
+ * \return ptaa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PTAA *
+ptaaIndexLabeledPixels(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("ptaaIndexLabeledPixels");
+
+ 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;
+}
+
+
+/*!
+ * \brief ptaGetNeighborPixLocs()
+ *
+ * \param[in] pixs any depth
+ * \param[in] x, y pixel from which we search for nearest neighbors
+ * \param[in] conn 4 or 8 connectivity
+ * \return pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Generates a pta of all valid neighbor pixel locations,
+ * or NULL on error.
+ * </pre>
+ */
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Interconversion with Numa *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief numaConvertToPta1()
+ *
+ * \param[in] na numa with implicit y(x)
+ * \return pta if OK; null on error
+ */
+PTA *
+numaConvertToPta1(NUMA *na)
+{
+l_int32 i, n;
+l_float32 startx, delx, val;
+PTA *pta;
+
+ PROCNAME("numaConvertToPta1");
+
+ if (!na)
+ return (PTA *)ERROR_PTR("na not defined", procName, NULL);
+
+ n = numaGetCount(na);
+ pta = ptaCreate(n);
+ numaGetParameters(na, &startx, &delx);
+ for (i = 0; i < n; i++) {
+ numaGetFValue(na, i, &val);
+ ptaAddPt(pta, startx + i * delx, val);
+ }
+ return pta;
+}
+
+
+/*!
+ * \brief numaConvertToPta2()
+ *
+ * \param[in] nax
+ * \param[in] nay
+ * \return pta if OK; null on error
+ */
+PTA *
+numaConvertToPta2(NUMA *nax,
+ NUMA *nay)
+{
+l_int32 i, n, nx, ny;
+l_float32 valx, valy;
+PTA *pta;
+
+ PROCNAME("numaConvertToPta2");
+
+ if (!nax || !nay)
+ return (PTA *)ERROR_PTR("nax and nay not both defined", procName, NULL);
+
+ nx = numaGetCount(nax);
+ ny = numaGetCount(nay);
+ n = L_MIN(nx, ny);
+ if (nx != ny)
+ L_WARNING("nx = %d does not equal ny = %d\n", procName, nx, ny);
+ pta = ptaCreate(n);
+ for (i = 0; i < n; i++) {
+ numaGetFValue(nax, i, &valx);
+ numaGetFValue(nay, i, &valy);
+ ptaAddPt(pta, valx, valy);
+ }
+ return pta;
+}
+
+
+/*!
+ * \brief ptaConvertToNuma()
+ *
+ * \param[in] pta
+ * \param[out] pnax addr of nax
+ * \param[out] pnay addr of nay
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+ptaConvertToNuma(PTA *pta,
+ NUMA **pnax,
+ NUMA **pnay)
+{
+l_int32 i, n;
+l_float32 valx, valy;
+
+ PROCNAME("ptaConvertToNuma");
+
+ if (pnax) *pnax = NULL;
+ if (pnay) *pnay = NULL;
+ if (!pnax || !pnay)
+ return ERROR_INT("&nax and &nay not both defined", procName, 1);
+ if (!pta)
+ return ERROR_INT("pta not defined", procName, 1);
+
+ n = ptaGetCount(pta);
+ *pnax = numaCreate(n);
+ *pnay = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ ptaGetPt(pta, i, &valx, &valy);
+ numaAddNumber(*pnax, valx);
+ numaAddNumber(*pnay, valy);
+ }
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Display Pta and Ptaa *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixDisplayPta()
+ *
+ * \param[in] pixd can be same as pixs or NULL; 32 bpp if in-place
+ * \param[in] pixs 1, 2, 4, 8, 16 or 32 bpp
+ * \param[in] pta of path to be plotted
+ * \return pixd 32 bpp RGB version of pixs, with path in green.
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixDisplayPtaaPattern()
+ *
+ * \param[in] pixd 32 bpp
+ * \param[in] pixs 1, 2, 4, 8, 16 or 32 bpp; 32 bpp if in place
+ * \param[in] ptaa giving locations at which the pattern is displayed
+ * \param[in] pixp 1 bpp pattern to be placed such that its reference
+ * point co-locates with each point in pta
+ * \param[in] cx, cy reference point in pattern
+ * \return pixd 32 bpp RGB version of pixs.
+ *
+ * <pre>
+ * 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()
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixDisplayPtaPattern()
+ *
+ * \param[in] pixd can be same as pixs or NULL; 32 bpp if in-place
+ * \param[in] pixs 1, 2, 4, 8, 16 or 32 bpp
+ * \param[in] pta giving locations at which the pattern is displayed
+ * \param[in] pixp 1 bpp pattern to be placed such that its reference
+ * point co-locates with each point in pta
+ * \param[in] cx, cy reference point in pattern
+ * \param[in] color in 0xrrggbb00 format
+ * \return pixd 32 bpp RGB version of pixs.
+ *
+ * <pre>
+ * 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()
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaReplicatePattern()
+ *
+ * \param[in] ptas "sparse" input pta
+ * \param[in] pixp [optional] 1 bpp pattern, to be replicated
+ * in output pta
+ * \param[in] ptap [optional] set of pts, to be replicated in output pta
+ * \param[in] cx, cy reference point in pattern
+ * \param[in] w, h clipping sizes for output pta
+ * \return ptad with all points of replicated pattern, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixDisplayPtaa()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16 or 32 bpp
+ * \param[in] 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) {
+ pixDestroy(&pixd);
+ 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/leptonica/src/ptafunc2.c b/leptonica/src/ptafunc2.c
new file mode 100644
index 00000000..1949a6cf
--- /dev/null
+++ b/leptonica/src/ptafunc2.c
@@ -0,0 +1,899 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file ptafunc2.c
+ * <pre>
+ *
+ * --------------------------------------
+ * This file has these Pta utilities:
+ * - sorting
+ * - ordered set operations
+ * - hash map operations
+ * --------------------------------------
+ *
+ * Sorting
+ * PTA *ptaSort()
+ * l_int32 ptaGetSortIndex()
+ * PTA *ptaSortByIndex()
+ * PTAA *ptaaSortByIndex()
+ * l_int32 ptaGetRankValue()
+ * PTA *ptaSort2d()
+ * l_int32 ptaEqual()
+ *
+ * Set operations using aset (rbtree)
+ * PTA *ptaUnionByAset()
+ * PTA *ptaRemoveDupsByAset()
+ * PTA *ptaIntersectionByAset()
+ * L_ASET *l_asetCreateFromPta()
+ *
+ * Set operations using hashing (dnahash)
+ * PTA *ptaUnionByHash()
+ * l_int32 ptaRemoveDupsByHash()
+ * PTA *ptaIntersectionByHash();
+ * l_int32 ptaFindPtByHash()
+ * L_DNAHASH *l_dnaHashCreateFromPta()
+ *
+ *
+ * We have two implementations of set operations on an array of points:
+ *
+ * (1) Using an underlying tree (rbtree)
+ * This uses a good 64 bit hashing function for the key,
+ * that is not expected to have hash collisions (and we do
+ * not test for them). The tree is built up of the hash
+ * values, and if the hash is found in the tree, it is
+ * assumed that the point has already been found.
+ *
+ * (2) Using an underlying hashing of the keys (dnahash)
+ * This uses a fast 64 bit hashing function for the key,
+ * which is then hashed into a bucket (a dna in a dnaHash).
+ * Because hash collisions can occur, the index into the
+ * pta for the point that gave rise to that key is stored,
+ * and the dna (bucket) is traversed, using the stored indices
+ * to determine if that point had already been seen.
+ *
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*
+ * Sorting *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaSort()
+ *
+ * \param[in] ptas
+ * \param[in] sorttype L_SORT_BY_X, L_SORT_BY_Y
+ * \param[in] sortorder L_SORT_INCREASING, L_SORT_DECREASING
+ * \param[out] pnaindex [optional] 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;
+}
+
+
+/*!
+ * \brief ptaGetSortIndex()
+ *
+ * \param[in] ptas
+ * \param[in] sorttype L_SORT_BY_X, L_SORT_BY_Y
+ * \param[in] sortorder L_SORT_INCREASING, L_SORT_DECREASING
+ * \param[out] pnaindex index of sorted order into original array
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+ptaGetSortIndex(PTA *ptas,
+ l_int32 sorttype,
+ l_int32 sortorder,
+ NUMA **pnaindex)
+{
+l_int32 i, n;
+l_float32 x, y;
+NUMA *na, *nai;
+
+ 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 */
+ nai = numaGetSortIndex(na, sortorder);
+ numaDestroy(&na);
+ if (!nai)
+ return ERROR_INT("naindex not made", procName, 1);
+ *pnaindex = nai;
+ return 0;
+}
+
+
+/*!
+ * \brief ptaSortByIndex()
+ *
+ * \param[in] ptas
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaaSortByIndex()
+ *
+ * \param[in] ptaas
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptaGetRankValue()
+ *
+ * \param[in] pta
+ * \param[in] fract use 0.0 for smallest, 1.0 for largest
+ * \param[in] ptasort [optional] version of %pta sorted by %sorttype
+ * \param[in] sorttype L_SORT_BY_X, L_SORT_BY_Y
+ * \param[out] pval rankval: the x or y value at %fract
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+ptaGetRankValue(PTA *pta,
+ l_float32 fract,
+ PTA *ptasort,
+ l_int32 sorttype,
+ l_float32 *pval)
+{
+l_int32 index, n;
+PTA *ptas;
+
+ PROCNAME("ptaGetRankValue");
+
+ if (!pval)
+ return ERROR_INT("&val not defined", procName, 1);
+ *pval = 0.0;
+ if (!pta)
+ return ERROR_INT("pta not defined", procName, 1);
+ if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y)
+ return ERROR_INT("invalid sort type", procName, 1);
+ if (fract < 0.0 || fract > 1.0)
+ return ERROR_INT("fract not in [0.0 ... 1.0]", procName, 1);
+ if ((n = ptaGetCount(pta)) == 0)
+ return ERROR_INT("pta empty", procName, 1);
+
+ if (ptasort)
+ ptas = ptasort;
+ else
+ ptas = ptaSort(pta, sorttype, L_SORT_INCREASING, NULL);
+
+ index = (l_int32)(fract * (l_float32)(n - 1) + 0.5);
+ if (sorttype == L_SORT_BY_X)
+ ptaGetPt(ptas, index, pval, NULL);
+ else /* sort by y */
+ ptaGetPt(ptas, index, NULL, pval);
+
+ if (!ptasort) ptaDestroy(&ptas);
+ return 0;
+}
+
+
+/*!
+ * \brief ptaSort2d()
+ *
+ * \param[in] ptas
+ * \return ptad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Sort increasing by row-major, scanning down from the UL corner,
+ * where for each value of y, order the pts from left to right.
+ * </pre>
+ */
+PTA *
+ptaSort2d(PTA *pta)
+{
+l_int32 index, i, j, n, nx, ny, start, end;
+l_float32 x, y, yp, val;
+NUMA *na1, *na2, *nas, *nax;
+PTA *pta1, *ptad;
+
+ PROCNAME("ptaSort2d");
+
+ if (!pta)
+ return (PTA *)ERROR_PTR("pta not defined", procName, NULL);
+
+ /* Sort by row-major (y first, then x). After sort by y,
+ * the x values at the same y are not sorted. */
+ pta1 = ptaSort(pta, L_SORT_BY_Y, L_SORT_INCREASING, NULL);
+
+ /* Find start and ending indices with the same y value */
+ n = ptaGetCount(pta1);
+ na1 = numaCreate(0); /* holds start index of sequence with same y */
+ na2 = numaCreate(0); /* holds end index of sequence with same y */
+ numaAddNumber(na1, 0);
+ ptaGetPt(pta1, 0, &x, &yp);
+ for (i = 1; i < n; i++) {
+ ptaGetPt(pta1, i, &x, &y);
+ if (y != yp) {
+ numaAddNumber(na1, i);
+ numaAddNumber(na2, i - 1);
+ }
+ yp = y;
+ }
+ numaAddNumber(na2, n - 1);
+
+ /* Sort by increasing x each set with the same y value */
+ ptad = ptaCreate(n);
+ ny = numaGetCount(na1); /* number of distinct y values */
+ for (i = 0, index = 0; i < ny; i++) {
+ numaGetIValue(na1, i, &start);
+ numaGetIValue(na2, i, &end);
+ nx = end - start + 1; /* number of points with current y value */
+ if (nx == 1) {
+ ptaGetPt(pta1, index++, &x, &y);
+ ptaAddPt(ptad, x, y);
+ } else {
+ /* More than 1 point; extract and sort the x values */
+ nax = numaCreate(nx);
+ for (j = 0; j < nx; j++) {
+ ptaGetPt(pta1, index + j, &x, &y);
+ numaAddNumber(nax, x);
+ }
+ nas = numaSort(NULL, nax, L_SORT_INCREASING);
+ /* Add the points with x sorted */
+ for (j = 0; j < nx; j++) {
+ numaGetFValue(nas, j, &val);
+ ptaAddPt(ptad, val, y);
+ }
+ index += nx;
+ numaDestroy(&nax);
+ numaDestroy(&nas);
+ }
+ }
+ numaDestroy(&na1);
+ numaDestroy(&na2);
+ ptaDestroy(&pta1);
+ return ptad;
+}
+
+
+/*!
+ * \brief ptaEqual()
+ *
+ * \param[in] pta1
+ * \param[in] pta2
+ * \param[out] psame 1 if same; 0 if different
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Equality is defined as having the same set of points,
+ * independent of the order in which they are presented.
+ * </pre>
+ */
+l_ok
+ptaEqual(PTA *pta1,
+ PTA *pta2,
+ l_int32 *psame)
+{
+l_int32 i, n1, n2;
+l_float32 x1, y1, x2, y2;
+PTA *ptas1, *ptas2;
+
+ PROCNAME("ptaEqual");
+
+ if (!psame)
+ return ERROR_INT("&same not defined", procName, 1);
+ *psame = 0.0;
+ if (!pta1 || !pta2)
+ return ERROR_INT("pta1 and pta2 not both defined", procName, 1);
+
+ n1 = ptaGetCount(pta1);
+ n2 = ptaGetCount(pta2);
+ if (n1 != n2) return 0;
+
+ /* 2d sort each and compare */
+ ptas1 = ptaSort2d(pta1);
+ ptas2 = ptaSort2d(pta2);
+ for (i = 0; i < n1; i++) {
+ ptaGetPt(ptas1, i, &x1, &y1);
+ ptaGetPt(ptas2, i, &x2, &y2);
+ if (x1 != x2 || y1 != y2) {
+ ptaDestroy(&ptas1);
+ ptaDestroy(&ptas2);
+ return 0;
+ }
+ }
+
+ *psame = 1;
+ ptaDestroy(&ptas1);
+ ptaDestroy(&ptas2);
+ return 0;
+}
+
+
+
+
+/*---------------------------------------------------------------------*
+ * Set operations using aset (rbtree) *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaUnionByAset()
+ *
+ * \param[in] pta1, pta2
+ * \return ptad with the union of the set of points, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaRemoveDupsByAset()
+ *
+ * \param[in] ptas assumed to be integer values
+ * \return ptad with duplicates removed, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaIntersectionByAset()
+ *
+ * \param[in] pta1, pta2
+ * \return ptad intersection of the point sets, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_asetCreateFromPta()
+ *
+ * \param[in] 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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Set operations using hashing (rbtree) *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ptaUnionByHash()
+ *
+ * \param[in] pta1, pta2
+ * \return ptad with the union of the set of points, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptaRemoveDupsByHash()
+ *
+ * \param[in] ptas assumed to be integer values
+ * \param[out] pptad unique set of pts; duplicates removed
+ * \param[out] pdahash [optional] dnahash used for lookup
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+ptaRemoveDupsByHash(PTA *ptas,
+ PTA **pptad,
+ L_DNAHASH **pdahash)
+{
+l_int32 i, n, index, items, x, y;
+l_uint32 nsize;
+l_uint64 key;
+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_hashPtToUint64(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;
+}
+
+
+/*!
+ * \brief ptaIntersectionByHash()
+ *
+ * \param[in] pta1, pta2
+ * \return ptad intersection of the point sets, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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_hashPtToUint64(x, y, &key);
+ l_dnaHashAdd(dahash2, key, (l_float64)i);
+ }
+ }
+ }
+
+ l_dnaHashDestroy(&dahash1);
+ l_dnaHashDestroy(&dahash2);
+ return ptad;
+}
+
+
+/*!
+ * \brief ptaFindPtByHash()
+ *
+ * \param[in] pta
+ * \param[in] dahash built from pta
+ * \param[in] x, y arbitrary points
+ * \param[out] pindex index into pta if (x,y) is in pta; -1 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * (2) We use a strong hash function to minimize the chance that
+ * two different points hash to the same key value.
+ * (3) We select the number of buckets to be about 5% of the size
+ * of the input %pta, so that when fully populated, each
+ * bucket (dna) will have about 20 entries, each being an index
+ * into %pta. In lookup, after hashing to the key, and then
+ * again to the bucket, we traverse the bucket (dna), using the
+ * index into %pta to check if the point (x,y) has been found before.
+ * </pre>
+ */
+l_ok
+ptaFindPtByHash(PTA *pta,
+ L_DNAHASH *dahash,
+ l_int32 x,
+ l_int32 y,
+ l_int32 *pindex)
+{
+l_int32 i, 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);
+
+ l_hashPtToUint64(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;
+}
+
+
+/*!
+ * \brief l_dnaHashCreateFromPta()
+ *
+ * \param[in] 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");
+
+ if (!pta)
+ return (L_DNAHASH *)ERROR_PTR("pta not defined", procName, NULL);
+
+ /* 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 */
+/* lept_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_hashPtToUint64(x, y, &key);
+ l_dnaHashAdd(dahash, key, (l_float64)i);
+ }
+
+ return dahash;
+}
diff --git a/leptonica/src/ptra.c b/leptonica/src/ptra.c
new file mode 100644
index 00000000..7fec7e32
--- /dev/null
+++ b/leptonica/src/ptra.c
@@ -0,0 +1,1010 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file ptra.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Bounds on initial array size */
+LEPT_DLL const l_uint32 MaxInitPtraSize = 1000001;
+static const l_int32 DefaultInitPtraSize = 20; /*!< n'importe quoi */
+
+ /* Static function */
+static l_int32 ptraExtendArray(L_PTRA *pa);
+
+/*--------------------------------------------------------------------------*
+ * Ptra creation and destruction *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief ptraCreate()
+ *
+ * \param[in] n size of ptr array to be alloc'd; use 0 for default
+ * \return pa, or NULL on error
+ */
+L_PTRA *
+ptraCreate(l_int32 n)
+{
+L_PTRA *pa;
+
+ PROCNAME("ptraCreate");
+
+ if (n > MaxInitPtraSize) {
+ L_ERROR("n = %d > maxsize = %d\n", procName, n, MaxInitPtraSize);
+ return NULL;
+ }
+ if (n <= 0) n = DefaultInitPtraSize;
+
+ pa = (L_PTRA *)LEPT_CALLOC(1, sizeof(L_PTRA));
+ if ((pa->array = (void **)LEPT_CALLOC(n, sizeof(void *))) == NULL) {
+ ptraDestroy(&pa, 0, 0);
+ return (L_PTRA *)ERROR_PTR("ptr array not made", procName, NULL);
+ }
+ pa->nalloc = n;
+ pa->imax = -1;
+ pa->nactual = 0;
+ return pa;
+}
+
+
+/*!
+ * \brief ptraDestroy()
+ *
+ * \param[in,out] ppa will be set to null before returning
+ * \param[in] freeflag TRUE to free each remaining item in the array
+ * \param[in] warnflag TRUE to warn if any remaining items
+ * are not destroyed
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*--------------------------------------------------------------------------*
+ * Add/insert/remove/replace generic ptr object *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief ptraAdd()
+ *
+ * \param[in] pa ptra
+ * \param[in] item generic ptr to a struct
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptraExtendArray()
+ *
+ * \param[in] pa
+ * \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;
+}
+
+
+/*!
+ * \brief ptraInsert()
+ *
+ * \param[in] pa ptra
+ * \param[in] index location in ptra to insert new value
+ * \param[in] item generic ptr to a struct; can be null
+ * \param[in] shiftflag L_AUTO_DOWNSHIFT, L_MIN_DOWNSHIFT, L_FULL_DOWNSHIFT
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptraRemove()
+ *
+ * \param[in] pa ptra
+ * \param[in] index element to be removed
+ * \param[in] flag L_NO_COMPACTION, L_COMPACTION
+ * \return item, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief ptraRemoveLast()
+ *
+ * \param[in] pa 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;
+}
+
+
+/*!
+ * \brief ptraReplace()
+ *
+ * \param[in] pa ptra
+ * \param[in] index element to be replaced
+ * \param[in] item new generic ptr to a struct; can be null
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief ptraSwap()
+ *
+ * \param[in] pa ptra
+ * \param[in] index1
+ * \param[in] index2
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptraCompactArray()
+ *
+ * \param[in] pa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief ptraReverse()
+ *
+ * \param[in] pa ptra
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptraJoin()
+ *
+ * \param[in] pa1 add to this one
+ * \param[in] pa2 appended to pa1, and emptied of items; can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief ptraGetMaxIndex()
+ *
+ * \param[in] pa ptra
+ * \param[out] pmaxindex index of last item in the array;
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptraGetActualCount()
+ *
+ * \param[in] pa ptra
+ * \param[out] pcount actual number of items on the ptr array
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptraGetPtrToItem()
+ *
+ * \param[in] pa ptra
+ * \param[in] index of element to be retrieved
+ * \return a ptr to the element, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief ptraaCreate()
+ *
+ * \param[in] n size of ptr array to be alloc'd
+ * \return paa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ paa = (L_PTRAA *)LEPT_CALLOC(1, sizeof(L_PTRAA));
+ if ((paa->ptra = (L_PTRA **)LEPT_CALLOC(n, sizeof(L_PTRA *))) == NULL) {
+ ptraaDestroy(&paa, 0, 0);
+ return (L_PTRAA *)ERROR_PTR("ptr array not made", procName, NULL);
+ }
+ paa->nalloc = n;
+ return paa;
+}
+
+
+/*!
+ * \brief ptraaDestroy()
+ *
+ * \param[in,out] ppaa will be set to null before returning
+ * \param[in] freeflag TRUE to free each remaining item in each ptra
+ * \param[in] warnflag TRUE to warn if any remaining items
+ * are not destroyed
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*--------------------------------------------------------------------------*
+ * Ptraa accessors *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief ptraaGetSize()
+ *
+ * \param[in] paa
+ * \param[out] psize size of ptr array
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief ptraaInsertPtra()
+ *
+ * \param[in] paa ptraa
+ * \param[in] index location in array for insertion
+ * \param[in] pa to be inserted
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 already stored at index", procName, 1);
+
+ paa->ptra[index] = pa;
+ return 0;
+}
+
+
+/*!
+ * \brief ptraaGetPtra()
+ *
+ * \param[in] paa ptraa
+ * \param[in] index location in array
+ * \param[in] accessflag L_HANDLE_ONLY, L_REMOVE
+ * \return ptra at index location, or NULL on error or if there
+ * is no ptra there.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief ptraaFlattenToPtra()
+ *
+ * \param[in] paa ptraa
+ * \return ptra, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/ptra.h b/leptonica/src/ptra.h
new file mode 100644
index 00000000..705d97af
--- /dev/null
+++ b/leptonica/src/ptra.h
@@ -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.
+ *====================================================================*/
+
+#ifndef LEPTONICA_PTRA_H
+#define LEPTONICA_PTRA_H
+
+/*!
+ * \file ptra.h
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+
+ /* Bound on max initial ptra size */
+LEPT_DLL extern const l_uint32 MaxInitPtraSize;
+
+/*------------------------------------------------------------------------*
+ * 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;
+
+
+
+/*------------------------------------------------------------------------*
+ * Accessor and modifier flags for L_Ptra and L_Ptraa *
+ *------------------------------------------------------------------------*/
+
+/*! Ptra Removal */
+enum {
+ L_NO_COMPACTION = 1, /*!< null the pointer only */
+ L_COMPACTION = 2 /*!< compact the array */
+};
+
+/*! Ptra Insertion */
+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 */
+};
+
+/*! Ptraa Accessor */
+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/leptonica/src/quadtree.c b/leptonica/src/quadtree.c
new file mode 100644
index 00000000..6c10232a
--- /dev/null
+++ b/leptonica/src/quadtree.c
@@ -0,0 +1,701 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file quadtree.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+#ifndef NO_CONSOLE_IO
+#define DEBUG_BOXES 0
+#endif /* !NO_CONSOLE_IO */
+
+
+/*----------------------------------------------------------------------*
+ * Top-level quadtree linear statistics *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixQuadtreeMean()
+ *
+ * \param[in] pixs 8 bpp, no colormap
+ * \param[in] nlevels in quadtree; max allowed depends on image size
+ * \param[in] pix_ma input mean accumulator; can be null
+ * \param[out] pfpixa mean values in quadtree
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixQuadtreeVariance()
+ *
+ * \param[in] pixs 8 bpp, no colormap
+ * \param[in] nlevels in quadtree
+ * \param[in] pix_ma input mean accumulator; can be null
+ * \param[in] dpix_msa input mean square accumulator; can be null
+ * \param[out] pfpixa_v [optional] variance values in quadtree
+ * \param[out] pfpixa_rv [optional] root variance values in quadtree
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The returned fpixav and fpixarv have %nlevels of fpix,
+ * each containing at the respective levels the variance
+ * and root variance values.
+ * </pre>
+ */
+l_ok
+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) {
+ pixDestroy(&pix_mac);
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixMeanInRectangle()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] box region to compute mean value
+ * \param[in] pixma mean accumulator
+ * \param[out] pval mean value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 / ((l_float32)(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;
+}
+
+
+/*!
+ * \brief pixVarianceInRectangle()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] box region to compute variance and/or root variance
+ * \param[in] pix_ma mean accumulator
+ * \param[in] dpix_msa mean square accumulator
+ * \param[out] pvar [optional] variance
+ * \param[out] prvar [optional] root variance
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 / ((l_float32)(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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief boxaaQuadtreeRegions()
+ *
+ * \param[in] w, h size of pix that is being quadtree-ized
+ * \param[in] nlevels number of levels in quadtree
+ * \return baa for quadtree regions at each level, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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
+ lept_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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief quadtreeGetParent()
+ *
+ * \param[in] fpixa mean, variance or root variance
+ * \param[in] level, x, y of current pixel
+ * \param[out] pval parent pixel value, or 0.0 on error
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief quadtreeGetChildren()
+ *
+ * \param[in] fpixa mean, variance or root variance
+ * \param[in] level, x, y of current pixel
+ * \param[out] pval00, pval01,
+ * pval10, pval11 four child pixel values
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief quadtreeMaxLevels()
+ *
+ * \param[in] w, h dimensions of image
+ * \return maxlevels maximum number of levels allowed, or -1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief fpixaDisplayQuadtree()
+ *
+ * \param[in] fpixa mean, variance or root variance
+ * \param[in] factor replication factor at lowest level
+ * \param[in] fontsize 4, ... 20
+ * \return pixd 8 bpp, mosaic of quadtree images, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+PIX *
+fpixaDisplayQuadtree(FPIXA *fpixa,
+ l_int32 factor,
+ l_int32 fontsize)
+{
+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(NULL, fontsize)) == 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/leptonica/src/queue.c b/leptonica/src/queue.c
new file mode 100644
index 00000000..cef571a8
--- /dev/null
+++ b/leptonica/src/queue.c
@@ -0,0 +1,326 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file queue.c
+ * <pre>
+ *
+ * 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.]
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief lqueueCreate()
+ *
+ * \param[in] nalloc size of ptr array to be alloc'd; 0 for default
+ * \return lqueue, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Allocates a ptr array of given size, and initializes counters.
+ * </pre>
+ */
+L_QUEUE *
+lqueueCreate(l_int32 nalloc)
+{
+L_QUEUE *lq;
+
+ PROCNAME("lqueueCreate");
+
+ if (nalloc < MIN_BUFFER_SIZE)
+ nalloc = INITIAL_BUFFER_ARRAYSIZE;
+
+ lq = (L_QUEUE *)LEPT_CALLOC(1, sizeof(L_QUEUE));
+ if ((lq->array = (void **)LEPT_CALLOC(nalloc, sizeof(void *))) == NULL) {
+ lqueueDestroy(&lq, 0);
+ return (L_QUEUE *)ERROR_PTR("ptr array not made", procName, NULL);
+ }
+ lq->nalloc = nalloc;
+ lq->nhead = lq->nelem = 0;
+ return lq;
+}
+
+
+/*!
+ * \brief lqueueDestroy()
+ *
+ * \param[in,out] plq will be set to null before returning
+ * \param[in] freeflag TRUE to free each remaining struct in the array
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*--------------------------------------------------------------------------*
+ * Accessors *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief lqueueAdd()
+ *
+ * \param[in] lq lqueue
+ * \param[in] item to be added to the tail of the queue
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) {
+ if (lqueueExtendArray(lq))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+
+ /* Now add the item */
+ lq->array[lq->nhead + lq->nelem] = (void *)item;
+ lq->nelem++;
+
+ return 0;
+}
+
+
+/*!
+ * \brief lqueueExtendArray()
+ *
+ * \param[in] lq 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;
+}
+
+
+/*!
+ * \brief lqueueRemove()
+ *
+ * \param[in] lq lqueue
+ * \return ptr to item popped from the head of the queue,
+ * or NULL if the queue is empty or on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief lqueueGetCount()
+ *
+ * \param[in] lq 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief lqueuePrint()
+ *
+ * \param[in] fp file stream
+ * \param[in] lq lqueue
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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/leptonica/src/queue.h b/leptonica/src/queue.h
new file mode 100644
index 00000000..fd380e83
--- /dev/null
+++ b/leptonica/src/queue.h
@@ -0,0 +1,77 @@
+/*====================================================================*
+ - 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
+
+/*!
+ * \file queue.h
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+
+/*! Expandable pointer queue for arbitrary void* data */
+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/leptonica/src/rank.c b/leptonica/src/rank.c
new file mode 100644
index 00000000..7d80ba24
--- /dev/null
+++ b/leptonica/src/rank.c
@@ -0,0 +1,544 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file rank.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*----------------------------------------------------------------------*
+ * Rank order filter *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixRankFilter()
+ *
+ * \param[in] pixs 8 or 32 bpp; no colormap
+ * \param[in] wf, hf width and height of filter; each is >= 1
+ * \param[in] rank in [0.0 ... 1.0]
+ * \return pixd of rank values, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixRankFilterRGB()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] wf, hf width and height of filter; each is >= 1
+ * \param[in] rank in [0.0 ... 1.0]
+ * \return pixd of rank values, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixRankFilterGray()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] wf, hf width and height of filter; each is >= 1
+ * \param[in] rank in [0.0 ... 1.0]
+ * \return pixd of rank values, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.0001f;
+ if (rank == 1.0) rank = 0.9999f;
+
+ /* 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;
+ }
+ }
+ if (n == 16) { /* avoid accessing out of bounds */
+ L_WARNING("n = 16; reducing\n", procName);
+ n = 15;
+ sum -= histo16[n];
+ }
+ 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;
+ }
+ }
+ if (n == 16) { /* avoid accessing out of bounds */
+ L_WARNING("n = 16; reducing\n", procName);
+ n = 15;
+ sum -= histo16[n];
+ }
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixMedianFilter()
+ *
+ * \param[in] pixs 8 or 32 bpp; no colormap
+ * \param[in] 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) *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixRankFilterWithScaling()
+ *
+ * \param[in] pixs 8 or 32 bpp; no colormap
+ * \param[in] wf, hf width and height of filter; each is >= 1
+ * \param[in] rank in [0.0 ... 1.0]
+ * \param[in] scalefactor scale factor; must be >= 0.2 and <= 0.7
+ * \return pixd of rank values, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/rbtree.c b/leptonica/src/rbtree.c
new file mode 100644
index 00000000..75def3d2
--- /dev/null
+++ b/leptonica/src/rbtree.c
@@ -0,0 +1,905 @@
+/*====================================================================*
+ - 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/).
+ */
+
+/*!
+ * \file rbtree.c
+ * <pre>
+ *
+ * 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 (e.g., int64 --> int64).
+ * * 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
+ * static l_int32 compareKeys()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* The node color enum is only needed in the rbtree implementation */
+enum {
+ L_RED_NODE = 1,
+ L_BLACK_NODE = 2
+};
+
+ /* This 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 l_int32 compareKeys(l_int32 keytype, RB_TYPE left, RB_TYPE right);
+
+static node *grandparent(node *n);
+static node *sibling(node *n);
+static node *uncle(node *n);
+static l_int32 node_color(node *n);
+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);
+static void verify_properties(L_RBTREE *t);
+
+#ifndef NO_CONSOLE_IO
+#define VERIFY_RBTREE 0 /* only for debugging */
+#endif /* ~NO_CONSOLE_IO */
+
+/* ------------------------------------------------------------- *
+ * Interface to Red-black Tree *
+ * ------------------------------------------------------------- */
+/*!
+ * \brief l_rbtreeCreate()
+ *
+ * \param[in] 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)
+{
+L_RBTREE *t;
+
+ 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);
+
+ t = (L_RBTREE *)LEPT_CALLOC(1, sizeof(L_RBTREE));
+ t->keytype = keytype;
+ verify_properties(t);
+ return t;
+}
+
+/*!
+ * \brief l_rbtreeLookup()
+ *
+ * \param[in] t rbtree, including root node
+ * \param[in] 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)
+{
+node *n;
+
+ PROCNAME("l_rbtreeLookup");
+
+ if (!t)
+ return (RB_TYPE *)ERROR_PTR("tree is null\n", procName, NULL);
+
+ n = lookup_node(t, key);
+ return n == NULL ? NULL : &n->value;
+}
+
+/*!
+ * \brief l_rbtreeInsert()
+ *
+ * \param[in] t rbtree, including root node
+ * \param[in] key insert a node with this key, if the key does not
+ * already exist in the tree
+ * \param[in] value typically an int, used for an index
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) If a node with the key already exists, this just updates the value.
+ * </pre>
+ */
+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 = 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);
+}
+
+/*!
+ * \brief l_rbtreeDelete()
+ *
+ * \param[in] t rbtree, including root node
+ * \param[in] 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);
+}
+
+/*!
+ * \brief l_rbtreeDestroy()
+ *
+ * \param[in] pt pointer to tree; will be wet to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Destroys the tree and nulls the input tree ptr.
+ * </pre>
+ */
+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;
+}
+
+ /* postorder DFS */
+static void
+destroy_helper(node *n)
+{
+ if (!n) return;
+ destroy_helper(n->left);
+ destroy_helper(n->right);
+ LEPT_FREE(n);
+}
+
+/*!
+ * \brief l_rbtreeGetFirst()
+ *
+ * \param[in] t rbtree, including root node
+ * \return first node, or NULL on error or if the tree is empty
+ *
+ * <pre>
+ * Notes:
+ * (1) This is the first node in an in-order traversal.
+ * </pre>
+ */
+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;
+}
+
+/*!
+ * \brief l_rbtreeGetNext()
+ *
+ * \param[in] n current node
+ * \return next node, or NULL if it's the last node
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+ }
+}
+
+/*!
+ * \brief l_rbtreeGetLast()
+ *
+ * \param[in] t rbtree, including root node
+ * \return last node, or NULL on error or if the tree is empty
+ *
+ * <pre>
+ * Notes:
+ * (1) This is the last node in an in-order traversal.
+ * </pre>
+ */
+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;
+}
+
+/*!
+ * \brief l_rbtreeGetPrev()
+ *
+ * \param[in] n current node
+ * \return next node, or NULL if it's the first node
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+ }
+}
+
+/*!
+ * \brief l_rbtreeGetCount()
+ *
+ * \param[in] t rbtree
+ * \return count the number of nodes in the tree, or 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);
+}
+
+
+/*!
+ * \brief l_rbtreePrint()
+ *
+ * \param[in] fp file stream
+ * \param[in] t rbtree
+ * \return void
+ */
+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);
+ }
+}
+
+
+/* ------------------------------------------------------------- *
+ * Static key comparison function *
+ * ------------------------------------------------------------- */
+static l_int32
+compareKeys(l_int32 keytype,
+ RB_TYPE left,
+ RB_TYPE right)
+{
+static char procName[] = "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 l_int32 node_color(node *n) {
+ return n == NULL ? L_BLACK_NODE : n->color;
+}
+
+
+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 = 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);
+ }
+}
+
+
+/* ------------------------------------------------------------- *
+ * Debugging: verify if tree is valid *
+ * ------------------------------------------------------------- */
+#if VERIFY_RBTREE
+static void verify_property_1(node *root);
+static void verify_property_2(node *root);
+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);
+#endif
+
+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
+}
+
+#if VERIFY_RBTREE
+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 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);
+}
+#endif
diff --git a/leptonica/src/rbtree.h b/leptonica/src/rbtree.h
new file mode 100644
index 00000000..6977d336
--- /dev/null
+++ b/leptonica/src/rbtree.h
@@ -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.
+ *====================================================================*/
+
+/*
+ * 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. */
+/*! RBTree Key Type */
+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.
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/readbarcode.c b/leptonica/src/readbarcode.c
new file mode 100644
index 00000000..178a2538
--- /dev/null
+++ b/leptonica/src/readbarcode.c
@@ -0,0 +1,1532 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file readbarcode.c
+ * <pre>
+ *
+ * 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).
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 */
+
+ /* Minimum barcode image size */
+static const l_int32 MIN_BC_WIDTH = 50;
+static const l_int32 MIN_BC_HEIGHT = 50;
+
+ /* 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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixProcessBarcodes()
+ *
+ * \param[in] pixs any depth
+ * \param[in] format L_BF_ANY, L_BF_CODEI2OF5, L_BF_CODE93, ...
+ * \param[in] method L_USE_WIDTHS, L_USE_WINDOWS
+ * \param[out] psaw [optional] sarray of bar widths
+ * \param[in] 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);
+
+ pixa = pixExtractBarcodes(pixg, debugflag);
+ pixDestroy(&pixg);
+ if (!pixa)
+ return (SARRAY *)ERROR_PTR("no barcode(s) found", procName, NULL);
+
+ sad = pixReadBarcodes(pixa, format, method, psaw, debugflag);
+ pixaDestroy(&pixa);
+ return sad;
+}
+
+
+/*!
+ * \brief pixExtractBarcodes()
+ *
+ * \param[in] pixs 8 bpp, no colormap
+ * \param[in] 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 *pix1, *pix2, *pix3;
+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, &pix2, &pix1);
+ n = boxaGetCount(boxa);
+ L_INFO("%d possible barcode(s) found\n", procName, n);
+ if (n == 0) {
+ boxaDestroy(&boxa);
+ pixDestroy(&pix2);
+ pixDestroy(&pix1);
+ return NULL;
+ }
+
+ if (debugflag) {
+ boxaWriteStderr(boxa);
+ pixDisplay(pix2, 100, 100);
+ pixDisplay(pix1, 800, 100);
+ }
+ pixDestroy(&pix1);
+
+ /* Deskew each barcode individually */
+ pixa = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ box = boxaGetBox(boxa, i, L_CLONE);
+ pix3 = pixDeskewBarcode(pixs, pix2, box, 15, 20, &angle, &conf);
+ if (!pix3) conf = 0.0; /* don't use */
+ L_INFO("angle = %6.2f, conf = %6.2f\n", procName, angle, conf);
+ if (conf > 5.0) {
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pixaAddBox(pixa, box, L_INSERT);
+ } else {
+ pixDestroy(&pix3);
+ boxDestroy(&box);
+ }
+ }
+ pixDestroy(&pix2);
+ boxaDestroy(&boxa);
+
+#if DEBUG_DESKEW
+ pix3 = pixaDisplayTiledInRows(pixa, 8, 1000, 1.0, 0, 30, 2);
+ pixWrite("lept/pix3.png", pix3, IFF_PNG);
+ pixDestroy(&pix3);
+#endif /* DEBUG_DESKEW */
+
+ return pixa;
+}
+
+
+/*!
+ * \brief pixReadBarcodes()
+ *
+ * \param[in] pixa of 8 bpp deskewed and cropped barcodes
+ * \param[in] format L_BF_ANY, L_BF_CODEI2OF5, L_BF_CODE93, ...
+ * \param[in] method L_USE_WIDTHS, L_USE_WINDOWS;
+ * \param[out] psaw [optional] sarray of bar widths
+ * \param[in] 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 w, h, i, j, n, nbars, ival;
+NUMA *na;
+PIX *pix1;
+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 */
+ pix1 = pixaGetPix(pixa, i, L_CLONE);
+ pixGetDimensions(pix1, &w, &h, NULL);
+ if (w < MIN_BC_WIDTH || h < MIN_BC_HEIGHT) {
+ L_ERROR("pix is too small: w = %d, h = %d\n", procName, w, h);
+ pixDestroy(&pix1);
+ continue;
+ }
+ na = pixReadBarcodeWidths(pix1, method, debugflag);
+ pixDestroy(&pix1);
+ 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;
+}
+
+
+/*!
+ * \brief pixReadBarcodeWidths()
+ *
+ * \param[in] pixs of 8 bpp deskewed and cropped barcode
+ * \param[in] method L_USE_WIDTHS, L_USE_WINDOWS;
+ * \param[in] 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)
+ lept_stderr("Window width for barcode: %7.3f\n", winwidth);
+ numaWriteStderr(na);
+#endif /* DEBUG_WIDTHS */
+
+ if (!na)
+ return (NUMA *)ERROR_PTR("barcode widths invalid", procName, NULL);
+
+ return na;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Locate barcode in image *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixLocateBarcodes()
+ *
+ * \param[in] pixs any depth
+ * \param[in] thresh for binarization of edge filter output; typ. 20
+ * \param[out] ppixb [optional] binarized edge filtered input image
+ * \param[out] ppixm [optional] 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;
+}
+
+
+/*!
+ * \brief pixGenerateBarcodeMask()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] maxspace largest space in the barcode, in pixels
+ * \param[in] nwidth opening 'width' to remove noise
+ * \param[in] nheight opening 'height' to remove noise
+ * \return pixm mask over barcodes, or NULL if none found or on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixDeskewBarcode()
+ *
+ * \param[in] pixs input image; 8 bpp
+ * \param[in] pixb binarized edge-filtered input image
+ * \param[in] box identified region containing barcode
+ * \param[in] margin of extra pixels around box to extract
+ * \param[in] threshold for binarization; ~20
+ * \param[out] pangle [optional] in degrees, clockwise is positive
+ * \param[out] pconf [optional] confidence
+ * \return pixd deskewed barcode, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The (optional) angle returned is the angle in degrees (cw positive)
+ * necessary to rotate the image so that it is deskewed.
+ * </pre>
+ */
+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 *box1, *box2;
+BOXA *boxa1, *boxa2;
+PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pixd;
+
+ PROCNAME("pixDeskewBarcode");
+
+ if (pangle) *pangle = 0.0;
+ if (pconf) *pconf = 0.0;
+ 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);
+ box2 = boxCreate(x - 25, y - 25, w + 51, h + 51);
+ pix1 = pixClipRectangle(pixb, box2, NULL);
+ pix2 = pixClipRectangle(pixs, box2, NULL);
+ boxDestroy(&box2);
+
+ /* Deskew, looking at all possible orientations over 180 degrees */
+ pix3 = pixRotateOrth(pix1, 1); /* look for vertical bar lines */
+ pix4 = pixClone(pix1); /* look for horizontal bar lines */
+ pixFindSkewSweepAndSearchScore(pix3, &angle1, &conf1, &score1,
+ 1, 1, 0.0, 45.0, 2.5, 0.01);
+ pixFindSkewSweepAndSearchScore(pix4, &angle2, &conf2, &score2,
+ 1, 1, 0.0, 45.0, 2.5, 0.01);
+ pixDestroy(&pix1);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+
+ /* 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;
+ pix5 = pixRotate(pix2, deg2rad * angle1, L_ROTATE_AREA_MAP,
+ L_BRING_IN_WHITE, 0, 0);
+ } else {
+ angle = 0.0;
+ pix5 = pixClone(pix2);
+ }
+ } else { /* score2 > score1 */
+ conf = conf2;
+ pix6 = pixRotateOrth(pix2, 1);
+ if (conf2 > 6.0 && L_ABS(angle2) > 0.1) {
+ angle = 90.0 + angle2;
+ pix5 = pixRotate(pix6, deg2rad * angle2, L_ROTATE_AREA_MAP,
+ L_BRING_IN_WHITE, 0, 0);
+ } else {
+ angle = 90.0;
+ pix5 = pixClone(pix6);
+ }
+ pixDestroy(&pix6);
+ }
+ pixDestroy(&pix2);
+
+ /* Extract barcode plus a margin around it */
+ boxa1 = pixLocateBarcodes(pix5, threshold, 0, 0);
+ if ((n = boxaGetCount(boxa1)) != 1) {
+ L_WARNING("barcode mask in %d components\n", procName, n);
+ boxa2 = boxaSort(boxa1, L_SORT_BY_AREA, L_SORT_DECREASING, NULL);
+ } else {
+ boxa2 = boxaCopy(boxa1, L_CLONE);
+ }
+ box1 = boxaGetBox(boxa2, 0, L_CLONE);
+ boxGetGeometry(box1, &x, &y, &w, &h);
+ box2 = boxCreate(x - margin, y - margin, w + 2 * margin,
+ h + 2 * margin);
+ pixd = pixClipRectangle(pix5, box2, NULL);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ boxaDestroy(&boxa1);
+ boxaDestroy(&boxa2);
+ pixDestroy(&pix5);
+
+ if (pangle) *pangle = angle;
+ if (pconf) *pconf = conf;
+
+ if (!pixd)
+ L_ERROR("pixd not made\n", procName);
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Process to get line widths *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixExtractBarcodeWidths1()
+ *
+ * \param[in] pixs input image; 8 bpp
+ * \param[in] thresh estimated pixel threshold for crossing
+ * white <--> black; typ. ~120
+ * \param[in] binfract histo binsize as a fraction of minsize; e.g., 0.25
+ * \param[out] pnaehist [optional] histogram of black widths; NULL ok
+ * \param[out] pnaohist [optional] histogram of white widths; NULL ok
+ * \param[in] debugflag use 1 to generate debug output
+ * \return nad numa of barcode widths in encoded integer units,
+ * or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (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.
+ * </pre>
+ */
+NUMA *
+pixExtractBarcodeWidths1(PIX *pixs,
+ l_float32 thresh,
+ l_float32 binfract,
+ NUMA **pnaehist,
+ NUMA **pnaohist,
+ l_int32 debugflag)
+{
+NUMA *nac, *nad;
+
+ PROCNAME("pixExtractBarcodeWidths1");
+
+ if (pnaehist) *pnaehist = NULL;
+ if (pnaohist) *pnaehist = NULL;
+ 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 */
+ if ((nac = pixExtractBarcodeCrossings(pixs, thresh, debugflag)) == NULL)
+ return (NUMA *)ERROR_PTR("nac not made", procName, NULL);
+
+ /* Get the array of bar widths, starting with a black bar */
+ nad = numaQuantizeCrossingsByWidth(nac, binfract, pnaehist,
+ pnaohist, debugflag);
+
+ numaDestroy(&nac);
+ return nad;
+}
+
+
+/*!
+ * \brief pixExtractBarcodeWidths2()
+ *
+ * \param[in] pixs input image; 8 bpp
+ * \param[in] thresh estimated pixel threshold for crossing
+ * white <--> black; typ. ~120
+ * \param[out] pwidth [optional] best decoding window width, in pixels
+ * \param[out] pnac [optional] number of transitions in each window
+ * \param[in] debugflag use 1 to generate debug output
+ * \return nad numa of barcode widths in encoded integer units,
+ * or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+NUMA *
+pixExtractBarcodeWidths2(PIX *pixs,
+ l_float32 thresh,
+ l_float32 *pwidth,
+ NUMA **pnac,
+ l_int32 debugflag)
+{
+l_int32 width;
+NUMA *nac, *nacp, *nad;
+
+ PROCNAME("pixExtractBarcodeWidths2");
+
+ if (pwidth) *pwidth = 0;
+ if (pnac) *pnac = NULL;
+ 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 */
+ if ((nacp = pixExtractBarcodeCrossings(pixs, thresh, debugflag)) == NULL)
+ return (NUMA *)ERROR_PTR("nacp not made", procName, NULL);
+
+ /* Quantize the crossings to get actual windowed data */
+ nad = numaQuantizeCrossingsByWindow(nacp, 2.0, pwidth, NULL,
+ pnac, debugflag);
+ numaDestroy(&nacp);
+ return nad;
+}
+
+
+/*!
+ * \brief pixExtractBarcodeCrossings()
+ *
+ * \param[in] pixs input image; 8 bpp
+ * \param[in] thresh estimated pixel threshold for crossing
+ * white <--> black; typ. ~120
+ * \param[in] debugflag use 1 to generate debug output
+ * \return numa of crossings, in pixel units, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Require at least 10 crossings.
+ * </pre>
+ */
+NUMA *
+pixExtractBarcodeCrossings(PIX *pixs,
+ l_float32 thresh,
+ l_int32 debugflag)
+{
+l_int32 w;
+l_float32 bestthresh;
+GPLOT *gplot;
+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 */
+ if ((nas = pixAverageRasterScans(pixs, 50)) == NULL)
+ return (NUMA *)ERROR_PTR("nas not made", procName, NULL);
+
+ /* 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) {
+ lept_mkdir("lept/barcode");
+ gplot = gplotCreate("/tmp/lept/barcode/signal", GPLOT_PNG,
+ "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);
+
+ if (numaGetCount(nad) < 10) {
+ L_ERROR("Only %d crossings; failure\n", procName, numaGetCount(nad));
+ numaDestroy(&nad);
+ }
+ return nad;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Average adjacent rasters *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixAverageRasterScans()
+ *
+ * \param[in] pixs input image; 8 bpp
+ * \param[in] 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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief numaQuantizeCrossingsByWidth()
+ *
+ * \param[in] nas numa of crossing locations, in pixel units
+ * \param[in] binfract histo binsize as a fraction of minsize; e.g., 0.25
+ * \param[out] pnaehist [optional] histo of even (black) bar widths
+ * \param[out] pnaohist [optional] histo of odd (white) bar widths
+ * \param[in] debugflag 1 to generate plots of histograms of bar widths
+ * \return nad sequence of widths, in unit sizes, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+NUMA *
+numaQuantizeCrossingsByWidth(NUMA *nas,
+ l_float32 binfract,
+ NUMA **pnaehist,
+ NUMA **pnaohist,
+ l_int32 debugflag)
+{
+l_int32 i, n, ret, 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 (pnaehist) *pnaehist = NULL;
+ if (pnaohist) *pnaohist = NULL;
+ if (!nas)
+ return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+ n = numaGetCount(nas);
+ if (n < 10)
+ return (NUMA *)ERROR_PTR("n < 10", procName, NULL);
+ if (binfract <= 0.0)
+ return (NUMA *)ERROR_PTR("binfract <= 0.0", procName, NULL);
+
+ /* Get even and odd crossing distances */
+ ret = numaGetCrossingDistances(nas, &naedist, &naodist, &minsize, &maxsize);
+ if (ret)
+ return (NUMA *)ERROR_PTR("crossing data not found", procName, NULL);
+
+ /* 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) {
+ lept_mkdir("lept/barcode");
+ gplot = gplotCreate("/tmp/lept/barcode/histw", GPLOT_PNG,
+ "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);
+/* lept_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);
+/* lept_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) {
+ lept_stderr(" ---- Black bar widths (pixels) ------ \n");
+ numaWriteStderr(naedist);
+ lept_stderr(" ---- Histogram of black bar widths ------ \n");
+ numaWriteStderr(naehist);
+ lept_stderr(" ---- Peak ranges in black bar histogram bins --- \n");
+ numaWriteStderr(naerange);
+ lept_stderr(" ---- Peak black bar centroid width values ------ \n");
+ numaWriteStderr(naecent);
+ lept_stderr(" ---- Black bar lookup table ------ \n");
+ numaWriteStderr(naelut);
+ lept_stderr(" ---- White bar widths (pixels) ------ \n");
+ numaWriteStderr(naodist);
+ lept_stderr(" ---- Histogram of white bar widths ------ \n");
+ numaWriteStderr(naohist);
+ lept_stderr(" ---- Peak ranges in white bar histogram bins --- \n");
+ numaWriteStderr(naorange);
+ lept_stderr(" ---- Peak white bar centroid width values ------ \n");
+ numaWriteStderr(naocent);
+ lept_stderr(" ---- White bar lookup table ------ \n");
+ numaWriteStderr(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;
+}
+
+
+/*!
+ * \brief numaGetCrossingDistances()
+ *
+ * \param[in] nas numa of crossing locations
+ * \param[out] pnaedist [optional] even distances between crossings
+ * \param[out] pnaodist [optional] odd distances between crossings
+ * \param[out] pmindist [optional] min distance between crossings
+ * \param[out] pmaxdist [optional] 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;
+}
+
+
+/*!
+ * \brief numaLocatePeakRanges()
+ *
+ * \param[in] nas numa of histogram of crossing widths
+ * \param[in] minfirst min location of center of first peak
+ * \param[in] minsep min separation between peak range centers
+ * \param[in] maxmin max allowed value for min histo value between peaks
+ * \return nad ranges for each peak found, in pairs, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaGetPeakCentroids()
+ *
+ * \param[in] nahist numa of histogram of crossing widths
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief numaGetPeakWidthLUT()
+ *
+ * \param[in] narange numa of x-val ranges for the histogram width peaks
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaQuantizeCrossingsByWindow()
+ *
+ * \param[in] nas numa of crossing locations
+ * \param[in] ratio of max window size over min window size in search;
+ * typ. 2.0
+ * \param[out] pwidth [optional] best window width
+ * \param[out] pfirstloc [optional] center of window for first xing
+ * \param[out] pnac [optional] array of window crossings (0, 1, 2)
+ * \param[in] debugflag 1 to generate various plots of intermediate results
+ * \return nad sequence of widths, in unit sizes, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief numaEvalBestWidthAndShift()
+ *
+ * \param[in] nas numa of crossing locations
+ * \param[in] nwidth number of widths to consider
+ * \param[in] nshift number of shifts to consider for each width
+ * \param[in] minwidth smallest width to consider
+ * \param[in] maxwidth largest width to consider
+ * \param[out] pbestwidth best size of window
+ * \param[out] pbestshift best shift for the window
+ * \param[out] pbestscore [optional] average squared error of dist
+ * of crossing signal from the center of the window
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ bestwidth = 0.0f;
+ bestshift = 0.0f;
+ 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
+ lept_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;
+}
+
+
+/*!
+ * \brief numaEvalSyncError()
+ *
+ * \param[in] nas numa of crossing locations
+ * \param[in] ifirst first crossing to use
+ * \param[in] ilast last crossing to use; use 0 for all crossings
+ * \param[in] width size of window
+ * \param[in] shift of center of window w/rt first crossing
+ * \param[out] pscore [optional] average squared error of dist
+ * of crossing signal from the center of the window
+ * \param[out] pnad [optional] numa of 1s and 0s for crossings
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/readbarcode.h b/leptonica/src/readbarcode.h
new file mode 100644
index 00000000..358ff4e5
--- /dev/null
+++ b/leptonica/src/readbarcode.h
@@ -0,0 +1,239 @@
+/*====================================================================*
+ - 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 *
+ * ----------------------------------------------------------------- */
+
+/*! Barcode Method */
+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. *
+ * ----------------------------------------------------------------- */
+
+/*! Barcode Format */
+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. *
+ * ----------------------------------------------------------------- */
+
+/*! Currently supported formats */
+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
+};
+
+/*! Currently supported format names */
+static const char *SupportedBarcodeFormatName[] = {
+ "Code2of5",
+ "CodeI2of5",
+ "Code93",
+ "Code39",
+ "Codabar",
+ "Upca",
+ "Ean13"
+};
+static const l_int32 NumSupportedBarcodeFormats = 7; /*!< Number of formats */
+
+
+ /* ----------------------------------------------------------------- *
+ * 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/leptonica/src/readfile.c b/leptonica/src/readfile.c
new file mode 100644
index 00000000..68d4c819
--- /dev/null
+++ b/leptonica/src/readfile.c
@@ -0,0 +1,1633 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file readfile.c: reads image on file into memory
+ * <pre>
+ *
+ * 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()
+ *
+ * Output image file information
+ * void writeImageFileInfo()
+ *
+ * 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.
+ * </pre>
+ */
+
+#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_JPEG = "/tmp/lept/format/file_jpeg.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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaReadFiles()
+ *
+ * \param[in] dirname
+ * \param[in] substr [optional] substring filter on filenames; can be null
+ * \return pixa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaReadFilesSA()
+ *
+ * \param[in] sa 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;
+}
+
+
+/*!
+ * \brief pixRead()
+ *
+ * \param[in] filename with full pathname or in local directory
+ * \return pix if OK; NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See at top of file for supported formats.
+ * </pre>
+ */
+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;
+ }
+ pix = pixReadStream(fp, 0);
+ fclose(fp);
+ if (!pix)
+ return (PIX *)ERROR_PTR("pix not read", procName, NULL);
+ return pix;
+}
+
+
+/*!
+ * \brief pixReadWithHint()
+ *
+ * \param[in] filename with full pathname or in local directory
+ * \param[in] hint bitwise OR of L_HINT_* values for jpeg;
+ * use 0 for no hint
+ * \return pix if OK; NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The hint is not binding, but may be used to optimize jpeg decoding.
+ * Use 0 for no hinting.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixReadIndexed()
+ *
+ * \param[in] sa string array of full pathnames
+ * \param[in] index into pathname array
+ * \return pix if OK; null if not found
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixReadStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] hint bitwise OR of L_HINT_* values for jpeg; 0 for no hint
+ * \return pix if OK; NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The hint only applies to jpeg.
+ * </pre>
+ */
+PIX *
+pixReadStream(FILE *fp,
+ l_int32 hint)
+{
+l_int32 format, ret, valid;
+l_uint8 *comment;
+PIX *pix;
+PIXCMAP *cmap;
+
+ 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:
+ case IFF_TIFF_JPEG:
+ 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_PS:
+ L_ERROR("PostScript reading is not supported\n", procName);
+ return NULL;
+
+ case IFF_LPDF:
+ L_ERROR("Pdf reading is not supported\n", procName);
+ return NULL;
+
+ 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);
+ if ((cmap = pixGetColormap(pix))) {
+ pixcmapIsValid(cmap, pix, &valid);
+ if (!valid) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("invalid colormap", procName, NULL);
+ }
+ }
+ }
+ return pix;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ * Read header information from file *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixReadHeader()
+ *
+ * \param[in] filename with full pathname or in local directory
+ * \param[out] pformat [optional] file format
+ * \param[out] pw, ph [optional] width and height
+ * \param[out] pbps [optional] bits/sample
+ * \param[out] pspp [optional] samples/pixel 1, 3 or 4
+ * \param[out] piscmap [optional] 1 if cmap exists; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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:
+ case IFF_TIFF_JPEG:
+ /* 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_PS:
+ if (pformat) *pformat = format;
+ return ERROR_INT("PostScript reading is not supported\n", procName, 1);
+
+ case IFF_LPDF:
+ if (pformat) *pformat = format;
+ return ERROR_INT("Pdf reading is not supported\n", procName, 1);
+
+ 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief findFileFormat()
+ *
+ * \param[in] filename
+ * \param[out] pformat found format
+ * \return 0 if OK, 1 on error or if format is not recognized
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief findFileFormatStream()
+ *
+ * \param[in] fp file stream
+ * \param[out] pformat found format
+ * \return 0 if OK, 1 on error or if format is not recognized
+ *
+ * <pre>
+ * Notes:
+ * (1) Important: Side effect -- this resets fp to BOF.
+ * </pre>
+ */
+l_ok
+findFileFormatStream(FILE *fp,
+ l_int32 *pformat)
+{
+l_uint8 firstbytes[13];
+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(&firstbytes, 1, 12, fp) != 12)
+ return ERROR_INT("failed to read first 12 bytes of file", procName, 1);
+ firstbytes[12] = 0;
+ 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;
+}
+
+
+/*!
+ * \brief findFileFormatBuffer()
+ *
+ * \param[in] buf byte buffer at least 12 bytes in size; we can't check
+ * \param[out] pformat found format
+ * \return 0 if OK, 1 on error or if format is not recognized
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+l_ok
+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 ASCII 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;
+ }
+
+ if (buf[0] == 'P' && buf[1] == '7') { /* new arbitrary (PAM) */
+ *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 (memcmp(buf, JP2K_CODESTREAM, 4) == 0 ||
+ memcmp(buf, 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 ps */
+ if (buf[0] == '%' && buf[1] == '!' && buf[2] == 'P' && buf[3] == 'S' &&
+ buf[4] == '-' && buf[5] == 'A' && buf[6] == 'd' && buf[7] == 'o' &&
+ buf[8] == 'b' && buf[9] == 'e') {
+ *pformat = IFF_PS;
+ return 0;
+ }
+
+ /* Check for pdf */
+ if (buf[0] == '%' && buf[1] == 'P' && buf[2] == 'D' && buf[3] == 'F' &&
+ buf[4] == '-' && buf[5] == '1') {
+ *pformat = IFF_LPDF;
+ 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;
+}
+
+
+/*!
+ * \brief fileFormatIsTiff()
+ *
+ * \param[in] 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 || format == IFF_TIFF_JPEG)
+ return 1;
+ else
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Read from memory *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixReadMem()
+ *
+ * \param[in] data const; encoded
+ * \param[in] size size of data
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * 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 writes to a temp file and
+ * decompresses 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.
+ * </pre>
+ */
+PIX *
+pixReadMem(const l_uint8 *data,
+ size_t size)
+{
+l_int32 format, valid;
+PIX *pix;
+PIXCMAP *cmap;
+
+ 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_PS:
+ L_ERROR("PostScript reading is not supported\n", procName);
+ return NULL;
+
+ case IFF_LPDF:
+ L_ERROR("Pdf reading is not supported\n", procName);
+ return NULL;
+
+ 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. Also
+ * verify that the colormap is valid. */
+ if (pix) {
+ if (format == IFF_TIFF && pixGetDepth(pix) == 1)
+ format = IFF_TIFF_G4;
+ pixSetInputFormat(pix, format);
+ if ((cmap = pixGetColormap(pix))) {
+ pixcmapIsValid(cmap, pix, &valid);
+ if (!valid) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("invalid colormap", procName, NULL);
+ }
+ }
+ pixSetPadBits(pix, 0);
+ }
+ return pix;
+}
+
+
+/*!
+ * \brief pixReadHeaderMem()
+ *
+ * \param[in] data const; encoded
+ * \param[in] size size of data
+ * \param[out] pformat [optional] image format
+ * \param[out] pw, ph [optional] width and height
+ * \param[out] pbps [optional] bits/sample
+ * \param[out] pspp [optional] samples/pixel 1, 3 or 4
+ * \param[out] piscmap [optional] 1 if cmap exists; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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 12 bytes to decide on
+ * the format, which we require.
+ * </pre>
+ */
+l_ok
+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 < 12)
+ return ERROR_INT("size < 12", 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:
+ case IFF_TIFF_JPEG:
+ /* 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_PS:
+ if (pformat) *pformat = format;
+ return ERROR_INT("PostScript reading is not supported\n", procName, 1);
+
+ case IFF_LPDF:
+ if (pformat) *pformat = format;
+ return ERROR_INT("Pdf reading is not supported\n", procName, 1);
+
+ case IFF_SPIX:
+ ret = sreadHeaderSpix((l_uint32 *)data, size, &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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Output image file information *
+ *---------------------------------------------------------------------*/
+extern const char *ImageFileFormatExtensions[];
+
+/*!
+ * \brief writeImageFileInfo()
+ *
+ * \param[in] filename input file
+ * \param[in] fpout output file stream
+ * \param[in] headeronly 1 to read only the header; 0 to read both
+ * the header and the input file
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If headeronly == 0 and the image has spp == 4,this will
+ * also call pixDisplayLayersRGBA() to display the image
+ * in three views.
+ * (2) This is a debug function that changes the value of
+ * var_PNG_STRIP_16_TO_8 to 1 (the default).
+ * </pre>
+ */
+l_ok
+writeImageFileInfo(const char *filename,
+ FILE *fpout,
+ l_int32 headeronly)
+{
+char *text;
+l_int32 w, h, d, wpl, count, npages, color;
+l_int32 format, bps, spp, iscmap, xres, yres, transparency;
+FILE *fpin;
+PIX *pix, *pixt;
+PIXCMAP *cmap;
+
+ PROCNAME("writeImageFileInfo");
+
+ if (!filename)
+ return ERROR_INT("filename not defined", procName, 1);
+ if (!fpout)
+ return ERROR_INT("stream not defined", procName, 1);
+
+ /* Read the header */
+ if (pixReadHeader(filename, &format, &w, &h, &bps, &spp, &iscmap)) {
+ L_ERROR("failure to read header of %s\n", procName, filename);
+ return 1;
+ }
+ fprintf(fpout, "===============================================\n"
+ "Reading the header:\n");
+ fprintf(fpout, " input image format type: %s\n",
+ ImageFileFormatExtensions[format]);
+ fprintf(fpout, " w = %d, h = %d, bps = %d, spp = %d, iscmap = %d\n",
+ w, h, bps, spp, iscmap);
+
+ findFileFormat(filename, &format);
+ if (format == IFF_JP2) {
+ fpin = lept_fopen(filename, "rb");
+ fgetJp2kResolution(fpin, &xres, &yres);
+ fclose(fpin);
+ fprintf(fpout, " xres = %d, yres = %d\n", xres, yres);
+ } else if (format == IFF_PNG) {
+ fpin = lept_fopen(filename, "rb");
+ fgetPngResolution(fpin, &xres, &yres);
+ fclose(fpin);
+ fprintf(fpout, " xres = %d, yres = %d\n", xres, yres);
+ if (iscmap) {
+ fpin = lept_fopen(filename, "rb");
+ fgetPngColormapInfo(fpin, &cmap, &transparency);
+ fclose(fpin);
+ if (transparency)
+ fprintf(fpout, " colormap has transparency\n");
+ else
+ fprintf(fpout, " colormap does not have transparency\n");
+ pixcmapWriteStream(fpout, cmap);
+ pixcmapDestroy(&cmap);
+ }
+ } else if (format == IFF_JFIF_JPEG) {
+ fpin = lept_fopen(filename, "rb");
+ fgetJpegResolution(fpin, &xres, &yres);
+ fclose(fpin);
+ fprintf(fpout, " xres = %d, yres = %d\n", xres, yres);
+ }
+
+ if (headeronly)
+ return 0;
+
+ /* Read the full image. Note that when we read an image that
+ * has transparency in a colormap, we convert it to RGBA. */
+ fprintf(fpout, "===============================================\n"
+ "Reading the full image:\n");
+
+ /* Preserve 16 bpp if the format is png */
+ if (format == IFF_PNG && bps == 16)
+ l_pngSetReadStrip16To8(0);
+
+ if ((pix = pixRead(filename)) == NULL) {
+ L_ERROR("failure to read full image of %s\n", procName, filename);
+ return 1;
+ }
+
+ format = pixGetInputFormat(pix);
+ pixGetDimensions(pix, &w, &h, &d);
+ wpl = pixGetWpl(pix);
+ spp = pixGetSpp(pix);
+ fprintf(fpout, " input image format type: %s\n",
+ ImageFileFormatExtensions[format]);
+ fprintf(fpout, " w = %d, h = %d, d = %d, spp = %d, wpl = %d\n",
+ w, h, d, spp, wpl);
+ fprintf(fpout, " xres = %d, yres = %d\n",
+ pixGetXRes(pix), pixGetYRes(pix));
+
+ text = pixGetText(pix);
+ if (text) /* not null */
+ fprintf(fpout, " text: %s\n", text);
+
+ cmap = pixGetColormap(pix);
+ if (cmap) {
+ pixcmapHasColor(cmap, &color);
+ if (color)
+ fprintf(fpout, " colormap exists and has color values:");
+ else
+ fprintf(fpout, " colormap exists and has only gray values:");
+ pixcmapWriteStream(fpout, pixGetColormap(pix));
+ }
+ else
+ fprintf(fpout, " colormap does not exist\n");
+
+ if (format == IFF_TIFF || format == IFF_TIFF_G4 ||
+ format == IFF_TIFF_G3 || format == IFF_TIFF_PACKBITS) {
+ fprintf(fpout, " Tiff header information:\n");
+ fpin = lept_fopen(filename, "rb");
+ tiffGetCount(fpin, &npages);
+ lept_fclose(fpin);
+ if (npages == 1)
+ fprintf(fpout, " One page in file\n");
+ else
+ fprintf(fpout, " %d pages in file\n", npages);
+ fprintTiffInfo(fpout, filename);
+ }
+
+ if (d == 1) {
+ pixCountPixels(pix, &count, NULL);
+ pixGetDimensions(pix, &w, &h, NULL);
+ fprintf(fpout, " 1 bpp: foreground pixel fraction ON/Total = %g\n",
+ (l_float32)count / (l_float32)(w * h));
+ }
+ fprintf(fpout, "===============================================\n");
+
+ /* If there is an alpha component, visualize it. Note that when
+ * alpha == 0, the rgb layer is transparent. We visualize the
+ * result when a white background is visible through the
+ * transparency layer. */
+ if (pixGetSpp(pix) == 4) {
+ pixt = pixDisplayLayersRGBA(pix, 0xffffff00, 600.0);
+ pixDisplay(pixt, 100, 100);
+ pixDestroy(&pixt);
+ }
+
+ if (format == IFF_PNG && bps == 16)
+ l_pngSetReadStrip16To8(1); /* return to default if format is png */
+
+ pixDestroy(&pix);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Test function for I/O with different formats *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief ioFormatTest()
+ *
+ * \param[in] filename input image file
+ * \return 0 if OK; 1 on error or if the test fails
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+ioFormatTest(const char *filename)
+{
+l_int32 w, h, d, equal, problems;
+#if HAVE_LIBJPEG || HAVE_LIBWEBP || HAVE_LIBJP2K
+l_int32 depth;
+#endif
+#if HAVE_LIBJPEG || HAVE_LIBTIFF || HAVE_LIBWEBP || HAVE_LIBJP2K
+l_float32 diff;
+#endif
+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 jpeg encoding works for grayscale and rgb */
+ if (d == 8 || d == 32) {
+ PIX *pixc1;
+ L_INFO("write/read jpeg compressed tiff\n", procName);
+ if (d == 8 && pixGetColormap(pixc)) {
+ pixc1 = pixRemoveColormap(pixc, REMOVE_CMAP_BASED_ON_SRC);
+ pixWrite(FILE_TIFF_JPEG, pixc1, IFF_TIFF_JPEG);
+ if ((pix1 = pixRead(FILE_TIFF_JPEG)) == NULL) {
+ L_INFO(" did not read FILE_TIFF_JPEG\n", procName);
+ problems = TRUE;
+ }
+ pixDestroy(&pixc1);
+ } else {
+ pixWrite(FILE_TIFF_JPEG, pixc, IFF_TIFF_JPEG);
+ pix1 = pixRead(FILE_TIFF_JPEG);
+ if (d == 8) {
+ pixCompareGray(pix1, pixc, L_COMPARE_ABS_DIFF, 0, NULL, &diff,
+ NULL, NULL);
+ } else {
+ pixCompareRGB(pix1, pixc, L_COMPARE_ABS_DIFF, 0, NULL, &diff,
+ NULL, NULL);
+ }
+ if (diff > 8.0) {
+ L_INFO(" **** bad tiff jpeg compressed image: "
+ "d = %d, diff = %5.2f ****\n", procName, d, diff);
+ 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);
+ }
+ lept_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/leptonica/src/recog.h b/leptonica/src/recog.h
new file mode 100644
index 00000000..44e6aa18
--- /dev/null
+++ b/leptonica/src/recog.h
@@ -0,0 +1,264 @@
+/*====================================================================*
+ - 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
+
+/*!
+ * \file recog.h
+ *
+ * <pre>
+ * This is a simple utility for training and recognizing individual
+ * machine-printed text characters. It is designed to be adapted
+ * to a particular set of character images; e.g., from a book.
+ *
+ * There are two methods of training the recognizer. In the most
+ * simple, a set of bitmaps has been labeled by some means, such
+ * a generic OCR program. This is input either one template at a time
+ * or as a pixa of templates, to a function that creates a recog.
+ * If in a pixa, the text string label must be embedded in the
+ * text field of each pix.
+ *
+ * If labeled data is not available, we start with a bootstrap
+ * recognizer (BSR) that has labeled data from a variety of sources.
+ * These images are scaled, typically to a fixed height, and then
+ * fed similarly scaled unlabeled images from the source (e.g., book),
+ * and the BSR attempts to identify them. All images that have
+ * a high enough correlation score with one of the templates in the
+ * BSR are emitted in a pixa, which now holds unscaled and labeled
+ * templates from the source. This is the generator for a book adapted
+ * recognizer (BAR).
+ *
+ * The pixa should always be thought of as the primary structure.
+ * It is the generator for the recog, because a recog is built
+ * from a pixa of unscaled images.
+ *
+ * New image templates can be added to a recog as long as it is
+ * in training mode. Once training is finished, to add templates
+ * it is necessary to extract the generating pixa, add templates
+ * to that pixa, and make a new recog. Similarly, we do not
+ * join two recog; instead, we simply join their generating pixa,
+ * and make a recog from that.
+ *
+ * To remove outliers from a pixa of labeled pix, make a recog,
+ * determine the outliers, and generate a new pixa with the
+ * outliers removed. The outliers are determined by building
+ * special templates for each character set that are scaled averages
+ * of the individual templates. Then a correlation score is found
+ * between each template and the averaged templates. There are
+ * two implementations; outliers are determined as either:
+ * (1) a template having a correlation score with its class average
+ * that is below a threshold, or
+ * (2) a template having a correlation score with its class average
+ * that is smaller than the correlation score with the average
+ * of another class.
+ * Outliers are removed from the generating pixa. Scaled averaging
+ * is only performed for determining outliers and for splitting
+ * characters; it is never used in a trained recognizer for identifying
+ * unlabeled samples.
+ *
+ * Two methods using averaged templates are provided for splitting
+ * touching characters:
+ * (1) greedy matching
+ * (2) document image decoding (DID)
+ * The DID method is the default. It is about 5x faster and
+ * possibly more accurate.
+ *
+ * Once a BAR has been made, unlabeled sample images are identified
+ * by finding the individual template in the BAR with highest
+ * correlation. The input images and images in the BAR can be
+ * represented in two ways:
+ * (1) as scanned, binarized to 1 bpp
+ * (2) as a width-normalized outline formed by thinning to a
+ * skeleton and then dilating by a fixed amount.
+ *
+ * The recog can be serialized to file and read back. The serialized
+ * version holds the templates used for correlation (which may have
+ * been modified by scaling and turning into lines from the unscaled
+ * templates), plus, for arbitrary character sets, the UTF8
+ * representation and the lookup table mapping from the character
+ * representation to index.
+ *
+ * Why do we not use averaged templates for recognition?
+ * Letterforms can take on significantly different shapes (eg.,
+ * the letters 'a' and 'g'), and it makes no sense to average these.
+ * The previous version of this utility allowed multiple recognizers
+ * to exist, but this is an unnecessary complication if recognition
+ * is done on all samples instead of on averages.
+ * </pre>
+ */
+
+#define RECOG_VERSION_NUMBER 2
+
+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 linew; /*!< use a value > 0 to convert the bitmap */
+ /*!< to lines of fixed width; 0 to skip */
+ l_int32 templ_use; /*!< template use: use either the average */
+ /*!< or all temmplates (L_USE_AVERAGE or */
+ /*!< 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_int32 charset_type; /*!< one of L_ARABIC_NUMERALS, etc. */
+ l_int32 charset_size; /*!< expected number of classes in charset */
+ l_int32 min_nopad; /*!< min number of samples without padding */
+ l_int32 num_samples; /*!< number of training samples */
+ l_int32 minwidth_u; /*!< min width averaged unscaled templates */
+ l_int32 maxwidth_u; /*!< max width averaged unscaled templates */
+ l_int32 minheight_u; /*!< min height averaged unscaled templates */
+ l_int32 maxheight_u; /*!< max height averaged unscaled templates */
+ l_int32 minwidth; /*!< min width averaged scaled templates */
+ l_int32 maxwidth; /*!< max width 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_float32 max_wh_ratio; /*!< max width/height ratio to split */
+ l_float32 max_ht_ratio; /*!< max of max/min template height ratio */
+ l_int32 min_splitw; /*!< min component width 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 charset */
+ l_int32 *centtab; /*!< table for finding centroids */
+ l_int32 *sumtab; /*!< table for finding pixel sums */
+ struct Pixaa *pixaa_u; /*!< all unscaled templates for each class */
+ struct Ptaa *ptaa_u; /*!< centroids of all unscaled templates */
+ struct Numaa *naasum_u; /*!< area of all unscaled templates */
+ struct Pixaa *pixaa; /*!< all (scaled) templates for each class */
+ struct Ptaa *ptaa; /*!< centroids of all (scaledl) templates */
+ struct Numaa *naasum; /*!< area of all (scaled) templates */
+ struct Pixa *pixa_u; /*!< averaged unscaled templates per class */
+ struct Pta *pta_u; /*!< centroids of unscaled ave. templates */
+ struct Numa *nasum_u; /*!< area of unscaled averaged templates */
+ struct Pixa *pixa; /*!< averaged (scaled) templates per class */
+ struct Pta *pta; /*!< centroids of (scaled) ave. templates */
+ struct Numa *nasum; /*!< area of (scaled) averaged templates */
+ struct Pixa *pixa_tr; /*!< all 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 */
+};
+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 average 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 cols */
+ 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 Boxa *boxa; /*!< Viterbi result for splitting input pixs */
+ struct Numa *nascore; /*!< correlation scores: best path templates */
+ struct Numa *natempl_r; /*!< indices of best rescored templates */
+ struct Numa *nasample_r; /*!< samples of best scored 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 describing limited character sets *
+ *-------------------------------------------------------------------------*/
+/*! Character Set */
+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 */
+};
+
+/*-------------------------------------------------------------------------*
+ * Flags for selecting between using average and all templates: *
+ * recog->templ_use *
+ *-------------------------------------------------------------------------*/
+/*! Template Select */
+enum {
+ L_USE_ALL_TEMPLATES = 0, /*!< use all templates; default */
+ L_USE_AVERAGE_TEMPLATES = 1 /*!< use average templates; special cases */
+};
+
+#endif /* LEPTONICA_RECOG_H */
diff --git a/leptonica/src/recogbasic.c b/leptonica/src/recogbasic.c
new file mode 100644
index 00000000..f57af91e
--- /dev/null
+++ b/leptonica/src/recogbasic.c
@@ -0,0 +1,1231 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file recogbasic.c
+ * <pre>
+ *
+ * Recog creation, destruction and access
+ * L_RECOG *recogCreateFromRecog()
+ * L_RECOG *recogCreateFromPixa()
+ * L_RECOG *recogCreateFromPixaNoFinish()
+ * L_RECOG *recogCreate()
+ * void recogDestroy()
+ *
+ * Recog accessors
+ * l_int32 recogGetCount()
+ * l_int32 recogSetParams()
+ * static l_int32 recogGetCharsetSize()
+ *
+ * Character/index lookup
+ * l_int32 recogGetClassIndex()
+ * l_int32 recogStringToIndex()
+ * l_int32 recogGetClassString()
+ * l_int32 l_convertCharstrToInt()
+ *
+ * Serialization
+ * L_RECOG *recogRead()
+ * L_RECOG *recogReadStream()
+ * L_RECOG *recogReadMem()
+ * l_int32 recogWrite()
+ * l_int32 recogWriteStream()
+ * l_int32 recogWriteMem()
+ * PIXA *recogExtractPixa()
+ * 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 labeled and unlabeled 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 assembled from data that has
+ * been labeled by a generic recognition system, such as Tesseract.
+ * The general procedure to create a recognizer (recog) from labeled data is
+ * to add the labeled character bitmaps, either one at a time or
+ * all together from a pixa with labeled pix.
+ *
+ * The suggested use for a BAR that consists of labeled templates drawn
+ * from a single source (e.g., a book) is to identify unlabeled samples
+ * by using unscaled character templates in the BAR, picking the
+ * template closest to the unlabeled sample.
+ *
+ * Outliers can be removed from a pixa of labeled pix. This is one of
+ * two methods that use averaged templates (the other is greedy splitting
+ * of characters). See recogtrain.c for a discussion and the implementation.
+ *
+ * A special bootstrap recognizer (BSR) can be used to make a BAR from
+ * unlabeled book data. This is done by comparing character images
+ * from the book with labeled templates in the BSR, where all images
+ * are scaled to h = 40. The templates can be either the scanned images
+ * or images consisting of width-normalized strokes derived from
+ * the skeleton of the character bitmaps.
+ *
+ * Two BARs of labeled character data, that have been made by
+ * different recognizers, can be joined by extracting a pixa of the
+ * labeled templates from each, joining the two pixa, and then
+ * and regenerating a BAR from the joined set of templates.
+ * If all the labeled character data is from a single source (e.g, a book),
+ * identification can proceed using unscaled templates (either the input
+ * image or width-normalized lines). But if the labeled data comes from
+ * more than one source, (a "hybrid" recognizer), the templates should
+ * be scaled, and we recommend scaling to a fixed height.
+ *
+ * Suppose it is not possible to generate a BAR with a sufficient number
+ * of templates of each class taken from a single source. In that case,
+ * templates from the BSR itself can be added. This is the condition
+ * described above, where the labeled templates come from multiple
+ * sources, and it is necessary to do all character matches using
+ * templates that have been scaled to a fixed height (e.g., 40).
+ * Likewise, the samples to be identified using this hybrid recognizer
+ * must be modified in the same way. See prog/recogtest3.c for an
+ * example of the steps that can be taken in the construction of a BAR
+ * using a BSR.
+ *
+ * For training numeric input, an example set of calls that scales
+ * each training input to fixed h and will use the line templates of
+ * width linew for identifying unknown characters is:
+ * L_Recog *rec = recogCreate(0, h, linew, 128, 1);
+ * for (i = 0; i < n; i++) { // read in n training digits
+ * Pix *pix = ...
+ * recogTrainLabeled(rec, pix, NULL, text[i], 0);
+ * }
+ * recogTrainingFinished(&rec, 1, -1, -1.0); // required
+ *
+ * It is an error if any function that computes averages, removes
+ * outliers or requests identification of an unlabeled character,
+ * such as:
+ * (1) computing the sample averages: recogAverageSamples()
+ * (2) removing outliers: recogRemoveOutliers1() or recogRemoveOutliers2()
+ * (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, you can set
+ * recog->train_done = FALSE;
+ * add the new training samples, and again call
+ * recogTrainingFinished(&rec, 1, -1, -1.0); // required
+ *
+ * If not scaling, using the images directly for identification, and
+ * removing outliers, do something like this:
+ * L_Recog *rec = recogCreate(0, 0, 0, 128, 1);
+ * for (i = 0; i < n; i++) { // read in n training characters
+ * Pix *pix = ...
+ * recogTrainLabeled(rec, pix, NULL, text[i], 0);
+ * }
+ * recogTrainingFinished(&rec, 1, -1, -1.0);
+ * if (!rec) ... [return]
+ * // remove outliers
+ * recogRemoveOutliers1(&rec, 0.7, 2, NULL, NULL);
+ *
+ * You can generate a recognizer from a pixa where the text field in
+ * each pix is the character string label for the pix. For example,
+ * the following recognizer will store unscaled line images:
+ * L_Recog *rec = recogCreateFromPixa(pixa, 0, 0, linew, 128, 1);
+ * and in use, it is fed unscaled line images to identify.
+ *
+ * For the following, assume that you have a pixa of labeled templates.
+ * If it is likely that some of the input templates are mislabeled,
+ * there are several things that can be done to remove them.
+ * The first is to put a size and quantity filter on them; e.g.
+ * Pixa *pixa2 = recogFilterPixaBySize(pixa1, 10, 15, 2.6);
+ * Then you can remove outliers; e.g.,
+ * Pixa *pixa3 = pixaRemoveOutliers2(pixa2, -1.0, -1, NULL, NULL);
+ *
+ * To this point, all templates are from a single source, so you
+ * can make a recognizer that uses the unscaled templates and optionally
+ * attempts to split touching characters:
+ * L_Recog *recog1 = recogCreateFromPixa(pixa3, ...);
+ * Alternatively, if you need more templates for some of the classes,
+ * you can pad with templates from a "bootstrap" recognizer (BSR).
+ * If you pad, it is necessary to scale the templates and input
+ * samples to a fixed height, and no attempt will be made to split
+ * the input sample connected components:
+ * L_Recog *recog1 = recogCreateFromPixa(pixa3, 0, 40, 0, 128, 0);
+ * recogPadDigitTrainingSet(&recog1, 40, 0);
+ *
+ * A special case is a pure BSR, that contains images scaled to a fixed
+ * height (we use 40 in these examples).
+ * For this,use either the scanned bitmap:
+ * L_Recog *recboot = recogCreateFromPixa(pixa, 0, 40, 0, 128, 1);
+ * or width-normalized lines (use width of 5 here):
+ * L_Recog *recboot = recogCreateFromPixa(pixa, 0, 40, 5, 128, 1);
+ *
+ * This can be used to train a new book adapted recognizer (BAC), on
+ * unlabeled data from, e.g., a book. To do this, the following is required:
+ * (1) the input images from the book must be scaled in the same
+ * way as those in the BSR, and
+ * (2) both the BSR and the input images must be set up to be either
+ * input scanned images or width-normalized lines.
+ *
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32 MaxExamplesInClass = 256;
+
+ /* Default recog parameters that can be changed */
+static const l_int32 DefaultCharsetType = L_ARABIC_NUMERALS;
+static const l_int32 DefaultMinNopad = 1;
+static const l_float32 DefaultMaxWHRatio = 3.0; /* max allowed w/h
+ ratio for a component to be split */
+static const l_float32 DefaultMaxHTRatio = 2.6; /* max allowed ratio of
+ max/min unscaled averaged template heights */
+static const l_int32 DefaultThreshold = 150; /* for binarization */
+static const l_int32 DefaultMaxYShift = 1; /* for identification */
+
+ /* Static functions */
+static l_int32 recogGetCharsetSize(l_int32 type);
+static l_int32 recogAddCharstrLabels(L_RECOG *recog);
+static l_int32 recogAddAllSamples(L_RECOG **precog, PIXAA *paa, l_int32 debug);
+
+
+/*------------------------------------------------------------------------*
+ * Recog: initialization and destruction *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogCreateFromRecog()
+ *
+ * \param[in] recs source recog with arbitrary input parameters
+ * \param[in] scalew scale all widths to this; use 0 otherwise
+ * \param[in] scaleh scale all heights to this; use 0 otherwise
+ * \param[in] linew width of normalized strokes; use 0 to skip
+ * \param[in] threshold for binarization; typically ~128
+ * \param[in] maxyshift from nominal centroid alignment; default is 1
+ * \return recd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a convenience function that generates a recog using
+ * the unscaled training data in an existing recog.
+ * (2) It is recommended to use %maxyshift = 1 (the default value)
+ * (3) See recogCreate() for use of %scalew, %scaleh and %linew.
+ * </pre>
+ */
+L_RECOG *
+recogCreateFromRecog(L_RECOG *recs,
+ l_int32 scalew,
+ l_int32 scaleh,
+ l_int32 linew,
+ 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 = recogExtractPixa(recs);
+ recd = recogCreateFromPixa(pixa, scalew, scaleh, linew, threshold,
+ maxyshift);
+ pixaDestroy(&pixa);
+ return recd;
+}
+
+
+/*!
+ * \brief recogCreateFromPixa()
+ *
+ * \param[in] pixa of labeled, 1 bpp images
+ * \param[in] scalew scale all widths to this; use 0 otherwise
+ * \param[in] scaleh scale all heights to this; use 0 otherwise
+ * \param[in] linew width of normalized strokes; use 0 to skip
+ * \param[in] threshold for binarization; typically ~150
+ * \param[in] maxyshift from nominal centroid alignment; default is 1
+ * \return recog, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a convenience function for training from labeled data.
+ * The pixa can be read from file.
+ * (2) The pixa should contain the unscaled bitmaps used for training.
+ * (3) See recogCreate() for use of %scalew, %scaleh and %linew.
+ * (4) It is recommended to use %maxyshift = 1 (the default value)
+ * (5) All examples in the same class (i.e., with the same character
+ * label) should be similar. They can be made similar by invoking
+ * recogRemoveOutliers[1,2]() on %pixa before calling this function.
+ * </pre>
+ */
+L_RECOG *
+recogCreateFromPixa(PIXA *pixa,
+ l_int32 scalew,
+ l_int32 scaleh,
+ l_int32 linew,
+ l_int32 threshold,
+ l_int32 maxyshift)
+{
+L_RECOG *recog;
+
+ PROCNAME("recogCreateFromPixa");
+
+ if (!pixa)
+ return (L_RECOG *)ERROR_PTR("pixa not defined", procName, NULL);
+
+ recog = recogCreateFromPixaNoFinish(pixa, scalew, scaleh, linew,
+ threshold, maxyshift);
+ if (!recog)
+ return (L_RECOG *)ERROR_PTR("recog not made", procName, NULL);
+
+ recogTrainingFinished(&recog, 1, -1, -1.0);
+ if (!recog)
+ return (L_RECOG *)ERROR_PTR("bad templates", procName, NULL);
+ return recog;
+}
+
+
+/*!
+ * \brief recogCreateFromPixaNoFinish()
+ *
+ * \param[in] pixa of labeled, 1 bpp images
+ * \param[in] scalew scale all widths to this; use 0 otherwise
+ * \param[in] scaleh scale all heights to this; use 0 otherwise
+ * \param[in] linew width of normalized strokes; use 0 to skip
+ * \param[in] threshold for binarization; typically ~150
+ * \param[in] maxyshift from nominal centroid alignment; default is 1
+ * \return recog, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See recogCreateFromPixa() for details.
+ * (2) This is also used to generate a pixaa with templates
+ * in each class within a pixa. For that, all args except for
+ * %pixa are ignored.
+ * </pre>
+ */
+L_RECOG *
+recogCreateFromPixaNoFinish(PIXA *pixa,
+ l_int32 scalew,
+ l_int32 scaleh,
+ l_int32 linew,
+ l_int32 threshold,
+ l_int32 maxyshift)
+{
+char *text;
+l_int32 full, n, i, ntext, same, maxd;
+PIX *pix;
+L_RECOG *recog;
+
+ PROCNAME("recogCreateFromPixaNoFinish");
+
+ if (!pixa)
+ return (L_RECOG *)ERROR_PTR("pixa not defined", procName, NULL);
+ pixaVerifyDepth(pixa, &same, &maxd);
+ if (maxd > 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, linew, 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;
+ }
+ recogTrainLabeled(recog, pix, NULL, text, 0);
+ pixDestroy(&pix);
+ }
+
+ return recog;
+}
+
+
+/*!
+ * \brief recogCreate()
+ *
+ * \param[in] scalew scale all widths to this; use 0 otherwise
+ * \param[in] scaleh scale all heights to this; use 0 otherwise
+ * \param[in] linew width of normalized strokes; use 0 to skip
+ * \param[in] threshold for binarization; typically ~128; 0 for default
+ * \param[in] maxyshift from nominal centroid alignment; default is 1
+ * \return recog, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %scalew == 0 and %scaleh == 0, no scaling is done.
+ * If one of these is 0 and the other is > 0, scaling is isotropic
+ * to the requested size. We typically do not set both > 0.
+ * (2) Use linew > 0 to convert the templates to images with fixed
+ * width strokes. linew == 0 skips the conversion.
+ * (3) The only valid values for %maxyshift are 0, 1 and 2.
+ * It is recommended to use %maxyshift == 1 (default value).
+ * Using %maxyshift == 0 is much faster than %maxyshift == 1, but
+ * it is much less likely to find the template with the best
+ * correlation. Use of anything but 1 results in a warning.
+ * (4) Scaling is used for finding outliers and for training a
+ * book-adapted recognizer (BAR) from a bootstrap recognizer (BSR).
+ * Scaling the height to a fixed value and scaling the width
+ * accordingly (e.g., %scaleh = 40, %scalew = 0) is recommended.
+ * (5) The storage for most of the arrays is allocated when training
+ * is finished.
+ * </pre>
+ */
+L_RECOG *
+recogCreate(l_int32 scalew,
+ l_int32 scaleh,
+ l_int32 linew,
+ l_int32 threshold,
+ l_int32 maxyshift)
+{
+L_RECOG *recog;
+
+ PROCNAME("recogCreate");
+
+ if (scalew < 0 || scaleh < 0)
+ return (L_RECOG *)ERROR_PTR("invalid scalew or scaleh", procName, NULL);
+ if (linew > 10)
+ return (L_RECOG *)ERROR_PTR("invalid linew > 10", procName, NULL);
+ if (threshold == 0) threshold = DefaultThreshold;
+ if (threshold < 0 || threshold > 255) {
+ L_WARNING("invalid threshold; using default\n", procName);
+ threshold = DefaultThreshold;
+ }
+ if (maxyshift < 0 || maxyshift > 2) {
+ L_WARNING("invalid maxyshift; using default value\n", procName);
+ maxyshift = DefaultMaxYShift;
+ } else if (maxyshift == 0) {
+ L_WARNING("Using maxyshift = 0; faster, worse correlation results\n",
+ procName);
+ } else if (maxyshift == 2) {
+ L_WARNING("Using maxyshift = 2; slower\n", procName);
+ }
+
+ recog = (L_RECOG *)LEPT_CALLOC(1, sizeof(L_RECOG));
+ recog->templ_use = L_USE_ALL_TEMPLATES; /* default */
+ recog->threshold = threshold;
+ recog->scalew = scalew;
+ recog->scaleh = scaleh;
+ recog->linew = linew;
+ recog->maxyshift = maxyshift;
+ recogSetParams(recog, 1, -1, -1.0, -1.0);
+ recog->bmf = bmfCreate(NULL, 6);
+ recog->bmf_size = 6;
+ recog->maxarraysize = MaxExamplesInClass;
+
+ /* 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->max_splith = 60;
+
+ /* Allocate the paa for the unscaled training bitmaps */
+ recog->pixaa_u = pixaaCreate(recog->maxarraysize);
+
+ /* Generate the storage for debugging */
+ recog->pixadb_boot = pixaCreate(2);
+ recog->pixadb_split = pixaCreate(2);
+ return recog;
+}
+
+
+/*!
+ * \brief recogDestroy()
+ *
+ * \param[in,out] precog will be set to null before returning
+ * \return void
+ */
+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;
+
+ LEPT_FREE(recog->centtab);
+ LEPT_FREE(recog->sumtab);
+ 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;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Recog accessors *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogGetCount()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief recogSetParams()
+ *
+ * \param[in] recog to be padded, if necessary
+ * \param[in] type type of char set; -1 for default;
+ * see enum in recog.h
+ * \param[in] min_nopad min number in a class without padding;
+ * use -1 for default
+ * \param[in] max_wh_ratio max width/height ratio allowed for splitting;
+ * use -1.0 for default
+ * \param[in] max_ht_ratio max of max/min averaged template height ratio;
+ * use -1.0 for default
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is called when a recog is created.
+ * (2) Default %min_nopad value allows for some padding.
+ * To disable padding, set %min_nopad = 0. To pad only when
+ * no samples are available for the class, set %min_nopad = 1.
+ * (3) The %max_wh_ratio limits the width/height ratio for components
+ * that we attempt to split. Splitting long components is expensive.
+ * (4) The %max_ht_ratio is a quality requirement on the training data.
+ * The recognizer will not run if the averages are computed and
+ * the templates do not satisfy it.
+ * </pre>
+ */
+l_ok
+recogSetParams(L_RECOG *recog,
+ l_int32 type,
+ l_int32 min_nopad,
+ l_float32 max_wh_ratio,
+ l_float32 max_ht_ratio)
+{
+ PROCNAME("recogSetParams");
+
+ if (!recog)
+ return ERROR_INT("recog not defined", procName, 1);
+
+ recog->charset_type = (type >= 0) ? type : DefaultCharsetType;
+ recog->charset_size = recogGetCharsetSize(recog->charset_type);
+ recog->min_nopad = (min_nopad >= 0) ? min_nopad : DefaultMinNopad;
+ recog->max_wh_ratio = (max_wh_ratio > 0.0) ? max_wh_ratio :
+ DefaultMaxWHRatio;
+ recog->max_ht_ratio = (max_ht_ratio > 1.0) ? max_ht_ratio :
+ DefaultMaxHTRatio;
+ return 0;
+}
+
+
+/*!
+ * \brief recogGetCharsetSize()
+ *
+ * \param[in] 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 */
+}
+
+
+/*------------------------------------------------------------------------*
+ * Character/index lookup *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogGetClassIndex()
+ *
+ * \param[in] recog with LUT's pre-computed
+ * \param[in] val integer value; can be up to 3 bytes for UTF-8
+ * \param[in] text text from which %val was derived; used if not found
+ * \param[out] pindex index into dna_tochar
+ * \return 0 if found; 1 if not found and added; 2 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This is used during training. There is one entry in
+ * recog->dna_tochar (integer value, e.g., ascii) and
+ * one in recog->sa_text (e.g, ascii letter in a string)
+ * for each character class.
+ * (2) This searches the dna character array for %val. If it is
+ * not found, the template represents a character class not
+ * already seen: it increments setsize (the number of character
+ * classes) by 1, and augments both the index (dna_tochar)
+ * and text (sa_text) arrays.
+ * (3) Returns the index in &index, except on error.
+ * (4) Caller must check the function return value.
+ * </pre>
+ */
+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 = -1;
+ 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;
+}
+
+
+/*!
+ * \brief recogStringToIndex()
+ *
+ * \param[in] recog
+ * \param[in] text text string for some class
+ * \param[out] pindex index for that class; -1 if not found
+ * \return 0 if OK, 1 on error not finding the string is an error
+ */
+l_ok
+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 */
+}
+
+
+/*!
+ * \brief recogGetClassString()
+ *
+ * \param[in] recog
+ * \param[in] index into array of char types
+ * \param[out] pcharstr string representation;
+ * returns an empty string on error
+ * \return 0 if found, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Extracts a copy of the string from sa_text, which
+ * the caller must free.
+ * (2) Caller must check the function return value.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_convertCharstrToInt()
+ *
+ * \param[in] str input string representing one UTF-8 character;
+ * not more than 4 bytes
+ * \param[out] pval integer value for the input. Think of it
+ * as a 1-to-1 hash code.
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogRead()
+ *
+ * \param[in] filename
+ * \return recog, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) When a recog is serialized, a pixaa of the templates that are
+ * actually used for correlation is saved in the pixaa_u array
+ * of the recog. These can be different from the templates that
+ * were used to generate the recog, because those original templates
+ * can be scaled and turned into normalized lines. When recog1
+ * is deserialized to recog2, these templates are put in both the
+ * unscaled array (pixaa_u) and the modified array (pixaa) in recog2.
+ * Why not put it in only the unscaled array and let
+ * recogTrainingFinalized() regenerate the modified templates?
+ * The reason is that with normalized lines, the operation of
+ * thinning to a skeleton and dilating back to a fixed width
+ * is not idempotent. Thinning to a skeleton saves pixels at
+ * the end of a line segment, and thickening the skeleton puts
+ * additional pixels at the end of the lines. This tends to
+ * close gaps.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief recogReadStream()
+ *
+ * \param[in] fp file stream
+ * \return recog, or NULL on error
+ */
+L_RECOG *
+recogReadStream(FILE *fp)
+{
+l_int32 version, setsize, threshold, scalew, scaleh, linew;
+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, "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 (fscanf(fp, "Normalized line width = %d\n", &linew) != 1)
+ return (L_RECOG *)ERROR_PTR("line width not read", procName, NULL);
+ if ((recog = recogCreate(scalew, scaleh, linew, threshold,
+ maxyshift)) == NULL)
+ return (L_RECOG *)ERROR_PTR("recog not made", procName, NULL);
+
+ if (fscanf(fp, "\nLabels for character set:\n") != 0) {
+ recogDestroy(&recog);
+ return (L_RECOG *)ERROR_PTR("label intro not read", procName, NULL);
+ }
+ l_dnaDestroy(&recog->dna_tochar);
+ if ((dna_tochar = l_dnaReadStream(fp)) == NULL) {
+ recogDestroy(&recog);
+ return (L_RECOG *)ERROR_PTR("dna_tochar not read", procName, NULL);
+ }
+ recog->dna_tochar = dna_tochar;
+ sarrayDestroy(&recog->sa_text);
+ if ((sa_text = sarrayReadStream(fp)) == NULL) {
+ recogDestroy(&recog);
+ return (L_RECOG *)ERROR_PTR("sa_text not read", procName, NULL);
+ }
+ recog->sa_text = sa_text;
+
+ if (fscanf(fp, "\nPixaa of all samples in the training set:\n") != 0) {
+ recogDestroy(&recog);
+ return (L_RECOG *)ERROR_PTR("pixaa intro not read", procName, NULL);
+ }
+ if ((paa = pixaaReadStream(fp)) == NULL) {
+ recogDestroy(&recog);
+ return (L_RECOG *)ERROR_PTR("pixaa not read", procName, NULL);
+ }
+ recog->setsize = setsize;
+ nc = pixaaGetCount(paa, NULL);
+ if (nc != setsize) {
+ recogDestroy(&recog);
+ pixaaDestroy(&paa);
+ L_ERROR("(setsize = %d) != (paa count = %d)\n", procName,
+ setsize, nc);
+ return NULL;
+ }
+
+ recogAddAllSamples(&recog, paa, 0); /* this finishes */
+ pixaaDestroy(&paa);
+ if (!recog)
+ return (L_RECOG *)ERROR_PTR("bad templates", procName, NULL);
+ return recog;
+}
+
+
+/*!
+ * \brief recogReadMem()
+ *
+ * \param[in] data serialization of recog (not ascii)
+ * \param[in] size of data in bytes
+ * \return recog, or NULL on error
+ */
+L_RECOG *
+recogReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+L_RECOG *recog;
+
+ PROCNAME("recogReadMem");
+
+ if (!data)
+ return (L_RECOG *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (L_RECOG *)ERROR_PTR("stream not opened", procName, NULL);
+
+ recog = recogReadStream(fp);
+ fclose(fp);
+ if (!recog) L_ERROR("recog not read\n", procName);
+ return recog;
+}
+
+
+/*!
+ * \brief recogWrite()
+ *
+ * \param[in] filename
+ * \param[in] recog
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The pixaa of templates that is written is the modified one
+ * in the pixaa field. It is the pixaa that is actually used
+ * for correlation. This is not the unscaled array of labeled
+ * bitmaps, in pixaa_u, that was used to generate the recog in the
+ * first place. See the notes in recogRead() for the rationale.
+ * </pre>
+ */
+l_ok
+recogWrite(const char *filename,
+ L_RECOG *recog)
+{
+l_int32 ret;
+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);
+ ret = recogWriteStream(fp, recog);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("recog not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief recogWriteStream()
+ *
+ * \param[in] fp file stream opened for "wb"
+ * \param[in] recog
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+recogWriteStream(FILE *fp,
+ L_RECOG *recog)
+{
+ PROCNAME("recogWriteStream");
+
+ if (!fp)
+ return ERROR_INT("stream not defined", procName, 1);
+ if (!recog)
+ return ERROR_INT("recog 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, "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, "Normalized line width = %d\n", recog->linew);
+ 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;
+}
+
+
+/*!
+ * \brief recogWriteMem()
+ *
+ * \param[out] pdata data of serialized recog (not ascii)
+ * \param[out] psize size of returned data
+ * \param[in] recog
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a recog in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+recogWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ L_RECOG *recog)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("recogWriteMem");
+
+ 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 (!recog)
+ return ERROR_INT("recog not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = recogWriteStream(fp, recog);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = recogWriteStream(fp, recog);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*!
+ * \brief recogExtractPixa()
+ *
+ * \param[in] recog
+ * \return pixa if OK, NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates a pixa of all the unscaled images in the
+ * recognizer, where each one has its character class label in
+ * the pix text field, by flattening pixaa_u to a pixa.
+ * </pre>
+ */
+PIXA *
+recogExtractPixa(L_RECOG *recog)
+{
+ PROCNAME("recogExtractPixa");
+
+ if (!recog)
+ return (PIXA *)ERROR_PTR("recog not defined", procName, NULL);
+
+ recogAddCharstrLabels(recog);
+ return pixaaFlattenToPixa(recog->pixaa_u, NULL, L_CLONE);
+}
+
+
+/*!
+ * \brief recogAddCharstrLabels()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief recogAddAllSamples()
+ *
+ * \param[in] precog addr of recog
+ * \param[in] paa pixaa from previously trained recog
+ * \param[in] debug
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) On error, the input recog is destroyed.
+ * (2) This is used with the serialization routine recogRead(),
+ * where each pixa in the pixaa represents a set of characters
+ * in a different class. Before calling this function, we have
+ * verified 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.
+ * </pre>
+ */
+static l_int32
+recogAddAllSamples(L_RECOG **precog,
+ PIXAA *paa,
+ l_int32 debug)
+{
+char *text;
+l_int32 i, j, nc, ns;
+PIX *pix;
+PIXA *pixa, *pixa1;
+L_RECOG *recog;
+
+ PROCNAME("recogAddAllSamples");
+
+ if (!precog)
+ return ERROR_INT("&recog not defined", procName, 1);
+ if ((recog = *precog) == NULL)
+ return ERROR_INT("recog not defined", procName, 1);
+ if (!paa) {
+ recogDestroy(&recog);
+ 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);
+ pixa1 = pixaCreate(ns);
+ pixaaAddPixa(recog->pixaa_u, pixa1, L_INSERT);
+ for (j = 0; j < ns; j++) {
+ pix = pixaGetPix(pixa, j, L_CLONE);
+ if (debug) lept_stderr("pix[%d,%d]: text = %s\n", i, j, text);
+ pixaaAddPix(recog->pixaa_u, i, pix, NULL, L_INSERT);
+ }
+ pixaDestroy(&pixa);
+ }
+
+ recogTrainingFinished(&recog, 0, -1, -1.0); /* For second parameter,
+ see comment in recogRead() */
+ if (!recog)
+ return ERROR_INT("bad templates; recog destroyed", procName, 1);
+ return 0;
+}
diff --git a/leptonica/src/recogdid.c b/leptonica/src/recogdid.c
new file mode 100644
index 00000000..5652c4ac
--- /dev/null
+++ b/leptonica/src/recogdid.c
@@ -0,0 +1,1078 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file recogdid.c
+ * <pre>
+ *
+ * Top-level identification
+ * BOXA *recogDecode()
+ *
+ * Generate decoding arrays
+ * static l_int32 recogPrepareForDecoding()
+ * static l_int32 recogMakeDecodingArray()
+ *
+ * Dynamic programming for best path
+ * static l_int32 recogRunViterbi()
+ * static 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 labeled 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+static l_int32 recogPrepareForDecoding(L_RECOG *recog, PIX *pixs,
+ l_int32 debug);
+static l_int32 recogMakeDecodingArray(L_RECOG *recog, l_int32 index,
+ l_int32 debug);
+static l_int32 recogRunViterbi(L_RECOG *recog, PIX **ppixdb);
+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.95f, 0.9f};
+static const l_float32 DefaultAlpha4[] = {0.95f, 0.9f, 0.75f, 0.25f};
+
+
+/*------------------------------------------------------------------------*
+ * Top-level identification *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogDecode()
+ *
+ * \param[in] recog with LUT's pre-computed
+ * \param[in] pixs typically of multiple touching characters, 1 bpp
+ * \param[in] nlevels of templates; 2 for now
+ * \param[out] ppixdb [optional] debug result; can be null
+ * \return boxa segmentation of pixs into characters, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The input pixs has been filtered so that it is likely to be
+ * composed of more than one touching character. Specifically,
+ * its height can only slightly exceed that of the tallest
+ * unscaled template, the width is somewhat larger than the
+ * width of the widest unscaled template, and the w/h aspect ratio
+ * is bounded by max_wh_ratio.
+ * (2) This uses the DID mechanism with labeled templates to
+ * segment the input %pixs. The resulting segmentation is
+ * returned. (It is given by did->boxa).
+ * (3) In debug mode, the Viterbi path is rescored based on all
+ * the templates. In non-debug mode, the same procedure is
+ * carried out by recogIdentifyPix() on the result of the
+ * segmentation.
+ * </pre>
+ */
+BOXA *
+recogDecode(L_RECOG *recog,
+ PIX *pixs,
+ l_int32 nlevels,
+ PIX **ppixdb)
+{
+l_int32 debug;
+PIX *pix1;
+PIXA *pixa;
+
+ PROCNAME("recogDecode");
+
+ if (ppixdb) *ppixdb = NULL;
+ if (!recog)
+ return (BOXA *)ERROR_PTR("recog not defined", procName, NULL);
+ if (!pixs || pixGetDepth(pixs) != 1)
+ return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+ if (!recog->train_done)
+ return (BOXA *)ERROR_PTR("training not finished", procName, NULL);
+ if (nlevels != 2)
+ return (BOXA *)ERROR_PTR("nlevels != 2 (for now)", procName, NULL);
+
+ debug = (ppixdb) ? 1 : 0;
+ if (recogPrepareForDecoding(recog, pixs, debug))
+ return (BOXA *)ERROR_PTR("error making arrays", procName, NULL);
+ recogSetChannelParams(recog, nlevels);
+
+ /* Normal path; just run Viterbi */
+ if (!debug) {
+ if (recogRunViterbi(recog, NULL) == 0)
+ return boxaCopy(recog->did->boxa, L_COPY);
+ else
+ return (BOXA *)ERROR_PTR("error in Viterbi", procName, NULL);
+ }
+
+ /* Debug path */
+ if (recogRunViterbi(recog, &pix1))
+ return (BOXA *)ERROR_PTR("error in viterbi", procName, NULL);
+ pixa = pixaCreate(2);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ if (recogRescoreDidResult(recog, &pix1)) {
+ pixaDestroy(&pixa);
+ return (BOXA *)ERROR_PTR("error in rescoring", procName, NULL);
+ }
+ pixaAddPix(pixa, pix1, L_INSERT);
+ *ppixdb = pixaDisplayTiledInRows(pixa, 32, 2 * pixGetWidth(pix1) + 100,
+ 1.0, 0, 30, 2);
+ pixaDestroy(&pixa);
+ return boxaCopy(recog->did->boxa, L_COPY);
+}
+
+
+/*------------------------------------------------------------------------*
+ * Generate decoding arrays *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogPrepareForDecoding()
+ *
+ * \param[in] recog with LUT's pre-computed
+ * \param[in] pixs typically of multiple touching characters, 1 bpp
+ * \param[in] debug 1 for debug output; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Binarizes and crops input %pixs.
+ * (2) Removes previous L_RDID struct and makes a new one.
+ * (3) Generates the bit-and sum arrays for each character template
+ * at each pixel position in %pixs. These are used in the
+ * Viterbi dynamic programming step.
+ * (4) 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.
+ * </pre>
+ */
+static l_int32
+recogPrepareForDecoding(L_RECOG *recog,
+ PIX *pixs,
+ l_int32 debug)
+{
+l_int32 i;
+PIX *pix1;
+L_RDID *did;
+
+ PROCNAME("recogPrepareForDecoding");
+
+ 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 (!recog->ave_done)
+ recogAverageSamples(&recog, 0);
+
+ /* 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;
+}
+
+
+/*!
+ * \brief recogMakeDecodingArray()
+ *
+ * \param[in] recog
+ * \param[in] index of averaged template
+ * \param[in] debug 1 for debug output; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+ maxdely = 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
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogRunViterbi()
+ *
+ * \param[in] recog with LUT's pre-computed
+ * \param[out] ppixdb [optional] debug result; can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This can be used when the templates are unscaled. It works by
+ * matching the average, unscaled templates of each class to
+ * all positions.
+ * (2) It 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.
+ * (3) The primary output is a boxa of the locations for splitting
+ * the input image. These locations are used later to split the
+ * image and send the pieces individually for recognition.
+ * This can be done in either recogIdentifyMultiple(), or
+ * for debugging in recogRescoreDidResult().
+ * </pre>
+ */
+static l_int32
+recogRunViterbi(L_RECOG *recog,
+ PIX **ppixdb)
+{
+l_int32 i, w1, w2, h1, xnz, x, narray, minsetw;
+l_int32 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;
+BOX *box;
+PIX *pix1;
+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);
+
+ /* Compute the minimum setwidth. Bad templates with very small
+ * width can cause havoc because the setwidth is too small. */
+ 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 <= 2)
+ return ERROR_INT("minsetw <= 2; bad templates", 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. */
+ didscore = did->trellisscore;
+ didtempl = did->trellistempl;
+ area2 = numaGetIArray(recog->nasum_u);
+ besttempl = 0; /* just tells compiler it is initialized */
+ maxscore = 0.0; /* ditto */
+ 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;
+ }
+ h1 = pixGetHeight(did->pixs);
+ 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 = ((l_float32)(counts) * counts) /
+ (l_float32)(area2[templ] * area1);
+ pix1 = pixaGetPix(recog->pixa_u, templ, L_CLONE);
+ w2 = pixGetWidth(pix1);
+ numaAddNumber(did->natempl, templ);
+ numaAddNumber(did->naxloc, xloc);
+ numaAddNumber(did->nadely, dely);
+ numaAddNumber(did->nawidth, pixGetWidth(pix1));
+ numaAddNumber(did->nascore, correl);
+ xnz = L_MAX(xloc, 0);
+ box = boxCreate(xnz, dely, w2, h1);
+ boxaAddBox(did->boxa, box, L_INSERT);
+ pixDestroy(&pix1);
+ x = xloc;
+ }
+
+ if (ppixdb) {
+ numaWriteStderr(did->natempl);
+ numaWriteStderr(did->naxloc);
+ numaWriteStderr(did->nadely);
+ numaWriteStderr(did->nawidth);
+ numaWriteStderr(did->nascore);
+ boxaWriteStderr(did->boxa);
+ *ppixdb = recogShowPath(recog, 0);
+ }
+
+ LEPT_FREE(area2);
+ return 0;
+}
+
+
+/*!
+ * \brief recogRescoreDidResult()
+ *
+ * \param[in] recog with LUT's pre-computed
+ * \param[out] ppixdb [optional] debug result; can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This does correlation matching with all unscaled templates,
+ * using the character segmentation determined by the Viterbi path.
+ * </pre>
+ */
+static l_int32
+recogRescoreDidResult(L_RECOG *recog,
+ PIX **ppixdb)
+{
+l_int32 i, n, sample, x, 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;
+ for (i = 0; i < n; i++) {
+ box1 = boxaGetBox(did->boxa, i, L_COPY);
+ boxGetGeometry(box1, &x, &dely, NULL, NULL);
+ pix1 = pixClipRectangle(pixs, box1, NULL);
+ recogIdentifyPix(recog, pix1, NULL);
+ recogTransferRchToDid(recog, x, dely);
+ if (ppixdb) {
+ rchExtract(recog->rch, &index, &score, &text,
+ &sample, NULL, NULL, NULL);
+ lept_stderr("text = %s, index = %d, sample = %d,"
+ " score = %5.3f\n", text, index, sample, score);
+ }
+ pixDestroy(&pix1);
+ boxDestroy(&box1);
+ LEPT_FREE(text);
+ }
+
+ if (ppixdb)
+ *ppixdb = recogShowPath(recog, 1);
+
+ return 0;
+}
+
+
+/*!
+ * \brief recogShowPath()
+ *
+ * \param[in] recog with LUT's pre-computed
+ * \param[in] 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, j, n, index, xloc, dely;
+l_float32 score;
+L_BMF *bmf;
+NUMA *natempl_s, *nasample_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;
+ nasample_s = did->nasample_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);
+ if (select == 0) {
+ pix2 = pixaGetPix(recog->pixa_u, index, L_CLONE);
+ } else {
+ numaGetIValue(nasample_s, i, &j);
+ pix2 = pixaaGetPix(recog->pixaa_u, index, j, 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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogCreateDid()
+ *
+ * \param[in] recog
+ * \param[in] pixs of 1 bpp image to match
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+recogCreateDid(L_RECOG *recog,
+ PIX *pixs)
+{
+l_int32 i;
+PIX *pix1;
+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->boxa = boxaCreate(5);
+ did->nascore = numaCreate(5);
+ did->natempl_r = numaCreate(5);
+ did->nasample_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++) {
+ pix1 = pixaGetPix(recog->pixa_u, i, L_CLONE);
+ did->setwidth[i] = (l_int32)(SetwidthFraction * pixGetWidth(pix1));
+ pixDestroy(&pix1);
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief recogDestroyDid()
+ *
+ * \param[in] recog
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) As the signature indicates, this is owned by the recog, and can
+ * only be destroyed using this function.
+ * </pre>
+ */
+l_ok
+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);
+ boxaDestroy(&did->boxa);
+ numaDestroy(&did->nascore);
+ numaDestroy(&did->natempl_r);
+ numaDestroy(&did->nasample_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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogDidExists()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief recogGetDid()
+ *
+ * \param[in] recog
+ * \return did still owned by the recog, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This also makes sure the arrays are defined.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief recogGetWindowedArea()
+ *
+ * \param[in] recog
+ * \param[in] index of template
+ * \param[in] x pixel position of left hand edge of template
+ * \param[out] pdely y shift of template relative to pix1
+ * \param[out] pwsum number of fg pixels in window of pixs
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief recogSetChannelParams()
+ *
+ * \param[in] recog
+ * \param[in] nlevels
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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].
+ * </pre>
+ */
+l_ok
+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])));
+/* lept_stderr("beta[%d] = %7.3f, gamma[%d] = %7.3f\n",
+ i, did->beta[i], i, did->gamma[i]); */
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief recogTransferRchToDid()
+ *
+ * \param[in] recog with rch and did defined
+ * \param[in] x left edge of extracted region, relative to decoded line
+ * \param[in] y top edge of extracted region, relative to input image
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is used to transfer the results for a single character match
+ * to the rescored did arrays.
+ * </pre>
+ */
+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->nasample_r, rch->sample);
+ 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/leptonica/src/recogident.c b/leptonica/src/recogident.c
new file mode 100644
index 00000000..9c607706
--- /dev/null
+++ b/leptonica/src/recogident.c
@@ -0,0 +1,1883 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file recogident.c
+ * <pre>
+ *
+ * Top-level identification
+ * l_int32 recogIdentifyMultiple()
+ *
+ * Segmentation and noise removal
+ * l_int32 recogSplitIntoCharacters()
+ *
+ * Greedy character splitting
+ * l_int32 recogCorrelationBestRow()
+ * l_int32 recogCorrelationBestChar()
+ * static l_int32 pixCorrelationBestShift()
+ *
+ * Low-level identification of single characters
+ * l_int32 recogIdentifyPixa()
+ * l_int32 recogIdentifyPix()
+ * l_int32 recogSkipIdentify()
+ *
+ * Operations for handling identification results
+ * static L_RCHA *rchaCreate()
+ * void rchaDestroy()
+ * static L_RCH *rchCreate()
+ * void rchDestroy()
+ * l_int32 rchaExtract()
+ * l_int32 rchExtract()
+ * static l_int32 transferRchToRcha()
+ *
+ * Preprocessing and filtering
+ * l_int32 recogProcessToIdentify()
+ * static PIX *recogPreSplittingFilter()
+ * static PIX *recogSplittingFilter()
+ *
+ * Postprocessing
+ * SARRAY *recogExtractNumbers()
+ * PIX *showExtractNumbers()
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* There are two methods for splitting characters: DID and greedy.
+ * The default method is DID. */
+#define SPLIT_WITH_DID 1
+
+ /* 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 MinFillFactor = 0.10;
+static const l_int32 DefaultMinHeight = 15; /* min unscaled height */
+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 PIX *recogPreSplittingFilter(L_RECOG *recog, PIX *pixs, l_int32 minh,
+ l_float32 minaf, l_int32 debug);
+static l_int32 recogSplittingFilter(L_RECOG *recog, PIX *pixs, l_int32 min,
+ l_float32 minaf, l_int32 *premove,
+ l_int32 debug);
+static void l_showIndicatorSplitValues(NUMA *na1, NUMA *na2, NUMA *na3,
+ NUMA *na4, NUMA *na5, NUMA *na6);
+
+/*------------------------------------------------------------------------*
+ * Identification
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogIdentifyMultiple()
+ *
+ * \param[in] recog with training finished
+ * \param[in] pixs containing typically a small number of characters
+ * \param[in] minh remove shorter components; use 0 for default
+ * \param[in] skipsplit 1 to skip the splitting step
+ * \param[out] pboxa [optional] locations of identified components
+ * \param[out] ppixa [optional] images of identified components
+ * \param[out] ppixdb [optional] debug pix: inputs and best fits
+ * \param[in] debugsplit 1 returns pix split debugging images
+ * \return 0 if OK; 1 if nothing is found; 2 for other errors.
+ *
+ * <pre>
+ * Notes:
+ * (1) This filters the input pixa and calls recogIdentifyPixa()
+ * (2) Splitting is relatively slow, because it tries to match all
+ * character templates to all locations. This step can be skipped.
+ * (3) 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.
+ * </pre>
+ */
+l_ok
+recogIdentifyMultiple(L_RECOG *recog,
+ PIX *pixs,
+ l_int32 minh,
+ l_int32 skipsplit,
+ BOXA **pboxa,
+ PIXA **ppixa,
+ PIX **ppixdb,
+ l_int32 debugsplit)
+{
+l_int32 n;
+BOXA *boxa;
+PIX *pixb;
+PIXA *pixa;
+
+ PROCNAME("recogIdentifyMultiple");
+
+ if (pboxa) *pboxa = NULL;
+ if (ppixa) *ppixa = NULL;
+ if (ppixdb) *ppixdb = NULL;
+ if (!recog)
+ return ERROR_INT("recog not defined", procName, 2);
+ if (!recog->train_done)
+ return ERROR_INT("training not finished", procName, 2);
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 2);
+
+ /* Binarize if necessary */
+ if (pixGetDepth(pixs) > 1)
+ pixb = pixConvertTo1(pixs, recog->threshold);
+ else
+ pixb = pixClone(pixs);
+
+ /* Noise removal and splitting of touching characters */
+ recogSplitIntoCharacters(recog, pixb, minh, skipsplit, &boxa, &pixa,
+ debugsplit);
+ pixDestroy(&pixb);
+ if (!pixa || (n = pixaGetCount(pixa)) == 0) {
+ pixaDestroy(&pixa);
+ boxaDestroy(&boxa);
+ L_WARNING("nothing found\n", procName);
+ return 1;
+ }
+
+ recogIdentifyPixa(recog, pixa, ppixdb);
+ if (pboxa)
+ *pboxa = boxa;
+ else
+ boxaDestroy(&boxa);
+ if (ppixa)
+ *ppixa = pixa;
+ else
+ pixaDestroy(&pixa);
+ return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Segmentation and noise removal *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogSplitIntoCharacters()
+ *
+ * \param[in] recog
+ * \param[in] pixs 1 bpp, contains only mostly deskewed text
+ * \param[in] minh remove shorter components; use 0 for default
+ * \param[in] skipsplit 1 to skip the splitting step
+ * \param[out] pboxa character bounding boxes
+ * \param[out] ppixa character images
+ * \param[in] debug 1 for results written to pixadb_split
+ * \return 0 if OK, 1 on error or if no components are returned
+ *
+ * <pre>
+ * Notes:
+ * (1) This can be given an image that has an arbitrary number
+ * of text characters. It optionally splits connected
+ * components based on document image decoding in recogDecode().
+ * The returned pixa includes the boxes from which the
+ * (possibly split) components are extracted.
+ * (2) After noise filtering, 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.
+ * (3) Note that the splitting function uses unscaled templates
+ * and does not bother returning the class results and scores.
+ * These are more accurately found later using the scaled templates.
+ * </pre>
+ */
+l_ok
+recogSplitIntoCharacters(L_RECOG *recog,
+ PIX *pixs,
+ l_int32 minh,
+ l_int32 skipsplit,
+ BOXA **pboxa,
+ PIXA **ppixa,
+ l_int32 debug)
+{
+static l_int32 ind = 0;
+char buf[32];
+l_int32 i, xoff, yoff, empty, maxw, bw, ncomp, scaling;
+BOX *box;
+BOXA *boxa1, *boxa2, *boxa3, *boxa4, *boxad;
+BOXAA *baa;
+PIX *pix, *pix1, *pix2, *pix3;
+PIXA *pixa;
+
+ PROCNAME("recogSplitIntoCharacters");
+
+ lept_mkdir("lept/recog");
+
+ if (pboxa) *pboxa = NULL;
+ if (ppixa) *ppixa = NULL;
+ if (!pboxa || !ppixa)
+ return ERROR_INT("&boxa and &pixa 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);
+ if (minh <= 0) minh = DefaultMinHeight;
+ 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, minh, MinFillFactor, debug);
+ pixDestroy(&pix1);
+
+ /* Get the 8-connected components to be split/identified */
+ boxa1 = pixConnComp(pix2, NULL, 8);
+ pixDestroy(&pix2);
+ ncomp = boxaGetCount(boxa1);
+ if (ncomp == 0) {
+ boxaDestroy(&boxa1);
+ L_WARNING("all components removed\n", procName);
+ return 1;
+ }
+
+ /* Save everything and split the large components */
+ boxa2 = boxaCreate(ncomp);
+ maxw = recog->maxwidth_u + 5;
+ scaling = (recog->scalew > 0 || recog->scaleh > 0) ? TRUE : FALSE;
+ pixa = (debug) ? pixaCreate(ncomp) : NULL;
+ for (i = 0; i < ncomp; i++) {
+ box = boxaGetBox(boxa1, i, L_CLONE);
+ boxGetGeometry(box, &xoff, &yoff, &bw, NULL);
+ /* Treat as one character if it is small, if the images
+ * have been scaled, or if splitting is not to be run. */
+ if (bw <= maxw || scaling || skipsplit) {
+ boxaAddBox(boxa2, box, L_INSERT);
+ } else {
+ pix = pixClipRectangle(pixs, box, NULL);
+#if SPLIT_WITH_DID
+ if (!debug) {
+ boxa3 = recogDecode(recog, pix, 2, NULL);
+ } else {
+ boxa3 = recogDecode(recog, pix, 2, &pix2);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ }
+#else /* use greedy splitting */
+ recogCorrelationBestRow(recog, pix, &boxa3, NULL, NULL,
+ NULL, debug);
+ if (debug) {
+ pix2 = pixConvertTo32(pix);
+ pixRenderBoxaArb(pix2, boxa3, 2, 255, 0, 0);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ }
+#endif /* SPLIT_WITH_DID */
+ pixDestroy(&pix);
+ boxDestroy(&box);
+ if (!boxa3) {
+ L_ERROR("boxa3 not found for component %d\n", procName, i);
+ } else {
+ boxa4 = boxaTransform(boxa3, xoff, yoff, 1.0, 1.0);
+ boxaJoin(boxa2, boxa4, 0, -1);
+ boxaDestroy(&boxa3);
+ boxaDestroy(&boxa4);
+ }
+ }
+ }
+ boxaDestroy(&boxa1);
+ if (pixa) { /* debug */
+ pix3 = pixaDisplayTiledInColumns(pixa, 1, 1.0, 20, 2);
+ snprintf(buf, sizeof(buf), "/tmp/lept/recog/decode-%d.png", ind++);
+ pixWrite(buf, pix3, IFF_PNG);
+ pixaDestroy(&pixa);
+ pixDestroy(&pix3);
+ }
+
+ /* 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);
+ boxa3 = 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(boxa3, L_COMBINE, 10, 0.5, 0.3, NULL);
+ boxaDestroy(&boxa3);
+
+ /* Extract and save the image pieces from the input image. */
+ *ppixa = pixClipRectangles(pixs, boxad);
+ *pboxa = boxad;
+ return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Greedy character splitting *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogCorrelationBestRow()
+ *
+ * \param[in] recog with LUT's pre-computed
+ * \param[in] pixs typically of multiple touching characters, 1 bpp
+ * \param[out] pboxa bounding boxs of best fit character
+ * \param[out] pnascore [optional] correlation scores
+ * \param[out] pnaindex [optional] indices of classes
+ * \param[out] psachar [optional] array of character strings
+ * \param[in] debug 1 for results written to pixadb_split
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, 0, MinFillFactor, &remove, debug);
+ if (debug)
+ lept_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)
+ lept_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;
+}
+
+
+/*!
+ * \brief recogCorrelationBestChar()
+ *
+ * \param[in] recog with LUT's pre-computed
+ * \param[in] pixs can be of multiple touching characters, 1 bpp
+ * \param[out] pbox bounding box of best fit character
+ * \param[out] pscore correlation score
+ * \param[out] pindex [optional] index of class
+ * \param[out] pcharstr [optional] character string of class
+ * \param[out] ppixdb [optional] debug pix showing input and best fit
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+ bestindex = bestdelx = bestdely = 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) {
+ lept_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)
+ lept_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;
+}
+
+
+/*!
+ * \brief pixCorrelationBestShift()
+ *
+ * \param[in] pix1 1 bpp, the unknown image; typically larger
+ * \param[in] pix2 1 bpp, the matching template image)
+ * \param[in] nasum1 vertical column pixel sums for pix1
+ * \param[in] namoment1 vertical column first moment of pixels for pix1
+ * \param[in] area2 number of on pixels in pix2
+ * \param[in] ycent2 y component of centroid of pix2
+ * \param[in] maxyshift max y shift of pix2 around the location where
+ * the centroids of pix2 and a windowed part of pix1
+ * are vertically aligned
+ * \param[in] tab8 [optional] sum tab for ON pixels in byte;
+ * can be NULL
+ * \param[out] pdelx [optional] best x shift of pix2 relative to pix1
+ * \param[out] pdely [optional] best y shift of pix2 relative to pix1
+ * \param[out] pscore [optional] maximum score found; can be NULL
+ * \param[in] debugflag <= 0 to skip; positive to generate output;
+ * the integer is used to label the debug image.
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ char buf[128];
+ lept_mkdir("lept/recog");
+ 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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogIdentifyPixa()
+ *
+ * \param[in] recog
+ * \param[in] pixa of 1 bpp images to match
+ * \param[out] ppixdb [optional] pix showing inputs and best fits
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This should be called by recogIdentifyMuliple(), which
+ * binarizes and splits characters before sending %pixa here.
+ * (2) This calls recogIdentifyPix(), which does the same operation
+ * on each pix in %pixa, and optionally returns the arrays
+ * of results (scores, class index and character string)
+ * for the best correlation match.
+ * </pre>
+ */
+l_ok
+recogIdentifyPixa(L_RECOG *recog,
+ PIXA *pixa,
+ PIX **ppixdb)
+{
+char *text;
+l_int32 i, n, fail, index, depth;
+l_float32 score;
+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);
+
+ /* Run the recognizer on the set of images. This writes
+ * the text string into each pix in pixa. */
+ n = pixaGetCount(pixa);
+ 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;
+ 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) {
+ 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);
+ }
+
+ /* Package the images for debug */
+ if (ppixdb) {
+ *ppixdb = pixaDisplayTiledInRows(pixa1, depth, 2500, 1.0, 0, 20, 1);
+ pixaDestroy(&pixa1);
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief recogIdentifyPix()
+ *
+ * \param[in] recog with LUT's pre-computed
+ * \param[in] pixs of a single character, 1 bpp
+ * \param[out] ppixdb [optional] debug pix showing input and best fit
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Basic recognition function for a single character.
+ * (2) If templ_use == L_USE_ALL_TEMPLATES, which is the default
+ * situation, matching is attempted to every bitmap in the recog,
+ * and the identify of the best match is returned.
+ * (3) For finding outliers, templ_use == L_USE_AVERAGE_TEMPLATES, and
+ * matching is only attemplted to the averaged bitmaps. For this
+ * case, the index of the bestsample is meaningless (0 is returned
+ * if requested).
+ * (4) 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.
+ * </pre>
+ */
+l_ok
+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 required and not yet done. */
+ if (recog->templ_use == L_USE_AVERAGE_TEMPLATES && !recog->ave_done) {
+ recogAverageSamples(&recog, 0);
+ if (!recog)
+ return ERROR_INT("averaging failed", procName, 1);
+ }
+
+ /* Binarize and crop to foreground if necessary */
+ if ((pix0 = recogProcessToIdentify(recog, pixs, 0)) == NULL)
+ return ERROR_INT("no fg pixels in pix0", procName, 1);
+
+ /* Optionally scale and/or convert to fixed stroke width */
+ pix1 = recogModifyTemplate(recog, pix0);
+ pixDestroy(&pix0);
+ if (!pix1)
+ return ERROR_INT("no fg pixels in pix1", procName, 1);
+
+ /* Do correlation at all positions within +-maxyshift of
+ * the nominal centroid alignment. */
+ 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_use == L_USE_AVERAGE_TEMPLATES) {
+ 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_use == L_USE_AVERAGE_TEMPLATES) {
+ L_INFO("Best match: str %s; class %d; sh (%d, %d); score %5.3f\n",
+ procName, text, bestindex, bestdelx, bestdely, maxscore);
+ pix2 = pixaGetPix(recog->pixa, bestindex, L_CLONE);
+ } else { /* L_USE_ALL_TEMPLATES */
+ L_INFO("Best match: str %s; sample %d in class %d; score %5.3f\n",
+ procName, text, bestsample, bestindex, maxscore);
+ if (maxyshift > 0 && (L_ABS(bestdelx) > 0 || L_ABS(bestdely) > 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(&pix1);
+ return 0;
+}
+
+
+/*!
+ * \brief recogSkipIdentify()
+ *
+ * \param[in] recog
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This just writes a "dummy" result with 0 score and empty
+ * string id into the rch.
+ * </pre>
+ */
+l_ok
+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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief 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;
+}
+
+
+/*!
+ * \brief rchaDestroy()
+ *
+ * \param[in,out] prcha to be nulled
+ */
+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;
+}
+
+
+/*!
+ * \brief rchCreate()
+ *
+ * \param[in] index index of best template
+ * \param[in] score correlation score of best template
+ * \param[in] text character string of best template
+ * \param[in] sample index of best sample; -1 if averages are used
+ * \param[in] xloc x-location of template: delx + shiftx
+ * \param[in] yloc y-location of template: dely + shifty
+ * \param[in] width width of best template
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief rchDestroy()
+ *
+ * \param[in,out] prch to be nulled
+ */
+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;
+}
+
+
+/*!
+ * \brief rchaExtract()
+ *
+ * \param[in] rcha
+ * \param[out] pnaindex [optional] indices of best templates
+ * \param[out] pnascore [optional] correl scores of best templates
+ * \param[out] psatext [optional] character strings of best templates
+ * \param[out] pnasample [optional] indices of best samples
+ * \param[out] pnaxloc [optional] x-locations of templates
+ * \param[out] pnayloc [optional] y-locations of templates
+ * \param[out] pnawidth [optional] widths of best templates
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This returns clones of the number and string arrays. They must
+ * be destroyed by the caller.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief rchExtract()
+ *
+ * \param[in] rch
+ * \param[out] pindex [optional] index of best template
+ * \param[out] pscore [optional] correlation score of best template
+ * \param[out] ptext [optional] character string of best template
+ * \param[out] psample [optional] index of best sample
+ * \param[out] pxloc [optional] x-location of template
+ * \param[out] pyloc [optional] y-location of template
+ * \param[out] pwidth [optional] width of best template
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief transferRchToRcha()
+ *
+ * \param[in] rch source of data
+ * \param[in] rcha append to arrays in this destination
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is used to transfer the results of a single character
+ * identification to an rcha array for the array of characters.
+ * </pre>
+ */
+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;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Preprocessing and filtering *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogProcessToIdentify()
+ *
+ * \param[in] recog with LUT's pre-computed
+ * \param[in] pixs typ. single character, possibly d > 1 and uncropped
+ * \param[in] 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.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief recogPreSplittingFilter()
+ *
+ * \param[in] recog
+ * \param[in] pixs 1 bpp, many connected components
+ * \param[in] minh minimum height of components to be retained
+ * \param[in] minaf minimum area fraction (|fg|/(w*h)) to be retained
+ * \param[in] debug 1 to output indicator arrays
+ * \return pixd with filtered components removed or NULL on error
+ */
+static PIX *
+recogPreSplittingFilter(L_RECOG *recog,
+ PIX *pixs,
+ l_int32 minh,
+ l_float32 minaf,
+ l_int32 debug)
+{
+l_int32 scaling, minsplitw, maxsplith, maxasp;
+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 and max_splith. */
+ scaling = (recog->scalew > 0 || recog->scaleh > 0) ? TRUE : FALSE;
+ minsplitw = (scaling) ? 1 : recog->min_splitw - 3;
+ maxsplith = (scaling) ? 150 : recog->max_splith;
+ maxasp = recog->max_wh_ratio;
+
+ /* Generate an indicator array of connected components to remove:
+ * short 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, minh, 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;
+}
+
+
+/*!
+ * \brief recogSplittingFilter()
+ *
+ * \param[in] recog
+ * \param[in] pixs 1 bpp, single connected component
+ * \param[in] minh minimum height of component; 0 for default
+ * \param[in] minaf minimum area fraction (|fg|/(w*h)) to be retained
+ * \param[out] premove 0 to save, 1 to remove
+ * \param[in] debug 1 to output indicator arrays
+ * \return 0 if OK, 1 on error
+ */
+static l_int32
+recogSplittingFilter(L_RECOG *recog,
+ PIX *pixs,
+ l_int32 minh,
+ 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);
+ if (minh <= 0) minh = DefaultMinHeight;
+
+ /* 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 < minh) {
+ if (debug) L_INFO("h = %d < %d\n", procName, h, minh);
+ *premove = 1;
+ return 0;
+ }
+ aspratio = (l_float32)w / (l_float32)h;
+ if (aspratio > recog->max_wh_ratio) {
+ 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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogExtractNumbers()
+ *
+ * \param[in] recog
+ * \param[in] boxas location of components
+ * \param[in] scorethresh min score for which we accept a component
+ * \param[in] spacethresh max horizontal distance allowed between digits;
+ * use -1 for default
+ * \param[out] pbaa [optional] bounding boxes of identified numbers
+ * \param[out] pnaa [optional] scores of identified digits
+ * \return sa of identified numbers, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+SARRAY *
+recogExtractNumbers(L_RECOG *recog,
+ BOXA *boxas,
+ l_float32 scorethresh,
+ l_int32 spacethresh,
+ BOXAA **pbaa,
+ NUMAA **pnaa)
+{
+char *str, *text;
+l_int32 i, n, x1, x2, h_ovl, v_ovl, h_sep, v_sep;
+l_float32 score;
+BOX *box, *prebox;
+BOXA *ba;
+BOXAA *baa;
+NUMA *nascore, *na;
+NUMAA *naa;
+SARRAY *satext, *sa, *saout;
+
+ PROCNAME("recogExtractNumbers");
+
+ if (pbaa) *pbaa = NULL;
+ if (pnaa) *pnaa = NULL;
+ if (!recog || !recog->rcha)
+ return (SARRAY *)ERROR_PTR("recog and rcha not both defined",
+ procName, NULL);
+ if (!boxas)
+ return (SARRAY *)ERROR_PTR("boxas not defined", procName, NULL);
+
+ if (spacethresh < 0)
+ spacethresh = L_MAX(recog->maxheight_u, 20);
+ rchaExtract(recog->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);
+ boxOverlapDistance(box, prebox, &h_ovl, &v_ovl);
+ h_sep = -h_ovl;
+ v_sep = -v_ovl;
+ 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);
+ L_INFO("saout has no identified text\n", procName);
+ return NULL;
+ }
+
+ if (pbaa)
+ *pbaa = baa;
+ else
+ boxaaDestroy(&baa);
+ if (pnaa)
+ *pnaa = naa;
+ else
+ numaaDestroy(&naa);
+ return saout;
+}
+
+/*!
+ * \brief showExtractNumbers()
+ *
+ * \param[in] pixs input 1 bpp image
+ * \param[in] sa recognized text strings
+ * \param[in] baa boxa array for location of characters in each string
+ * \param[in] naa numa array for scores of characters in each string
+ * \param[out] ppixdb [optional] input pixs with identified chars outlined
+ * \return pixa of identified strings with text and scores, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a debugging routine on digit identification; e.g.:
+ * recogIdentifyMultiple(recog, pixs, 0, 1, &boxa, NULL, NULL, 0);
+ * sa = recogExtractNumbers(recog, boxa, 0.8, -1, &baa, &naa);
+ * pixa = showExtractNumbers(pixs, sa, baa, naa, NULL);
+ * </pre>
+ */
+PIXA *
+showExtractNumbers(PIX *pixs,
+ SARRAY *sa,
+ BOXAA *baa,
+ NUMAA *naa,
+ PIX **ppixdb)
+{
+char buf[128];
+char *textstr, *scorestr;
+l_int32 i, j, n, nchar, len;
+l_float32 score;
+L_BMF *bmf;
+BOX *box1, *box2;
+BOXA *ba;
+NUMA *na;
+PIX *pix1, *pix2, *pix3, *pix4;
+PIXA *pixa;
+
+ PROCNAME("showExtractNumbers");
+
+ if (ppixdb) *ppixdb = NULL;
+ if (!pixs)
+ return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
+ if (!sa)
+ return (PIXA *)ERROR_PTR("sa not defined", procName, NULL);
+ if (!baa)
+ return (PIXA *)ERROR_PTR("baa not defined", procName, NULL);
+ if (!naa)
+ return (PIXA *)ERROR_PTR("naa not defined", procName, NULL);
+
+ n = sarrayGetCount(sa);
+ pixa = pixaCreate(n);
+ bmf = bmfCreate(NULL, 6);
+ if (ppixdb) *ppixdb = pixConvertTo8(pixs, 1);
+ for (i = 0; i < n; i++) {
+ textstr = sarrayGetString(sa, i, L_NOCOPY);
+ ba = boxaaGetBoxa(baa, i, L_CLONE);
+ na = numaaGetNuma(naa, i, L_CLONE);
+ boxaGetExtent(ba, NULL, NULL, &box1);
+ box2 = boxAdjustSides(NULL, box1, -5, 5, -5, 5);
+ if (ppixdb) pixRenderBoxArb(*ppixdb, box2, 3, 255, 0, 0);
+ pix1 = pixClipRectangle(pixs, box1, NULL);
+ len = strlen(textstr) + 1;
+ pix2 = pixAddBlackOrWhiteBorder(pix1, 14 * len, 14 * len,
+ 5, 3, L_SET_WHITE);
+ pix3 = pixConvertTo8(pix2, 1);
+ nchar = numaGetCount(na);
+ scorestr = NULL;
+ for (j = 0; j < nchar; j++) {
+ numaGetFValue(na, j, &score);
+ snprintf(buf, sizeof(buf), "%d", (l_int32)(100 * score));
+ stringJoinIP(&scorestr, buf);
+ if (j < nchar - 1) stringJoinIP(&scorestr, ",");
+ }
+ snprintf(buf, sizeof(buf), "%s: %s\n", textstr, scorestr);
+ pix4 = pixAddTextlines(pix3, bmf, buf, 0xff000000, L_ADD_BELOW);
+ pixaAddPix(pixa, pix4, L_INSERT);
+ boxDestroy(&box1);
+ boxDestroy(&box2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ boxaDestroy(&ba);
+ numaDestroy(&na);
+ LEPT_FREE(scorestr);
+ }
+
+ bmfDestroy(&bmf);
+ return pixa;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Static debug helper *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief l_showIndicatorSplitValues()
+ *
+ * \param[in] na1, na2, na3, na4, na5, na6 6 indicator array
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static void
+l_showIndicatorSplitValues(NUMA *na1,
+ NUMA *na2,
+ NUMA *na3,
+ NUMA *na4,
+ NUMA *na5,
+ NUMA *na6)
+{
+l_int32 i, n;
+
+ n = numaGetCount(na1);
+ lept_stderr("================================================\n");
+ lept_stderr("lt minw: ");
+ for (i = 0; i < n; i++)
+ lept_stderr("%4d ", (l_int32)na1->array[i]);
+ lept_stderr("\nlt minh: ");
+ for (i = 0; i < n; i++)
+ lept_stderr("%4d ", (l_int32)na2->array[i]);
+ lept_stderr("\ngt maxh: ");
+ for (i = 0; i < n; i++)
+ lept_stderr("%4d ", (l_int32)na3->array[i]);
+ lept_stderr("\ngt maxasp: ");
+ for (i = 0; i < n; i++)
+ lept_stderr("%4d ", (l_int32)na4->array[i]);
+ lept_stderr("\nlt minaf: ");
+ for (i = 0; i < n; i++)
+ lept_stderr("%4d ", (l_int32)na5->array[i]);
+ lept_stderr("\n------------------------------------------------");
+ lept_stderr("\nresult: ");
+ for (i = 0; i < n; i++)
+ lept_stderr("%4d ", (l_int32)na6->array[i]);
+ lept_stderr("\n================================================\n");
+}
diff --git a/leptonica/src/recogtrain.c b/leptonica/src/recogtrain.c
new file mode 100644
index 00000000..39f7a76c
--- /dev/null
+++ b/leptonica/src/recogtrain.c
@@ -0,0 +1,2482 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file recogtrain.c
+ * <pre>
+ *
+ * Training on labeled data
+ * l_int32 recogTrainLabeled()
+ * PIX *recogProcessLabeled()
+ * l_int32 recogAddSample()
+ * PIX *recogModifyTemplate()
+ * l_int32 recogAverageSamples()
+ * l_int32 pixaAccumulateSamples()
+ * l_int32 recogTrainingFinished()
+ * static l_int32 recogTemplatesAreOK()
+ * PIXA *recogFilterPixaBySize()
+ * PIXAA *recogSortPixaByClass()
+ * l_int32 recogRemoveOutliers1()
+ * PIXA *pixaRemoveOutliers1()
+ * l_int32 recogRemoveOutliers2()
+ * PIXA *pixaRemoveOutliers2()
+ *
+ * Training on unlabeled data
+ * L_RECOG recogTrainFromBoot()
+ *
+ * Padding the digit training set
+ * l_int32 recogPadDigitTrainingSet()
+ * l_int32 recogIsPaddingNeeded()
+ * static SARRAY *recogAddMissingClassStrings()
+ * PIXA *recogAddDigitPadTemplates()
+ * static l_int32 recogCharsetAvailable()
+ *
+ * Making a boot digit recognizer
+ * L_RECOG *recogMakeBootDigitRecog()
+ * PIXA *recogMakeBootDigitTemplates()
+ *
+ * Debugging
+ * l_int32 recogShowContent()
+ * l_int32 recogDebugAverages()
+ * l_int32 recogShowAverageTemplates()
+ * static PIX *pixDisplayOutliers()
+ * PIX *recogDisplayOutlier()
+ * PIX *recogShowMatchesInRange()
+ * PIX *recogShowMatch()
+ *
+ * These abbreviations are for the type of template to be used:
+ * * SI (for the scanned images)
+ * * WNL (for width-normalized lines, formed by first skeletonizing
+ * the scanned images, and then dilating to a fixed width)
+ * These abbreviations are for the type of recognizer:
+ * * BAR (book-adapted recognizer; the best type; can do identification
+ * with unscaled images and separation of touching characters.
+ * * BSR (bootstrap recognizer; used if more labeled templates are
+ * required for a BAR, either for finding more templates from
+ * the book, or making a hybrid BAR/BSR.
+ *
+ * The recog struct typically holds two versions of the input templates
+ * (e.g. from a pixa) that were used to generate it. One version is
+ * the unscaled input templates. The other version is the one that
+ * will be used by the recog to identify unlabeled data. That version
+ * depends on the input parameters when the recog is created. The choices
+ * for the latter version, and their suggested use, are:
+ * (1) unscaled SI -- typical for BAR, generated from book images
+ * (2) unscaled WNL -- ditto
+ * (3) scaled SI -- typical for recognizers containing template
+ * images from sources other than the book to be recognized
+ * (4) scaled WNL -- ditto
+ * For cases (3) and (4), we recommend scaling to fixed height; e.g.,
+ * scalew = 0, scaleh = 40.
+ * When using WNL, we recommend using a width of 5 in the template
+ * and 4 in the unlabeled data.
+ * It appears that better results for a BAR are usually obtained using
+ * SI than WNL, but more experimentation is needed.
+ *
+ * This utility is designed to build recognizers that are specifically
+ * adapted from a large amount of material, such as a book. These
+ * use labeled templates taken from the material, and not scaled.
+ * In addition, two special recognizers are useful:
+ * (1) Bootstrap recognizer (BSR). This uses height-scaled templates,
+ * that have been extended with several repetitions in one of two ways:
+ * (a) aniotropic width scaling (for either SI or WNL)
+ * (b) iterative erosions/dilations (for SI).
+ * (2) Outlier removal. This uses height scaled templates. It can be
+ * implemented without using templates that are aligned averages of all
+ * templates in a class.
+ *
+ * Recognizers are inexpensive to generate, for example, from a pixa
+ * of labeled templates. The general process of building a BAR is
+ * to start with labeled templates, e.g., in a pixa, make a BAR, and
+ * analyze new samples from the book to augment the BAR until it has
+ * enough samples for each character class. Along the way, samples
+ * from a BSR may be added for help in training. If not enough samples
+ * are available for the BAR, it can finally be augmented with BSR
+ * samples, in which case the resulting hybrid BAR/BSR recognizer
+ * must work on scaled images.
+ *
+ * Here are the steps in doing recog training:
+ * A. Generate a BAR from any existing labeled templates
+ * (1) Create a recog and add the templates, using recogAddSample().
+ * This stores the unscaled templates.
+ * [Note: this can be done in one step if the labeled templates are put
+ * into a pixa:
+ * L_Recog *rec = recogCreateFromPixa(pixa, ...); ]
+ * (2) Call recogTrainingFinished() to generate the (sometimes modified)
+ * templates to be used for correlation.
+ * (3) Optionally, remove outliers.
+ * If there are sufficient samples in the classes, we're done. Otherwise,
+ * B. Try to get more samples from the book to pad the BAR.
+ * (1) Save the unscaled, labeled templates from the BAR.
+ * (2) Supplement the BAR with bootstrap templates to make a hybrid BAR/BSR.
+ * (3) Do recognition on more unlabeled images, scaled to a fixed height
+ * (4) Add the unscaled, labeled images to the saved set.
+ * (5) Optionally, remove outliers.
+ * If there are sufficient samples in the classes, we're done. Otherwise,
+ * C. For classes without a sufficient number of templates, we can
+ * supplement the BAR with templates from a BSR (a hybrid RAR/BSR),
+ * and do recognition scaled to a fixed height.
+ *
+ * Here are several methods that can be used for identifying outliers:
+ * (1) Compute average templates for each class and remove a candidate
+ * that is poorly correlated with the average. This is the most
+ * simple method. recogRemoveOutliers1() uses this, supplemented with
+ * a second threshold and a target number of templates to be saved.
+ * (2) Compute average templates for each class and remove a candidate
+ * that is more highly correlated with the average of some other class.
+ * This does not require setting a threshold for the correlation.
+ * recogRemoveOutliers2() uses this method, supplemented with a minimum
+ * correlation score.
+ * (3) For each candidate, find the average correlation with other
+ * members of its class, and remove those that have a relatively
+ * low average correlation. This is similar to (1), gives comparable
+ * results and because it does not use average templates, it requires
+ * a bit more computation.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Static functions */
+static l_int32 recogTemplatesAreOK(L_RECOG *recog, l_int32 minsize,
+ l_float32 minfract, l_int32 *pok);
+static SARRAY *recogAddMissingClassStrings(L_RECOG *recog);
+static l_int32 recogCharsetAvailable(l_int32 type);
+static PIX *pixDisplayOutliers(PIXA *pixas, NUMA *nas);
+static PIX *recogDisplayOutlier(L_RECOG *recog, l_int32 iclass, l_int32 jsamp,
+ l_int32 maxclass, l_float32 maxscore);
+
+ /* Default parameters that are used in recogTemplatesAreOK() and
+ * in outlier removal functions, and that use template set size
+ * to decide if the set of templates (before outliers are removed)
+ * is valid. Values are set to accept most sets of sample templates. */
+static const l_int32 DefaultMinSetSize = 1; /* minimum number of
+ samples for a valid class */
+static const l_float32 DefaultMinSetFract = 0.4; /* minimum fraction
+ of classes required for a valid recog */
+
+ /* Defaults in pixaRemoveOutliers1() and pixaRemoveOutliers2() */
+static const l_float32 DefaultMinScore = 0.75; /* keep everything above */
+static const l_int32 DefaultMinTarget = 3; /* to be kept if possible */
+static const l_float32 LowerScoreThreshold = 0.5; /* templates can be
+ * kept down to this score to if needed to retain the
+ * desired minimum number of templates */
+
+
+/*------------------------------------------------------------------------*
+ * Training *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogTrainLabeled()
+ *
+ * \param[in] recog in training mode
+ * \param[in] pixs if depth > 1, will be thresholded to 1 bpp
+ * \param[in] box [optional] cropping box
+ * \param[in] text [optional] if null, use text field in pix
+ * \param[in] debug 1 to display images of samples not captured
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Training is restricted to the addition of a single
+ * character in an arbitrary (e.g., UTF8) charset
+ * (2) If box != null, it should represent the location in %pixs
+ * of the character image.
+ * </pre>
+ */
+l_ok
+recogTrainLabeled(L_RECOG *recog,
+ PIX *pixs,
+ BOX *box,
+ char *text,
+ l_int32 debug)
+{
+l_int32 ret;
+PIX *pix;
+
+ PROCNAME("recogTrainLabeled");
+
+ if (!recog)
+ return ERROR_INT("recog not defined", procName, 1);
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+
+ /* Prepare the sample to be added. This step also acts
+ * as a filter, and can invalidate pixs as a template. */
+ ret = recogProcessLabeled(recog, pixs, box, text, &pix);
+ if (ret) {
+ pixDestroy(&pix);
+ L_WARNING("failure to get sample '%s' for training\n", procName,
+ text);
+ return 1;
+ }
+
+ recogAddSample(recog, pix, debug);
+ pixDestroy(&pix);
+ return 0;
+}
+
+
+/*!
+ * \brief recogProcessLabeled()
+ *
+ * \param[in] recog in training mode
+ * \param[in] pixs if depth > 1, will be thresholded to 1 bpp
+ * \param[in] box [optional] cropping box
+ * \param[in] text [optional] if null, use text field in pix
+ * \param[out] ppix addr of pix, 1 bpp, labeled
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This crops and binarizes the input image, generating a pix
+ * of one character where the charval is inserted into the pix.
+ * </pre>
+ */
+l_ok
+recogProcessLabeled(L_RECOG *recog,
+ PIX *pixs,
+ BOX *box,
+ char *text,
+ PIX **ppix)
+{
+char *textdata;
+l_int32 textinpix, textin, nsets;
+NUMA *na;
+PIX *pix1, *pix2, *pix3, *pix4;
+
+ PROCNAME("recogProcessLabeled");
+
+ if (!ppix)
+ return ERROR_INT("&pix not defined", procName, 1);
+ *ppix = 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)
+ pix1 = pixClipRectangle(pixs, box, NULL);
+ else
+ pix1 = pixClone(pixs);
+ if (pixGetDepth(pix1) > 1)
+ pix2 = pixConvertTo1(pix1, recog->threshold);
+ else
+ pix2 = pixClone(pix1);
+ pixDestroy(&pix1);
+
+ /* Remove isolated noise, using as a criterion all components
+ * that are removed by a vertical opening of size 5. */
+ pix3 = pixMorphSequence(pix2, "o1.5", 0); /* seed */
+ pixSeedfillBinary(pix3, pix3, pix2, 8); /* fill from seed; clip to pix2 */
+ pixDestroy(&pix2);
+
+ /* Clip to foreground */
+ pixClipToForeground(pix3, &pix4, NULL);
+ pixDestroy(&pix3);
+ if (!pix4)
+ return ERROR_INT("pix4 is empty", procName, 1);
+
+ /* Verify that if there is more than 1 c.c., they all have
+ * horizontal overlap */
+ na = pixCountByColumn(pix4, NULL);
+ numaCountNonzeroRuns(na, &nsets);
+ numaDestroy(&na);
+ if (nsets > 1) {
+ L_WARNING("found %d sets of horiz separated c.c.; skipping\n",
+ procName, nsets);
+ pixDestroy(&pix4);
+ return 1;
+ }
+
+ pixSetText(pix4, textdata);
+ *ppix = pix4;
+ return 0;
+}
+
+
+/*!
+ * \brief recogAddSample()
+ *
+ * \param[in] recog
+ * \param[in] pix a single character, 1 bpp
+ * \param[in] debug
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The pix is 1 bpp, with the character string label embedded.
+ * (2) The pixaa_u array of the recog 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 is extended by adding a pixa to hold them.
+ * </pre>
+ */
+l_ok
+recogAddSample(L_RECOG *recog,
+ PIX *pix,
+ l_int32 debug)
+{
+char *text;
+l_int32 npa, charint, index;
+PIXA *pixa1;
+PIXAA *paa;
+
+ PROCNAME("recogAddSample");
+
+ if (!recog)
+ return ERROR_INT("recog not defined", procName, 1);
+ if (!pix || pixGetDepth(pix) != 1)
+ return ERROR_INT("pix not defined or not 1 bpp\n", procName, 1);
+ if (recog->train_done)
+ return ERROR_INT("not added: training has been completed", procName, 1);
+ paa = recog->pixaa_u;
+
+ /* Make sure the character is in the set */
+ text = pixGetText(pix);
+ if (l_convertCharstrToInt(text, &charint) == 1) {
+ L_ERROR("invalid text: %s\n", procName, text);
+ return 1;
+ }
+
+ /* Determine the class array index. Check if the class
+ * alreadly exists, and if not, add it. */
+ if (recogGetClassIndex(recog, charint, text, &index) == 1) {
+ /* New class must be added */
+ npa = pixaaGetCount(paa, NULL);
+ if (index > npa) {
+ L_ERROR("oops: bad index %d > npa %d!!\n", procName, index, npa);
+ return 1;
+ }
+ if (index == npa) { /* paa needs to be extended */
+ L_INFO("Adding new class and pixa: index = %d, text = %s\n",
+ procName, index, text);
+ 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);
+ }
+
+ /* Insert the unscaled character image into the right pixa.
+ * (Unscaled images are required to split touching characters.) */
+ recog->num_samples++;
+ pixaaAddPix(paa, index, pix, NULL, L_COPY);
+ return 0;
+}
+
+
+/*!
+ * \brief recogModifyTemplate()
+ *
+ * \param[in] recog
+ * \param[in] pixs 1 bpp, to be optionally scaled and turned into
+ * strokes of fixed width
+ * \return pixd modified pix if OK, NULL on error
+ */
+PIX *
+recogModifyTemplate(L_RECOG *recog,
+ PIX *pixs)
+{
+l_int32 w, h, empty;
+PIX *pix1, *pix2;
+
+ PROCNAME("recogModifyTemplate");
+
+ if (!recog)
+ return (PIX *)ERROR_PTR("recog not defined", procName, NULL);
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+ /* Scale first */
+ pixGetDimensions(pixs, &w, &h, NULL);
+ if ((recog->scalew == 0 || recog->scalew == w) &&
+ (recog->scaleh == 0 || recog->scaleh == h)) { /* no scaling */
+ pix1 = pixCopy(NULL, pixs);
+ } else {
+ pix1 = pixScaleToSize(pixs, recog->scalew, recog->scaleh);
+ }
+ if (!pix1)
+ return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+
+ /* Then optionally convert to lines */
+ if (recog->linew <= 0) {
+ pix2 = pixClone(pix1);
+ } else {
+ pix2 = pixSetStrokeWidth(pix1, recog->linew, 1, 8);
+ }
+ pixDestroy(&pix1);
+ if (!pix2)
+ return (PIX *)ERROR_PTR("pix2 not made", procName, NULL);
+
+ /* Make sure we still have some pixels */
+ pixZero(pix2, &empty);
+ if (empty) {
+ pixDestroy(&pix2);
+ return (PIX *)ERROR_PTR("modified template has no pixels",
+ procName, NULL);
+ }
+ return pix2;
+}
+
+
+/*!
+ * \brief recogAverageSamples()
+ *
+ * \param[in] precog addr of existing recog; may be destroyed
+ * \param[in] debug
+ * \return 0 on success, 1 on failure
+ *
+ * <pre>
+ * Notes:
+ * (1) This is only called in two situations:
+ * (a) When splitting characters using either the DID method
+ * recogDecode() or the the greedy splitter
+ * recogCorrelationBestRow()
+ * (b) By a special recognizer that is used to remove outliers.
+ * Both unscaled and scaled inputs are averaged.
+ * (2) If the data in any class is nonexistent (no samples), or
+ * very bad (no fg pixels in the average), or if the ratio
+ * of max/min average unscaled class template heights is
+ * greater than max_ht_ratio, this destroys the recog.
+ * The caller must check the return value of the recog.
+ * (3) Set debug = 1 to view the resulting templates and their centroids.
+ * </pre>
+ */
+l_int32
+recogAverageSamples(L_RECOG **precog,
+ l_int32 debug)
+{
+l_int32 i, nsamp, size, area, bx, by, badclass;
+l_float32 x, y, hratio;
+BOX *box;
+PIXA *pixa1;
+PIX *pix1, *pix2, *pix3;
+PTA *pta1;
+L_RECOG *recog;
+
+ PROCNAME("recogAverageSamples");
+
+ if (!precog)
+ return ERROR_INT("&recog not defined", procName, 1);
+ if ((recog = *precog) == NULL)
+ 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.
+ * Note that when we threshold to 1 bpp the 8 bpp averaged template
+ * that is returned from the accumulator, it will not be cropped
+ * to the foreground. We must crop it, because the correlator
+ * makes that assumption and will return a zero value if the
+ * width or height of the two images differs by several pixels.
+ * But cropping to fg can cause the value of the centroid to
+ * change, if bx > 0 or by > 0. */
+ badclass = FALSE;
+ for (i = 0; i < size; i++) {
+ pixa1 = pixaaGetPixa(recog->pixaa_u, i, L_CLONE);
+ pta1 = ptaaGetPta(recog->ptaa_u, i, L_CLONE);
+ nsamp = pixaGetCount(pixa1);
+ nsamp = L_MIN(nsamp, 256); /* we only use the first 256 */
+ if (nsamp == 0) { /* no information for this class */
+ L_ERROR("no samples in class %d\n", procName, i);
+ badclass = TRUE;
+ pixaDestroy(&pixa1);
+ ptaDestroy(&pta1);
+ break;
+ } else {
+ pixaAccumulateSamples(pixa1, pta1, &pix1, &x, &y);
+ pix2 = pixThresholdToBinary(pix1, L_MAX(1, nsamp / 2));
+ pixInvert(pix2, pix2);
+ pixClipToForeground(pix2, &pix3, &box);
+ if (!box) {
+ L_ERROR("no fg pixels in average for uclass %d\n", procName, i);
+ badclass = TRUE;
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa1);
+ ptaDestroy(&pta1);
+ break;
+ } else {
+ boxGetGeometry(box, &bx, &by, NULL, NULL);
+ pixaAddPix(recog->pixa_u, pix3, L_INSERT);
+ ptaAddPt(recog->pta_u, x - bx, y - by); /* correct centroid */
+ pixCountPixels(pix3, &area, recog->sumtab);
+ numaAddNumber(recog->nasum_u, area); /* foreground */
+ boxDestroy(&box);
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ }
+ pixaDestroy(&pixa1);
+ ptaDestroy(&pta1);
+ }
+
+ /* Are any classes bad? If so, destroy the recog and return an error */
+ if (badclass) {
+ recogDestroy(precog);
+ return ERROR_INT("at least 1 bad class; destroying recog", procName, 1);
+ }
+
+ /* Get the range of sizes of the unscaled average templates.
+ * Reject if the height ratio is too large. */
+ pixaSizeRange(recog->pixa_u, &recog->minwidth_u, &recog->minheight_u,
+ &recog->maxwidth_u, &recog->maxheight_u);
+ hratio = (l_float32)recog->maxheight_u / (l_float32)recog->minheight_u;
+ if (hratio > recog->max_ht_ratio) {
+ L_ERROR("ratio of max/min height of average templates = %4.1f;"
+ " destroying recog\n", procName, hratio);
+ recogDestroy(precog);
+ return 1;
+ }
+
+ /* Scaled bitmaps: compute averaged bitmap, centroid, and fg area */
+ for (i = 0; i < size; i++) {
+ pixa1 = pixaaGetPixa(recog->pixaa, i, L_CLONE);
+ pta1 = ptaaGetPta(recog->ptaa, i, L_CLONE);
+ nsamp = pixaGetCount(pixa1);
+ nsamp = L_MIN(nsamp, 256); /* we only use the first 256 */
+ pixaAccumulateSamples(pixa1, pta1, &pix1, &x, &y);
+ pix2 = pixThresholdToBinary(pix1, L_MAX(1, nsamp / 2));
+ pixInvert(pix2, pix2);
+ pixClipToForeground(pix2, &pix3, &box);
+ if (!box) {
+ L_ERROR("no fg pixels in average for sclass %d\n", procName, i);
+ badclass = TRUE;
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa1);
+ ptaDestroy(&pta1);
+ break;
+ } else {
+ boxGetGeometry(box, &bx, &by, NULL, NULL);
+ pixaAddPix(recog->pixa, pix3, L_INSERT);
+ ptaAddPt(recog->pta, x - bx, y - by); /* correct centroid */
+ pixCountPixels(pix3, &area, recog->sumtab);
+ numaAddNumber(recog->nasum, area); /* foreground */
+ boxDestroy(&box);
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixaDestroy(&pixa1);
+ ptaDestroy(&pta1);
+ }
+
+ if (badclass) {
+ recogDestroy(precog);
+ return ERROR_INT("at least 1 bad class; destroying recog", procName, 1);
+ }
+
+ /* Get the range of widths of the scaled average templates */
+ pixaSizeRange(recog->pixa, &recog->minwidth, NULL, &recog->maxwidth, NULL);
+
+ /* Get dimensions useful for splitting */
+ recog->min_splitw = L_MAX(5, recog->minwidth_u - 5);
+ recog->max_splith = recog->maxheight_u + 12; /* allow for skew */
+
+ if (debug)
+ recogShowAverageTemplates(recog);
+
+ recog->ave_done = TRUE;
+ return 0;
+}
+
+
+/*!
+ * \brief pixaAccumulateSamples()
+ *
+ * \param[in] pixa of samples from the same class, 1 bpp
+ * \param[in] pta [optional] of centroids of the samples
+ * \param[out] ppixd accumulated samples, 8 bpp
+ * \param[out] px [optional] average x coordinate of centroids
+ * \param[out] py [optional] average y coordinate of centroids
+ * \return 0 on success, 1 on failure
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 xc, yc, 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);
+
+ /* Find the centroids */
+ 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, &xc, &yc);
+ ptaAddPt(ptac, xc, yc);
+ }
+ 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, &xc, &yc);
+ xave += xc;
+ yave += yc;
+ }
+ xave = xave / (l_float32)n;
+ yave = yave / (l_float32)n;
+ if (px) *px = xave;
+ if (py) *py = yave;
+
+ /* Place all pix with their centroids located at the average
+ * centroid value, and sum the results. Make the accumulator
+ * image slightly larger than the largest sample to insure
+ * that all pixels are represented in the accumulator. */
+ pixaSizeRange(pixa, NULL, NULL, &maxw, &maxh);
+ pixsum = pixInitAccumulate(maxw + 5, maxh + 5, 0);
+ pix1 = pixCreate(maxw, maxh, 1);
+ for (i = 0; i < n; i++) {
+ pix2 = pixaGetPix(pixa, i, L_CLONE);
+ ptaGetPt(ptac, i, &xc, &yc);
+ xdiff = (l_int32)(xave - xc);
+ ydiff = (l_int32)(yave - yc);
+ 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;
+}
+
+
+/*!
+ * \brief recogTrainingFinished()
+ *
+ * \param[in] precog addr of recog
+ * \param[in] modifyflag 1 to use recogModifyTemplate(); 0 otherwise
+ * \param[in] minsize set to -1 for default
+ * \param[in] minfract set to -1.0 for default
+ * \return 0 if OK, 1 on error (input recog will be destroyed)
+ *
+ * <pre>
+ * Notes:
+ * (1) This must be called after all training samples have been added.
+ * (2) If the templates are not good enough, the recog input is destroyed.
+ * (3) Usually, %modifyflag == 1, because we want to apply
+ * recogModifyTemplate() to generate the actual templates
+ * that will be used. The one exception is when reading a
+ * serialized recog: there we want to put the same set of
+ * templates in both the unscaled and modified pixaa.
+ * See recogReadStream() to see why we do this.
+ * (4) See recogTemplatesAreOK() for %minsize and %minfract usage.
+ * (5) The following things are done here:
+ * (a) Allocate (or reallocate) storage for (possibly) modified
+ * bitmaps, centroids, and fg areas.
+ * (b) Generate the (possibly) modified bitmaps.
+ * (c) Compute centroid and fg area data for both unscaled and
+ * modified bitmaps.
+ * (d) Truncate the pixaa, ptaa and numaa arrays down from
+ * 256 to the actual size.
+ * (6) Putting these operations here makes it simple to recompute
+ * the recog with different modifications on the bitmaps.
+ * (7) Call recogShowContent() to display the templates, both
+ * unscaled and modified.
+ * </pre>
+ */
+l_ok
+recogTrainingFinished(L_RECOG **precog,
+ l_int32 modifyflag,
+ l_int32 minsize,
+ l_float32 minfract)
+{
+l_int32 ok, i, j, size, nc, ns, area;
+l_float32 xave, yave;
+PIX *pix, *pixd;
+PIXA *pixa;
+PIXAA *paa;
+PTA *pta;
+PTAA *ptaa;
+L_RECOG *recog;
+
+ PROCNAME("recogTrainingFinished");
+
+ if (!precog)
+ return ERROR_INT("&recog not defined", procName, 1);
+ if ((recog = *precog) == NULL)
+ return ERROR_INT("recog not defined", procName, 1);
+ if (recog->train_done) return 0;
+
+ /* Test the input templates */
+ recogTemplatesAreOK(recog, minsize, minfract, &ok);
+ if (!ok) {
+ recogDestroy(precog);
+ return ERROR_INT("bad templates", procName, 1);
+ }
+
+ /* 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 */
+ if (modifyflag == 1)
+ pixd = recogModifyTemplate(recog, pix);
+ else
+ pixd = pixClone(pix);
+ if (pixd) {
+ 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);
+ } else {
+ L_ERROR("failed: modified template for class %d, sample %d\n",
+ procName, i, j);
+ }
+ pixDestroy(&pix);
+ }
+ pixaDestroy(&pixa);
+ }
+
+ /* 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;
+}
+
+
+/*!
+ * \brief recogTemplatesAreOK()
+ *
+ * \param[in] recog
+ * \param[in] minsize set to -1 for default
+ * \param[in] minfract set to -1.0 for default
+ * \param[out] pok set to 1 if template set is valid; 0 otherwise
+ * \return 1 on error; 0 otherwise. An invalid template set is not an error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This is called by recogTrainingFinished(). A return value of 0
+ * will cause recogTrainingFinished() to destroy the recog.
+ * (2) %minsize is the minimum number of samples required for
+ * the class; -1 uses the default
+ * (3) %minfract is the minimum fraction of classes required for
+ * the recog to be usable; -1.0 uses the default
+ * </pre>
+ */
+static l_int32
+recogTemplatesAreOK(L_RECOG *recog,
+ l_int32 minsize,
+ l_float32 minfract,
+ l_int32 *pok)
+{
+l_int32 i, n, validsets, nt;
+l_float32 ratio;
+NUMA *na;
+
+ PROCNAME("recogTemplatesAreOK");
+
+ if (!pok)
+ return ERROR_INT("&ok not defined", procName, 1);
+ *pok = 0;
+ if (!recog)
+ return ERROR_INT("recog not defined", procName, 1);
+
+ minsize = (minsize < 0) ? DefaultMinSetSize : minsize;
+ minfract = (minfract < 0) ? DefaultMinSetFract : minfract;
+ n = pixaaGetCount(recog->pixaa_u, &na);
+ validsets = 0;
+ for (i = 0, validsets = 0; i < n; i++) {
+ numaGetIValue(na, i, &nt);
+ if (nt >= minsize)
+ validsets++;
+ }
+ numaDestroy(&na);
+ ratio = (l_float32)validsets / (l_float32)recog->charset_size;
+ *pok = (ratio >= minfract) ? 1 : 0;
+ return 0;
+}
+
+
+/*!
+ * \brief recogFilterPixaBySize()
+ *
+ * \param[in] pixas labeled templates
+ * \param[in] setsize size of character set (number of classes)
+ * \param[in] maxkeep max number of templates to keep in a class
+ * \param[in] max_ht_ratio max allowed height ratio (see below)
+ * \param[out] pna [optional] debug output, giving the number
+ * in each class after filtering; use NULL to skip
+ * \return pixa filtered templates, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The basic assumption is that the most common and larger
+ * templates in each class are more likely to represent the
+ * characters we are interested in. For example, larger digits
+ * are more likely to represent page numbers, and smaller digits
+ * could be data in tables. Therefore, we bias the first
+ * stage of filtering toward the larger characters by removing
+ * very small ones, and select based on proximity of the
+ * remaining characters to median height.
+ * (2) For each of the %setsize classes, order the templates
+ * increasingly by height. Take the rank 0.9 height. Eliminate
+ * all templates that are shorter by more than %max_ht_ratio.
+ * Of the remaining ones, select up to %maxkeep that are closest
+ * in rank order height to the median template.
+ * </pre>
+ */
+PIXA *
+recogFilterPixaBySize(PIXA *pixas,
+ l_int32 setsize,
+ l_int32 maxkeep,
+ l_float32 max_ht_ratio,
+ NUMA **pna)
+{
+l_int32 i, j, h90, hj, j1, j2, j90, n, nc;
+l_float32 ratio;
+NUMA *na;
+PIXA *pixa1, *pixa2, *pixa3, *pixa4, *pixa5;
+PIXAA *paa;
+
+ PROCNAME("recogFilterPixaBySize");
+
+ if (pna) *pna = NULL;
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+
+ if ((paa = recogSortPixaByClass(pixas, setsize)) == NULL)
+ return (PIXA *)ERROR_PTR("paa not made", procName, NULL);
+ nc = pixaaGetCount(paa, NULL);
+ na = (pna) ? numaCreate(0) : NULL;
+ if (pna) *pna = na;
+ pixa5 = pixaCreate(0);
+ for (i = 0; i < nc; i++) {
+ pixa1 = pixaaGetPixa(paa, i, L_CLONE);
+ if ((n = pixaGetCount(pixa1)) == 0) {
+ pixaDestroy(&pixa1);
+ continue;
+ }
+ pixa2 = pixaSort(pixa1, L_SORT_BY_HEIGHT, L_SORT_INCREASING, NULL,
+ L_COPY);
+ j90 = (l_int32)(0.9 * n);
+ pixaGetPixDimensions(pixa2, j90, NULL, &h90, NULL);
+ pixa3 = pixaCreate(n);
+ for (j = 0; j < n; j++) {
+ pixaGetPixDimensions(pixa2, j, NULL, &hj, NULL);
+ ratio = (l_float32)h90 / (l_float32)hj;
+ if (ratio <= max_ht_ratio)
+ pixaAddPix(pixa3, pixaGetPix(pixa2, j, L_COPY), L_INSERT);
+ }
+ n = pixaGetCount(pixa3);
+ if (n <= maxkeep) {
+ pixa4 = pixaCopy(pixa3, L_CLONE);
+ } else {
+ j1 = (n - maxkeep) / 2;
+ j2 = j1 + maxkeep - 1;
+ pixa4 = pixaSelectRange(pixa3, j1, j2, L_CLONE);
+ }
+ if (na) numaAddNumber(na, pixaGetCount(pixa4));
+ pixaJoin(pixa5, pixa4, 0, -1);
+ pixaDestroy(&pixa1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+ pixaDestroy(&pixa4);
+ }
+
+ pixaaDestroy(&paa);
+ return pixa5;
+}
+
+
+/*!
+ * \brief recogSortPixaByClass()
+ *
+ * \param[in] pixa labeled templates
+ * \param[in] setsize size of character set (number of classes)
+ * \return paa pixaa where each pixa has templates for one class,
+ * or null on error
+ */
+PIXAA *
+recogSortPixaByClass(PIXA *pixa,
+ l_int32 setsize)
+{
+PIXAA *paa;
+L_RECOG *recog;
+
+ PROCNAME("recogSortPixaByClass");
+
+ if (!pixa)
+ return (PIXAA *)ERROR_PTR("pixa not defined", procName, NULL);
+
+ if ((recog = recogCreateFromPixaNoFinish(pixa, 0, 0, 0, 0, 0)) == NULL)
+ return (PIXAA *)ERROR_PTR("recog not made", procName, NULL);
+ paa = recog->pixaa_u; /* grab the paa of unscaled templates */
+ recog->pixaa_u = NULL;
+ recogDestroy(&recog);
+ return paa;
+}
+
+
+/*!
+ * \brief recogRemoveOutliers1()
+ *
+ * \param[in] precog addr of recog with unscaled labeled templates
+ * \param[in] minscore keep everything with at least this score
+ * \param[in] mintarget minimum desired number to retain if possible
+ * \param[in] minsize minimum number of samples required for a class
+ * \param[out] ppixsave [optional debug] saved templates, with scores
+ * \param[out] ppixrem [optional debug] removed templates, with scores
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a convenience wrapper when using default parameters
+ * for the recog. See pixaRemoveOutliers1() for details.
+ * (2) If this succeeds, the new recog replaces the input recog;
+ * if it fails, the input recog is destroyed.
+ * </pre>
+ */
+l_ok
+recogRemoveOutliers1(L_RECOG **precog,
+ l_float32 minscore,
+ l_int32 mintarget,
+ l_int32 minsize,
+ PIX **ppixsave,
+ PIX **ppixrem)
+{
+PIXA *pixa1, *pixa2;
+L_RECOG *recog;
+
+ PROCNAME("recogRemoveOutliers1");
+
+ if (!precog)
+ return ERROR_INT("&recog not defined", procName, 1);
+ if (*precog == NULL)
+ return ERROR_INT("recog not defined", procName, 1);
+
+ /* Extract the unscaled templates */
+ pixa1 = recogExtractPixa(*precog);
+ recogDestroy(precog);
+
+ pixa2 = pixaRemoveOutliers1(pixa1, minscore, mintarget, minsize,
+ ppixsave, ppixrem);
+ pixaDestroy(&pixa1);
+ if (!pixa2)
+ return ERROR_INT("failure to remove outliers", procName, 1);
+
+ recog = recogCreateFromPixa(pixa2, 0, 0, 0, 150, 1);
+ pixaDestroy(&pixa2);
+ if (!recog)
+ return ERROR_INT("failure to make recog from pixa sans outliers",
+ procName, 1);
+
+ *precog = recog;
+ return 0;
+}
+
+
+/*!
+ * \brief pixaRemoveOutliers1()
+ *
+ * \param[in] pixas unscaled labeled templates
+ * \param[in] minscore keep everything with at least this score;
+ * use -1.0 for default.
+ * \param[in] mintarget minimum desired number to retain if possible;
+ * use -1 for default.
+ * \param[in] minsize minimum number of samples required for a class;
+ * use -1 for default.
+ * \param[out] ppixsave [optional debug] saved templates, with scores
+ * \param[out] ppixrem [optional debug] removed templates, with scores
+ * \return pixa of unscaled templates to be kept, or NULL on error
+ *
+ * <pre>
+ * 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 template is a best
+ * match for an input sample.
+ * (2) Because the score values depend strongly on the quality
+ * of the character images, to avoid losing too many samples
+ * we supplement a minimum score for retention with a score
+ * necessary to acquire the minimum target number of templates.
+ * To do this we are willing to use a lower threshold,
+ * LowerScoreThreshold, on the score. Consequently, with
+ * poor quality templates, we may keep samples with a score
+ * less than %minscore, but never less than LowerScoreThreshold.
+ * And if the number of samples is less than %minsize, we do
+ * not use any.
+ * (3) This is meant to be used on a BAR, where the templates all
+ * come from the same book; use minscore ~0.75.
+ * (4) Method: make a scaled recog from the input %pixas. Then,
+ * for each class: generate the averages, match each
+ * scaled template against the average, and save unscaled
+ * templates that had a sufficiently good match.
+ * </pre>
+ */
+PIXA *
+pixaRemoveOutliers1(PIXA *pixas,
+ l_float32 minscore,
+ l_int32 mintarget,
+ l_int32 minsize,
+ PIX **ppixsave,
+ PIX **ppixrem)
+{
+l_int32 i, j, debug, n, area1, area2;
+l_float32 x1, y1, x2, y2, minfract, score, rankscore, threshscore;
+NUMA *nasum, *narem, *nasave, *nascore;
+PIX *pix1, *pix2;
+PIXA *pixa, *pixarem, *pixad;
+PTA *pta;
+L_RECOG *recog;
+
+ PROCNAME("pixaRemoveOutliers1");
+
+ if (ppixsave) *ppixsave = NULL;
+ if (ppixrem) *ppixrem = NULL;
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ minscore = L_MIN(minscore, 1.0);
+ if (minscore <= 0.0)
+ minscore = DefaultMinScore;
+ mintarget = L_MIN(mintarget, 3);
+ if (mintarget <= 0)
+ mintarget = DefaultMinTarget;
+ if (minsize < 0)
+ minsize = DefaultMinSetSize;
+
+ /* Make a special height-scaled recognizer with average templates */
+ debug = (ppixsave || ppixrem) ? 1 : 0;
+ recog = recogCreateFromPixa(pixas, 0, 40, 0, 128, 1);
+ if (!recog)
+ return (PIXA *)ERROR_PTR("bad pixas; recog not made", procName, NULL);
+ recogAverageSamples(&recog, debug);
+ if (!recog)
+ return (PIXA *)ERROR_PTR("bad templates", procName, NULL);
+
+ nasave = (ppixsave) ? numaCreate(0) : NULL;
+ pixarem = (ppixrem) ? pixaCreate(0) : NULL;
+ narem = (ppixrem) ? numaCreate(0) : NULL;
+
+ pixad = pixaCreate(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 scores for each sample in the class */
+ pixa = pixaaGetPixa(recog->pixaa, i, L_CLONE);
+ pta = ptaaGetPta(recog->ptaa, i, L_CLONE); /* centroids */
+ nasum = numaaGetNuma(recog->naasum, i, L_CLONE); /* fg areas */
+ n = pixaGetCount(pixa);
+ nascore = numaCreate(n);
+ for (j = 0; j < n; j++) {
+ pix2 = pixaGetPix(pixa, j, L_CLONE);
+ ptaGetPt(pta, j, &x2, &y2); /* centroid average */
+ numaGetIValue(nasum, j, &area2); /* fg sum average */
+ pixCorrelationScoreSimple(pix1, pix2, area1, area2,
+ x1 - x2, y1 - y2, 5, 5,
+ recog->sumtab, &score);
+ numaAddNumber(nascore, score);
+ if (debug && score == 0.0) /* typ. large size difference */
+ lept_stderr("Got 0 score for i = %d, j = %d\n", i, j);
+ pixDestroy(&pix2);
+ }
+ pixDestroy(&pix1);
+
+ /* Find the rankscore, corresponding to the 1.0 - minfract.
+ * To attempt to maintain the minfract of templates, use as a
+ * cutoff the minimum of minscore and the rank score. However,
+ * no template is saved with an actual score less than
+ * that at least one template is kept. */
+ minfract = (l_float32)mintarget / (l_float32)n;
+ numaGetRankValue(nascore, 1.0 - minfract, NULL, 0, &rankscore);
+ threshscore = L_MAX(LowerScoreThreshold,
+ L_MIN(minscore, rankscore));
+ if (debug) {
+ L_INFO("minscore = %4.2f, rankscore = %4.2f, threshscore = %4.2f\n",
+ procName, minscore, rankscore, threshscore);
+ }
+
+ /* Save templates that are at or above threshold.
+ * Toss any classes with less than %minsize templates. */
+ for (j = 0; j < n; j++) {
+ numaGetFValue(nascore, j, &score);
+ pix1 = pixaaGetPix(recog->pixaa_u, i, j, L_COPY);
+ if (score >= threshscore && n >= minsize) {
+ pixaAddPix(pixad, pix1, L_INSERT);
+ if (nasave) numaAddNumber(nasave, score);
+ } else if (debug) {
+ pixaAddPix(pixarem, pix1, L_INSERT);
+ numaAddNumber(narem, score);
+ } else {
+ pixDestroy(&pix1);
+ }
+ }
+
+ pixaDestroy(&pixa);
+ ptaDestroy(&pta);
+ numaDestroy(&nasum);
+ numaDestroy(&nascore);
+ }
+
+ if (ppixsave) {
+ *ppixsave = pixDisplayOutliers(pixad, nasave);
+ numaDestroy(&nasave);
+ }
+ if (ppixrem) {
+ *ppixrem = pixDisplayOutliers(pixarem, narem);
+ pixaDestroy(&pixarem);
+ numaDestroy(&narem);
+ }
+ recogDestroy(&recog);
+ return pixad;
+}
+
+
+/*!
+ * \brief recogRemoveOutliers2()
+ *
+ * \param[in] precog addr of recog with unscaled labeled templates
+ * \param[in] minscore keep everything with at least this score
+ * \param[in] minsize minimum number of samples required for a class
+ * \param[out] ppixsave [optional debug] saved templates, with scores
+ * \param[out] ppixrem [optional debug] removed templates, with scores
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a convenience wrapper when using default parameters
+ * for the recog. See pixaRemoveOutliers2() for details.
+ * (2) If this succeeds, the new recog replaces the input recog;
+ * if it fails, the input recog is destroyed.
+ * </pre>
+ */
+l_ok
+recogRemoveOutliers2(L_RECOG **precog,
+ l_float32 minscore,
+ l_int32 minsize,
+ PIX **ppixsave,
+ PIX **ppixrem)
+{
+PIXA *pixa1, *pixa2;
+L_RECOG *recog;
+
+ PROCNAME("recogRemoveOutliers2");
+
+ if (!precog)
+ return ERROR_INT("&recog not defined", procName, 1);
+ if (*precog == NULL)
+ return ERROR_INT("recog not defined", procName, 1);
+
+ /* Extract the unscaled templates */
+ pixa1 = recogExtractPixa(*precog);
+ recogDestroy(precog);
+
+ pixa2 = pixaRemoveOutliers2(pixa1, minscore, minsize, ppixsave, ppixrem);
+ pixaDestroy(&pixa1);
+ if (!pixa2)
+ return ERROR_INT("failure to remove outliers", procName, 1);
+
+ recog = recogCreateFromPixa(pixa2, 0, 0, 0, 150, 1);
+ pixaDestroy(&pixa2);
+ if (!recog)
+ return ERROR_INT("failure to make recog from pixa sans outliers",
+ procName, 1);
+
+ *precog = recog;
+ return 0;
+}
+
+
+/*!
+ * \brief pixaRemoveOutliers2()
+ *
+ * \param[in] pixas unscaled labeled templates
+ * \param[in] minscore keep everything with at least this score;
+ * use -1.0 for default.
+ * \param[in] minsize minimum number of samples required for a class;
+ * use -1 for default.
+ * \param[out] ppixsave [optional debug] saved templates, with scores
+ * \param[out] ppixrem [optional debug] removed templates, with scores
+ * \return pixa of unscaled templates to be kept, or NULL on error
+ *
+ * <pre>
+ * 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 template is a best
+ * match for an input sample.
+ * (2) This method compares each template against the average templates
+ * of each class, and discards any template that has a higher
+ * correlation to a class different from its own. It also
+ * sets a lower bound on correlation scores with its class average.
+ * (3) This is meant to be used on a BAR, where the templates all
+ * come from the same book; use minscore ~0.75.
+ * </pre>
+ */
+PIXA *
+pixaRemoveOutliers2(PIXA *pixas,
+ l_float32 minscore,
+ l_int32 minsize,
+ PIX **ppixsave,
+ PIX **ppixrem)
+{
+l_int32 i, j, k, n, area1, area2, maxk, debug;
+l_float32 x1, y1, x2, y2, score, maxscore;
+NUMA *nan, *nascore, *nasave;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixarem, *pixad;
+L_RECOG *recog;
+
+ PROCNAME("pixaRemoveOutliers2");
+
+ if (ppixsave) *ppixsave = NULL;
+ if (ppixrem) *ppixrem = NULL;
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ minscore = L_MIN(minscore, 1.0);
+ if (minscore <= 0.0)
+ minscore = DefaultMinScore;
+ if (minsize < 0)
+ minsize = DefaultMinSetSize;
+
+ /* Make a special height-scaled recognizer with average templates */
+ debug = (ppixsave || ppixrem) ? 1 : 0;
+ recog = recogCreateFromPixa(pixas, 0, 40, 0, 128, 1);
+ if (!recog)
+ return (PIXA *)ERROR_PTR("bad pixas; recog not made", procName, NULL);
+ recogAverageSamples(&recog, debug);
+ if (!recog)
+ return (PIXA *)ERROR_PTR("bad templates", procName, NULL);
+
+ nasave = (ppixsave) ? numaCreate(0) : NULL;
+ pixarem = (ppixrem) ? pixaCreate(0) : NULL;
+
+ pixad = pixaCreate(0);
+ pixaaGetCount(recog->pixaa, &nan); /* number of templates in each class */
+ for (i = 0; i < recog->setsize; i++) {
+ /* Get the scores for each sample in the class, when comparing
+ * with averages from all the classes. */
+ numaGetIValue(nan, i, &n);
+ for (j = 0; j < n; j++) {
+ pix1 = pixaaGetPix(recog->pixaa, i, j, L_CLONE);
+ ptaaGetPt(recog->ptaa, i, j, &x1, &y1); /* centroid */
+ numaaGetValue(recog->naasum, i, j, NULL, &area1); /* fg sum */
+ nascore = numaCreate(n);
+ for (k = 0; k < recog->setsize; k++) { /* average templates */
+ pix2 = pixaGetPix(recog->pixa, k, L_CLONE);
+ ptaGetPt(recog->pta, k, &x2, &y2); /* average centroid */
+ numaGetIValue(recog->nasum, k, &area2); /* average fg sum */
+ pixCorrelationScoreSimple(pix1, pix2, area1, area2,
+ x1 - x2, y1 - y2, 5, 5,
+ recog->sumtab, &score);
+ numaAddNumber(nascore, score);
+ pixDestroy(&pix2);
+ }
+
+ /* Save templates that are in the correct class and
+ * at or above threshold. Toss any classes with less
+ * than %minsize templates. */
+ numaGetMax(nascore, &maxscore, &maxk);
+ if (maxk == i && maxscore >= minscore && n >= minsize) {
+ /* save it */
+ pix3 = pixaaGetPix(recog->pixaa_u, i, j, L_COPY);
+ pixaAddPix(pixad, pix3, L_INSERT);
+ if (nasave) numaAddNumber(nasave, maxscore);
+ } else if (ppixrem) { /* outlier */
+ pix3 = recogDisplayOutlier(recog, i, j, maxk, maxscore);
+ pixaAddPix(pixarem, pix3, L_INSERT);
+ }
+ numaDestroy(&nascore);
+ pixDestroy(&pix1);
+ }
+ }
+
+ if (ppixsave) {
+ *ppixsave = pixDisplayOutliers(pixad, nasave);
+ numaDestroy(&nasave);
+ }
+ if (ppixrem) {
+ *ppixrem = pixaDisplayTiledInRows(pixarem, 32, 1500, 1.0, 0, 20, 2);
+ pixaDestroy(&pixarem);
+ }
+
+ numaDestroy(&nan);
+ recogDestroy(&recog);
+ return pixad;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Training on unlabeled data *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogTrainFromBoot()
+ *
+ * \param[in] recogboot labeled boot recognizer
+ * \param[in] pixas set of unlabeled input characters
+ * \param[in] minscore min score for accepting the example; e.g., 0.75
+ * \param[in] threshold for binarization, if needed
+ * \param[in] debug 1 for debug output saved to recogboot; 0 otherwise
+ * \return pixad labeled version of input pixas, trained on a BSR,
+ * or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This takes %pixas of unscaled single characters and %recboot,
+ * a bootstrep recognizer (BSR) that has been set up with parameters
+ * * scaleh: scale all templates to this height
+ * * linew: width of normalized strokes, or 0 if using
+ * the input image
+ * It modifies the pix in %pixas accordingly and correlates
+ * with the templates in the BSR. It returns those input
+ * images in %pixas whose best correlation with the BSR is at
+ * or above %minscore. The returned pix have added text labels
+ * for the text string of the class to which the best
+ * correlated template belongs.
+ * (2) Identification occurs in scaled mode (typically with h = 40),
+ * optionally using a width-normalized line images derived
+ * from those in %pixas.
+ * </pre>
+ */
+PIXA *
+recogTrainFromBoot(L_RECOG *recogboot,
+ PIXA *pixas,
+ l_float32 minscore,
+ l_int32 threshold,
+ l_int32 debug)
+{
+char *text;
+l_int32 i, n, same, maxd, scaleh, linew;
+l_float32 score;
+PIX *pix1, *pix2, *pixdb;
+PIXA *pixa1, *pixa2, *pixa3, *pixad;
+
+ PROCNAME("recogTrainFromBoot");
+
+ if (!recogboot)
+ return (PIXA *)ERROR_PTR("recogboot not defined", procName, NULL);
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+
+ /* Make sure all input pix are 1 bpp */
+ if ((n = pixaGetCount(pixas)) == 0)
+ return (PIXA *)ERROR_PTR("no pix in pixa", procName, NULL);
+ pixaVerifyDepth(pixas, &same, &maxd);
+ if (maxd == 1) {
+ pixa1 = pixaCopy(pixas, L_COPY);
+ } else {
+ pixa1 = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ pix2 = pixConvertTo1(pix1, threshold);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ }
+
+ /* Scale the input images to match the BSR */
+ scaleh = recogboot->scaleh;
+ linew = recogboot->linew;
+ pixa2 = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa1, i, L_CLONE);
+ pix2 = pixScaleToSize(pix1, 0, scaleh);
+ pixaAddPix(pixa2, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa1);
+
+ /* Optionally convert to width-normalized line */
+ if (linew > 0)
+ pixa3 = pixaSetStrokeWidth(pixa2, linew, 4, 8);
+ else
+ pixa3 = pixaCopy(pixa2, L_CLONE);
+ pixaDestroy(&pixa2);
+
+ /* Identify using recogboot */
+ n = pixaGetCount(pixa3);
+ pixad = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixa3, i, L_COPY);
+ pixSetText(pix1, NULL); /* remove any existing text or labelling */
+ if (!debug) {
+ recogIdentifyPix(recogboot, pix1, NULL);
+ } else {
+ recogIdentifyPix(recogboot, pix1, &pixdb);
+ pixaAddPix(recogboot->pixadb_boot, pixdb, L_INSERT);
+ }
+ rchExtract(recogboot->rch, NULL, &score, &text, NULL, NULL, NULL, NULL);
+ if (score >= minscore) {
+ pix2 = pixaGetPix(pixas, i, L_COPY);
+ pixSetText(pix2, text);
+ pixaAddPix(pixad, pix2, L_INSERT);
+ pixaAddPix(recogboot->pixadb_boot, pixdb, L_COPY);
+ }
+ LEPT_FREE(text);
+ pixDestroy(&pix1);
+ }
+ pixaDestroy(&pixa3);
+
+ return pixad;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Padding the digit training set *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogPadDigitTrainingSet()
+ *
+ * \param[in,out] precog trained; if padding is needed, it is replaced
+ * by a a new padded recog
+ * \param[in] scaleh must be > 0; suggest ~40.
+ * \param[in] linew use 0 for original scanned images
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a no-op if padding is not needed. However,
+ * if it is, this replaces the input recog with a new recog,
+ * padded appropriately with templates from a boot recognizer,
+ * and set up with correlation templates derived from
+ * %scaleh and %linew.
+ * </pre>
+ */
+l_ok
+recogPadDigitTrainingSet(L_RECOG **precog,
+ l_int32 scaleh,
+ l_int32 linew)
+{
+PIXA *pixa;
+L_RECOG *recog1, *recog2;
+SARRAY *sa;
+
+ PROCNAME("recogPadDigitTrainingSet");
+
+ if (!precog)
+ return ERROR_INT("&recog not defined", procName, 1);
+ recog1 = *precog;
+
+ recogIsPaddingNeeded(recog1, &sa);
+ if (!sa) return 0;
+
+ /* Get a new pixa with the padding templates added */
+ pixa = recogAddDigitPadTemplates(recog1, sa);
+ sarrayDestroy(&sa);
+ if (!pixa)
+ return ERROR_INT("pixa not made", procName, 1);
+
+ /* Need to use templates that are scaled to a fixed height. */
+ if (scaleh <= 0) {
+ L_WARNING("templates must be scaled to fixed height; using %d\n",
+ procName, 40);
+ scaleh = 40;
+ }
+
+ /* Create a hybrid recog, composed of templates from both
+ * the original and bootstrap sources. */
+ recog2 = recogCreateFromPixa(pixa, 0, scaleh, linew, recog1->threshold,
+ recog1->maxyshift);
+ pixaDestroy(&pixa);
+ recogDestroy(precog);
+ *precog = recog2;
+ return 0;
+}
+
+
+/*!
+ * \brief recogIsPaddingNeeded()
+ *
+ * \param[in] recog trained
+ * \param[out] psa addr of returned string containing text value
+ * \return 1 on error; 0 if OK, whether or not additional padding
+ * templates are required.
+ *
+ * <pre>
+ * Notes:
+ * (1) This returns a string array in &sa containing character values
+ * for which extra templates are needed; this sarray is
+ * used by recogGetPadTemplates(). It returns NULL
+ * if no padding templates are needed.
+ * </pre>
+ */
+l_int32
+recogIsPaddingNeeded(L_RECOG *recog,
+ SARRAY **psa)
+{
+char *str;
+l_int32 i, nt, min_nopad, nclass, allclasses;
+l_float32 minval;
+NUMA *naclass;
+SARRAY *sa;
+
+ PROCNAME("recogIsPaddingNeeded");
+
+ if (!psa)
+ return ERROR_INT("&sa not defined", procName, 1);
+ *psa = NULL;
+ if (!recog)
+ return ERROR_INT("recog not defined", procName, 1);
+
+ /* Do we have samples from all classes? */
+ nclass = pixaaGetCount(recog->pixaa_u, &naclass); /* unscaled bitmaps */
+ allclasses = (nclass == recog->charset_size) ? 1 : 0;
+
+ /* Are there enough samples in each class already? */
+ min_nopad = recog->min_nopad;
+ numaGetMin(naclass, &minval, NULL);
+ if (allclasses && (minval >= min_nopad)) {
+ numaDestroy(&naclass);
+ return 0;
+ }
+
+ /* Are any classes not represented? */
+ sa = recogAddMissingClassStrings(recog);
+ *psa = sa;
+
+ /* Are any other classes under-represented? */
+ for (i = 0; i < nclass; i++) {
+ numaGetIValue(naclass, i, &nt);
+ if (nt < min_nopad) {
+ str = sarrayGetString(recog->sa_text, i, L_COPY);
+ sarrayAddString(sa, str, L_INSERT);
+ }
+ }
+ numaDestroy(&naclass);
+ return 0;
+}
+
+
+/*!
+ * \brief recogAddMissingClassStrings()
+ *
+ * \param[in] recog trained
+ * \return sa of class string missing in %recog, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This returns an empty %sa if there is at least one template
+ * in each class in %recog.
+ * </pre>
+ */
+static SARRAY *
+recogAddMissingClassStrings(L_RECOG *recog)
+{
+char *text;
+char str[4];
+l_int32 i, nclass, index, ival;
+NUMA *na;
+SARRAY *sa;
+
+ PROCNAME("recogAddMissingClassStrings");
+
+ if (!recog)
+ return (SARRAY *)ERROR_PTR("recog not defined", procName, NULL);
+
+ /* Only handling digits */
+ nclass = pixaaGetCount(recog->pixaa_u, NULL); /* unscaled bitmaps */
+ if (recog->charset_type != 1 || nclass == 10)
+ return sarrayCreate(0); /* empty */
+
+ /* Make an indicator array for missing classes */
+ na = numaCreate(0);
+ sa = sarrayCreate(0);
+ for (i = 0; i < recog->charset_size; i++)
+ numaAddNumber(na, 1);
+ for (i = 0; i < nclass; i++) {
+ text = sarrayGetString(recog->sa_text, i, L_NOCOPY);
+ index = text[0] - '0';
+ numaSetValue(na, index, 0);
+ }
+
+ /* Convert to string and add to output */
+ for (i = 0; i < nclass; i++) {
+ numaGetIValue(na, i, &ival);
+ if (ival == 1) {
+ str[0] = '0' + i;
+ str[1] = '\0';
+ sarrayAddString(sa, str, L_COPY);
+ }
+ }
+ numaDestroy(&na);
+ return sa;
+}
+
+
+/*!
+ * \brief recogAddDigitPadTemplates()
+ *
+ * \param[in] recog trained
+ * \param[in] sa set of text strings that need to be padded
+ * \return pixa of all templates from %recog and the additional pad
+ * templates from a boot recognizer; or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Call recogIsPaddingNeeded() first, which returns %sa of
+ * template text strings for classes where more templates
+ * are needed.
+ * </pre>
+ */
+PIXA *
+recogAddDigitPadTemplates(L_RECOG *recog,
+ SARRAY *sa)
+{
+char *str, *text;
+l_int32 i, j, n, nt;
+PIX *pix;
+PIXA *pixa1, *pixa2;
+
+ PROCNAME("recogAddDigitPadTemplates");
+
+ if (!recog)
+ return (PIXA *)ERROR_PTR("recog not defined", procName, NULL);
+ if (!sa)
+ return (PIXA *)ERROR_PTR("sa not defined", procName, NULL);
+ if (recogCharsetAvailable(recog->charset_type) == FALSE)
+ return (PIXA *)ERROR_PTR("boot charset not available", procName, NULL);
+
+ /* Make boot recog templates */
+ pixa1 = recogMakeBootDigitTemplates(0, 0);
+ n = pixaGetCount(pixa1);
+
+ /* Extract the unscaled templates from %recog */
+ pixa2 = recogExtractPixa(recog);
+
+ /* Add selected boot recog templates based on the text strings in sa */
+ nt = sarrayGetCount(sa);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixa1, i, L_CLONE);
+ text = pixGetText(pix);
+ for (j = 0; j < nt; j++) {
+ str = sarrayGetString(sa, j, L_NOCOPY);
+ if (!strcmp(text, str)) {
+ pixaAddPix(pixa2, pix, L_COPY);
+ break;
+ }
+ }
+ pixDestroy(&pix);
+ }
+
+ pixaDestroy(&pixa1);
+ return pixa2;
+}
+
+
+/*!
+ * \brief recogCharsetAvailable()
+ *
+ * \param[in] 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\n", procName, type);
+ ret = FALSE;
+ break;
+ default:
+ L_INFO("charset type %d is unknown\n", procName, type);
+ ret = FALSE;
+ break;
+ }
+
+ return ret;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Making a boot digit recognizer *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogMakeBootDigitRecog()
+ *
+ * \param[in] nsamp number of samples of each digit; or 0
+ * \param[in] scaleh scale all heights to this; typ. use 40
+ * \param[in] linew normalized line width; typ. use 5; 0 to skip
+ * \param[in] maxyshift from nominal centroid alignment; typically 0 or 1
+ * \param[in] debug 1 for showing templates; 0 otherwise
+ * \return recog, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This takes a set of pre-computed, labeled pixa of single
+ * digits, and generates a recognizer from them.
+ * The templates used in the recognizer can be modified by:
+ * - scaling (isotropically to fixed height)
+ * - generating a skeleton and thickening so that all strokes
+ * have the same width.
+ * (2) The resulting templates are scaled versions of either the
+ * input bitmaps or images with fixed line widths. To use the
+ * input bitmaps, set %linew = 0; otherwise, set %linew to the
+ * desired line width.
+ * (3) If %nsamp == 0, this uses and extends the output from
+ * three boot generators:
+ * l_bootnum_gen1, l_bootnum_gen2, l_bootnum_gen3.
+ * Otherwise, it uses exactly %nsamp templates of each digit,
+ * extracted by l_bootnum_gen4.
+ * </pre>
+ */
+L_RECOG *
+recogMakeBootDigitRecog(l_int32 nsamp,
+ l_int32 scaleh,
+ l_int32 linew,
+ l_int32 maxyshift,
+ l_int32 debug)
+
+{
+PIXA *pixa;
+L_RECOG *recog;
+
+ /* Get the templates, extended by horizontal scaling */
+ pixa = recogMakeBootDigitTemplates(nsamp, debug);
+
+ /* Make the boot recog; recogModifyTemplate() will scale the
+ * templates and optionally turn them into strokes of fixed width. */
+ recog = recogCreateFromPixa(pixa, 0, scaleh, linew, 128, maxyshift);
+ pixaDestroy(&pixa);
+ if (debug)
+ recogShowContent(stderr, recog, 0, 1);
+
+ return recog;
+}
+
+
+/*!
+ * \brief recogMakeBootDigitTemplates()
+ *
+ * \param[in] nsamp number of samples of each digit; or 0
+ * \param[in] debug 1 for display of templates
+ * \return pixa of templates; or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See recogMakeBootDigitRecog().
+ * </pre>
+ */
+PIXA *
+recogMakeBootDigitTemplates(l_int32 nsamp,
+ l_int32 debug)
+{
+NUMA *na1;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa1, *pixa2, *pixa3;
+
+ if (nsamp > 0) {
+ pixa1 = l_bootnum_gen4(nsamp);
+ if (debug) {
+ pix1 = pixaDisplayTiledWithText(pixa1, 1500, 1.0, 10,
+ 2, 6, 0xff000000);
+ pixDisplay(pix1, 0, 0);
+ pixDestroy(&pix1);
+ }
+ return pixa1;
+ }
+
+ /* Else, generate from 3 pixa */
+ pixa1 = l_bootnum_gen1();
+ pixa2 = l_bootnum_gen2();
+ pixa3 = l_bootnum_gen3();
+ if (debug) {
+ pix1 = pixaDisplayTiledWithText(pixa1, 1500, 1.0, 10, 2, 6, 0xff000000);
+ pix2 = pixaDisplayTiledWithText(pixa2, 1500, 1.0, 10, 2, 6, 0xff000000);
+ pix3 = pixaDisplayTiledWithText(pixa3, 1500, 1.0, 10, 2, 6, 0xff000000);
+ pixDisplay(pix1, 0, 0);
+ pixDisplay(pix2, 600, 0);
+ pixDisplay(pix3, 1200, 0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+ pixaJoin(pixa1, pixa2, 0, -1);
+ pixaJoin(pixa1, pixa3, 0, -1);
+ pixaDestroy(&pixa2);
+ pixaDestroy(&pixa3);
+
+ /* Extend by horizontal scaling */
+ na1 = numaCreate(4);
+ numaAddNumber(na1, 0.9);
+ numaAddNumber(na1, 1.1);
+ numaAddNumber(na1, 1.2);
+ pixa2 = pixaExtendByScaling(pixa1, na1, L_HORIZ, 1);
+
+ pixaDestroy(&pixa1);
+ numaDestroy(&na1);
+ return pixa2;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Debugging *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief recogShowContent()
+ *
+ * \param[in] fp file stream
+ * \param[in] recog
+ * \param[in] index for naming of output files of template images
+ * \param[in] display 1 for showing template images; 0 otherwise
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+recogShowContent(FILE *fp,
+ L_RECOG *recog,
+ l_int32 index,
+ l_int32 display)
+{
+char buf[128];
+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->linew <= 0)
+ fprintf(fp, " Using image templates for matching\n");
+ else
+ fprintf(fp, " Using templates with fixed line width 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) {
+ lept_mkdir("lept/recog");
+ pix = pixaaDisplayByPixa(recog->pixaa_u, 50, 1.0, 20, 20, 0);
+ snprintf(buf, sizeof(buf), "/tmp/lept/recog/templates_u.%d.png", index);
+ pixWriteDebug(buf, pix, IFF_PNG);
+ pixDisplay(pix, 0, 200 * index);
+ pixDestroy(&pix);
+ if (recog->train_done) {
+ pix = pixaaDisplayByPixa(recog->pixaa, 50, 1.0, 20, 20, 0);
+ snprintf(buf, sizeof(buf),
+ "/tmp/lept/recog/templates.%d.png", index);
+ pixWriteDebug(buf, pix, IFF_PNG);
+ pixDisplay(pix, 800, 200 * index);
+ pixDestroy(&pix);
+ }
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief recogDebugAverages()
+ *
+ * \param[in] precog addr of recog
+ * \param[in] debug 0 no output; 1 for images; 2 for text; 3 for both
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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().
+ * (3) Destroys the recog if the averaging function finds any bad classes.
+ * </pre>
+ */
+l_ok
+recogDebugAverages(L_RECOG **precog,
+ l_int32 debug)
+{
+l_int32 i, j, n, np, index;
+l_float32 score;
+PIX *pix1, *pix2, *pix3;
+PIXA *pixa, *pixat;
+PIXAA *paa1, *paa2;
+L_RECOG *recog;
+
+ PROCNAME("recogDebugAverages");
+
+ if (!precog)
+ return ERROR_INT("&recog not defined", procName, 1);
+ if ((recog = *precog) == NULL)
+ 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);
+ if (!recog)
+ return ERROR_INT("averaging failed; recog destroyed", procName, 1);
+
+ /* Save a pixa of all the training examples */
+ paa1 = recog->pixaa;
+ 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)
+ lept_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, 50, 1.0, 20, 20, 0);
+ if (debug % 2) {
+ lept_mkdir("lept/recog");
+ pixWriteDebug("/tmp/lept/recog/templ_match.png", recog->pixdb_ave,
+ IFF_PNG);
+ pixDisplay(recog->pixdb_ave, 100, 100);
+ }
+
+ pixaaDestroy(&paa2);
+ return 0;
+}
+
+
+/*!
+ * \brief recogShowAverageTemplates()
+ *
+ * \param[in] recog
+ * \return 0 on success, 1 on failure
+ *
+ * <pre>
+ * Notes:
+ * (1) This debug routine generates a display of the averaged templates,
+ * both scaled and unscaled, with the centroid visible in red.
+ * </pre>
+ */
+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);
+
+ lept_stderr("min/max width_u = (%d,%d); min/max height_u = (%d,%d)\n",
+ recog->minwidth_u, recog->maxwidth_u,
+ recog->minheight_u, recog->maxheight_u);
+ lept_stderr("min splitw = %d, max splith = %d\n",
+ recog->min_splitw, 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;
+}
+
+
+/*!
+ * \brief pixDisplayOutliers()
+ *
+ * \param[in] pixas unscaled labeled templates
+ * \param[in] nas scores of templates (against class averages)
+ * \return pix tiled pixa with text and scores, or NULL on failure
+ *
+ * <pre>
+ * Notes:
+ * (1) This debug routine is called from recogRemoveOutliers2(),
+ * and takes the saved templates and their scores as input.
+ * </pre>
+ */
+static PIX *
+pixDisplayOutliers(PIXA *pixas,
+ NUMA *nas)
+{
+char *text;
+char buf[16];
+l_int32 i, n;
+l_float32 fval;
+PIX *pix1, *pix2;
+PIXA *pixa1;
+
+ PROCNAME("pixDisplayOutliers");
+
+ if (!pixas)
+ return (PIX *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (!nas)
+ return (PIX *)ERROR_PTR("nas not defined", procName, NULL);
+ n = pixaGetCount(pixas);
+ if (numaGetCount(nas) != n)
+ return (PIX *)ERROR_PTR("pixas and nas sizes differ", procName, NULL);
+
+ pixa1 = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ pix2 = pixAddBlackOrWhiteBorder(pix1, 25, 25, 0, 0, L_GET_WHITE_VAL);
+ text = pixGetText(pix1);
+ numaGetFValue(nas, i, &fval);
+ snprintf(buf, sizeof(buf), "'%s': %5.2f", text, fval);
+ pixSetText(pix2, buf);
+ pixaAddPix(pixa1, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+ pix1 = pixaDisplayTiledWithText(pixa1, 1500, 1.0, 20, 2, 6, 0xff000000);
+ pixaDestroy(&pixa1);
+ return pix1;
+}
+
+
+/*!
+ * \brief recogDisplayOutlier()
+ *
+ * \param[in] recog
+ * \param[in] iclass sample is in this class
+ * \param[in] jsamp index of sample is class i
+ * \param[in] maxclass index of class with closest average to sample
+ * \param[in] maxscore score of sample with average of class %maxclass
+ * \return pix sample and template images, with score, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This shows three templates, side-by-side:
+ * - The outlier sample
+ * - The average template from the same class
+ * - The average class template that best matched the outlier sample
+ * </pre>
+ */
+static PIX *
+recogDisplayOutlier(L_RECOG *recog,
+ l_int32 iclass,
+ l_int32 jsamp,
+ l_int32 maxclass,
+ l_float32 maxscore)
+{
+char buf[64];
+PIX *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXA *pixa;
+
+ PROCNAME("recogDisplayOutlier");
+
+ if (!recog)
+ return (PIX *)ERROR_PTR("recog not defined", procName, NULL);
+
+ pix1 = pixaaGetPix(recog->pixaa, iclass, jsamp, L_CLONE);
+ pix2 = pixaGetPix(recog->pixa, iclass, L_CLONE);
+ pix3 = pixaGetPix(recog->pixa, maxclass, L_CLONE);
+ pixa = pixaCreate(3);
+ pixaAddPix(pixa, pix1, L_INSERT);
+ pixaAddPix(pixa, pix2, L_INSERT);
+ pixaAddPix(pixa, pix3, L_INSERT);
+ pix4 = pixaDisplayTiledInRows(pixa, 32, 400, 2.0, 0, 12, 2);
+ snprintf(buf, sizeof(buf), "C=%d, BAC=%d, S=%4.2f", iclass, maxclass,
+ maxscore);
+ pix5 = pixAddSingleTextblock(pix4, recog->bmf, buf, 0xff000000,
+ L_ADD_BELOW, NULL);
+ pixDestroy(&pix4);
+ pixaDestroy(&pixa);
+ return pix5;
+}
+
+
+/*!
+ * \brief recogShowMatchesInRange()
+ *
+ * \param[in] recog
+ * \param[in] pixa of 1 bpp images to match
+ * \param[in] minscore min score to include output
+ * \param[in] maxscore max score to include output
+ * \param[in] display 1 to display the result
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This gives a visual output of the best matches for a given
+ * range of scores. Each pair of images can optionally be
+ * labeled with the index of the best match and the correlation.
+ * (2) To use this, save a set of 1 bpp images (labeled or
+ * unlabeled) that can be given to a recognizer in a pixa.
+ * Then call this function with the pixa and parameters
+ * to filter a range of scores.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief recogShowMatch()
+ *
+ * \param[in] recog
+ * \param[in] pix1 input pix; several possibilities
+ * \param[in] pix2 [optional] matching template
+ * \param[in] box [optional] region in pix1 for which pix2 matches
+ * \param[in] index index of matching template; use -1 to disable printing
+ * \param[in] score score of match
+ * \return pixd pair of images, showing input pix and best template,
+ * optionally with matching information, or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
diff --git a/leptonica/src/regutils.c b/leptonica/src/regutils.c
new file mode 100644
index 00000000..a2070aa4
--- /dev/null
+++ b/leptonica/src/regutils.c
@@ -0,0 +1,887 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file regutils.c
+ * <pre>
+ *
+ * Regression test utilities
+ * l_int32 regTestSetup()
+ * l_int32 regTestCleanup()
+ * l_int32 regTestCompareValues()
+ * l_int32 regTestCompareStrings()
+ * l_int32 regTestComparePix()
+ * l_int32 regTestCompareSimilarPix()
+ * l_int32 regTestCheckFile()
+ * l_int32 regTestCompareFiles()
+ * l_int32 regTestWritePixAndCheck()
+ * l_int32 regTestWriteDataAndCheck()
+ * char *regTestGenLocalFilename()
+ *
+ * Static function
+ * char *getRootNameFromArgv0()
+ *
+ * These functions are for testing and development. They are not intended
+ * for use with programs that run in a production environment, such as a
+ * cloud service with unrestricted access.
+ *
+ * See regutils.h for how to use this. Here is a minimal setup:
+ *
+ * main(int argc, char **argv) {
+ * ...
+ * L_REGPARAMS *rp;
+ *
+ * if (regTestSetup(argc, argv, &rp))
+ * return 1;
+ * ...
+ * regTestWritePixAndCheck(rp, pix, IFF_PNG); // 0
+ * ...
+ * return regTestCleanup(rp);
+ * }
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+extern l_int32 NumImageFileFormatExtensions;
+extern const char *ImageFileFormatExtensions[];
+
+static char *getRootNameFromArgv0(const char *argv0);
+
+
+/*--------------------------------------------------------------------*
+ * Regression test utilities *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief regTestSetup()
+ *
+ * \param[in] argc from invocation; can be either 1 or 2
+ * \param[in] argv to regtest: %argv[1] is one of these:
+ * "generate", "compare", "display"
+ * \param[out] prp all regression params
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Call this function with the args to the reg test. The first arg
+ * is the name of the reg test. There are three cases:
+ * Case 1:
+ * There is either only one arg, or the second arg is "compare".
+ * This is the mode in which you run a regression test
+ * (or a set of them), looking for failures and logging
+ * the results to a file. The output, which includes
+ * logging of all reg test failures plus a SUCCESS or
+ * FAILURE summary for each test, is appended to the file
+ * "/tmp/lept/reg_results.txt. For this case, as in Case 2,
+ * the display field in rp is set to FALSE, preventing
+ * image display.
+ * Case 2:
+ * The second arg is "generate". This will cause
+ * generation of new golden files for the reg test.
+ * The results of the reg test are not recorded, and
+ * the display field in rp is set to FALSE.
+ * Case 3:
+ * The second arg is "display". The test will run and
+ * files will be written. Comparisons with golden files
+ * will not be carried out, so the only notion of success
+ * or failure is with tests that do not involve golden files.
+ * The display field in rp is TRUE, and this is used by
+ * pixDisplayWithTitle().
+ * (2) See regutils.h for examples of usage.
+ * </pre>
+ */
+l_ok
+regTestSetup(l_int32 argc,
+ char **argv,
+ L_REGPARAMS **prp)
+{
+char *testname, *vers;
+char errormsg[64];
+L_REGPARAMS *rp;
+
+ 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);
+
+ setLeptDebugOK(1); /* required for testing */
+
+ rp = (L_REGPARAMS *)LEPT_CALLOC(1, sizeof(L_REGPARAMS));
+ *prp = rp;
+ rp->testname = testname;
+ rp->index = -1; /* increment before each test */
+
+ /* Initialize to true. A failure in any test is registered
+ * as a failure of the regression test. */
+ rp->success = TRUE;
+
+ /* Make sure the lept/regout subdirectory exists */
+ lept_mkdir("lept/regout");
+
+ /* Only open a stream to a temp file for the 'compare' case */
+ if (argc == 1 || !strcmp(argv[1], "compare")) {
+ rp->mode = L_REG_COMPARE;
+ rp->tempfile = stringNew("/tmp/lept/regout/regtest_output.txt");
+ rp->fp = fopenWriteStream(rp->tempfile, "wb");
+ if (rp->fp == NULL) {
+ rp->success = FALSE;
+ return ERROR_INT("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 */
+ lept_stderr("\n////////////////////////////////////////////////\n"
+ "//////////////// %s_reg ///////////////\n"
+ "////////////////////////////////////////////////\n",
+ rp->testname);
+ vers = getLeptonicaVersion();
+ lept_stderr("%s : ", vers);
+ LEPT_FREE(vers);
+ vers = getImagelibVersions();
+ lept_stderr("%s\n", vers);
+ LEPT_FREE(vers);
+
+ rp->tstart = startTimerNested();
+ return 0;
+}
+
+
+/*!
+ * \brief regTestCleanup()
+ *
+ * \param[in] rp regression test parameters
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This copies anything written to the temporary file to the
+ * output file /tmp/lept/reg_results.txt.
+ * </pre>
+ */
+l_ok
+regTestCleanup(L_REGPARAMS *rp)
+{
+char result[512];
+char *results_file; /* success/failure output in 'compare' mode */
+char *text, *message;
+l_int32 retval;
+size_t nbytes;
+
+ PROCNAME("regTestCleanup");
+
+ if (!rp)
+ return ERROR_INT("rp not defined", procName, 1);
+
+ lept_stderr("Time: %7.3f sec\n", stopTimerNested(rp->tstart));
+
+ /* If generating golden files or running in display mode, release rp */
+ if (!rp->fp) {
+ LEPT_FREE(rp->testname);
+ LEPT_FREE(rp->tempfile);
+ LEPT_FREE(rp);
+ return 0;
+ }
+
+ /* Compare mode: read back data from temp file */
+ fclose(rp->fp);
+ text = (char *)l_binaryRead(rp->tempfile, &nbytes);
+ LEPT_FREE(rp->tempfile);
+ if (!text) {
+ rp->success = FALSE;
+ LEPT_FREE(rp->testname);
+ LEPT_FREE(rp);
+ return ERROR_INT("text not returned", 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 = stringNew("/tmp/lept/reg_results.txt");
+ fileAppendString(results_file, message);
+ retval = (rp->success) ? 0 : 1;
+ LEPT_FREE(results_file);
+ LEPT_FREE(message);
+
+ LEPT_FREE(rp->testname);
+ LEPT_FREE(rp);
+ return retval;
+}
+
+
+/*!
+ * \brief regTestCompareValues()
+ *
+ * \param[in] rp regtest parameters
+ * \param[in] val1 typ. the golden value
+ * \param[in] val2 typ. the value computed
+ * \param[in] delta allowed max absolute difference
+ * \return 0 if OK, 1 on error a failure in comparison is not an error
+ */
+l_ok
+regTestCompareValues(L_REGPARAMS *rp,
+ l_float32 val1,
+ l_float32 val2,
+ l_float32 delta)
+{
+l_float32 diff;
+
+ 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);
+ }
+ lept_stderr("Failure in %s_reg: value comparison for index %d\n"
+ "difference = %f but allowed delta = %f\n",
+ rp->testname, rp->index, diff, delta);
+ rp->success = FALSE;
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief regTestCompareStrings()
+ *
+ * \param[in] rp regtest parameters
+ * \param[in] string1 typ. the expected string
+ * \param[in] bytes1 size of string1
+ * \param[in] string2 typ. the computed string
+ * \param[in] bytes2 size of string2
+ * \return 0 if OK, 1 on error a failure in comparison is not an error
+ */
+l_ok
+regTestCompareStrings(L_REGPARAMS *rp,
+ l_uint8 *string1,
+ size_t bytes1,
+ l_uint8 *string2,
+ size_t bytes2)
+{
+l_int32 same;
+char buf[256];
+
+ PROCNAME("regTestCompareStrings");
+
+ if (!rp)
+ return ERROR_INT("rp not defined", procName, 1);
+
+ rp->index++;
+ l_binaryCompare(string1, bytes1, string2, bytes2, &same);
+
+ /* Output on failure */
+ if (!same) {
+ /* Write the two strings to file */
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/string1_%d_%zu",
+ rp->index, bytes1);
+ l_binaryWrite(buf, "w", string1, bytes1);
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/string2_%d_%zu",
+ rp->index, bytes2);
+ l_binaryWrite(buf, "w", string2, bytes2);
+
+ /* Report comparison failure */
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/string*_%d_*", rp->index);
+ if (rp->fp) {
+ fprintf(rp->fp,
+ "Failure in %s_reg: string comp for index %d; "
+ "written to %s\n", rp->testname, rp->index, buf);
+ }
+ lept_stderr("Failure in %s_reg: string comp for index %d; "
+ "written to %s\n", rp->testname, rp->index, buf);
+ rp->success = FALSE;
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief regTestComparePix()
+ *
+ * \param[in] rp regtest parameters
+ * \param[in] pix1, pix2 to be tested for equality
+ * \return 0 if OK, 1 on error a failure in comparison is not an error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function compares two pix for equality. On failure,
+ * this writes to stderr.
+ * </pre>
+ */
+l_ok
+regTestComparePix(L_REGPARAMS *rp,
+ PIX *pix1,
+ PIX *pix2)
+{
+l_int32 same;
+
+ 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);
+ }
+ lept_stderr("Failure in %s_reg: pix comparison for index %d\n",
+ rp->testname, rp->index);
+ rp->success = FALSE;
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief regTestCompareSimilarPix()
+ *
+ * \param[in] rp regtest parameters
+ * \param[in] pix1, pix2 to be tested for near equality
+ * \param[in] mindiff minimum pixel difference to be counted; > 0
+ * \param[in] maxfract maximum fraction of pixels allowed to have
+ * diff greater than or equal to mindiff
+ * \param[in] printstats use 1 to print normalized histogram to stderr
+ * \return 0 if OK, 1 on error a failure in similarity comparison
+ * is not an error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function compares two pix for near equality. On failure,
+ * this writes to stderr.
+ * (2) The pix are similar if the fraction of non-conforming pixels
+ * does not exceed %maxfract. Pixels are non-conforming if
+ * the difference in pixel values equals or exceeds %mindiff.
+ * Typical values might be %mindiff = 15 and %maxfract = 0.01.
+ * (3) The input images must have the same size and depth. The
+ * pixels for comparison are typically subsampled from the images.
+ * (4) Normally, use %printstats = 0. In debugging mode, to see
+ * the relation between %mindiff and the minimum value of
+ * %maxfract for success, set this to 1.
+ * </pre>
+ */
+l_ok
+regTestCompareSimilarPix(L_REGPARAMS *rp,
+ PIX *pix1,
+ PIX *pix2,
+ l_int32 mindiff,
+ l_float32 maxfract,
+ l_int32 printstats)
+{
+l_int32 w, h, factor, similar;
+
+ 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);
+ }
+ lept_stderr("Failure in %s_reg: pix similarity comp for index %d\n",
+ rp->testname, rp->index);
+ rp->success = FALSE;
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief regTestCheckFile()
+ *
+ * \param[in] rp regtest parameters
+ * \param[in] localname name of output file from reg test
+ * \return 0 if OK, 1 on error a failure in comparison is not an error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function does one of three things, depending on the mode:
+ * * "generate": makes a "golden" file as a copy of %localname.
+ * * "compare": compares %localname contents with the golden file
+ * * "display": this does nothing
+ * (2) The canonical format of the golden filenames is:
+ * /tmp/lept/golden/[root of main name]_golden.[index].
+ * [ext of localname]
+ * e.g.,
+ * /tmp/lept/golden/maze_golden.0.png
+ * (3) The local file can be made in any subdirectory of /tmp/lept,
+ * including /tmp/lept/regout/.
+ * (4) It is important to add an extension to the local name, such as
+ * /tmp/lept/maze/file1.png (extension ".png")
+ * because the extension is added to the name of the golden file.
+ * </pre>
+ */
+l_ok
+regTestCheckFile(L_REGPARAMS *rp,
+ const char *localname)
+{
+char *ext;
+char namebuf[256];
+l_int32 ret, same, format;
+PIX *pix1, *pix2;
+
+ 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. This can be used
+ * for all image formats, as well as for all files of serialized
+ * data, such as boxa, pta, etc. In all cases except for
+ * GIF compressed images, we compare the files to see if they
+ * are identical. GIF doesn't support RGB images; to write
+ * a 32 bpp RGB image in GIF, we do a lossy quantization to
+ * 256 colors, so the cycle read-RGB/write-GIF is not idempotent.
+ * And although the read/write cycle for GIF images with bpp <= 8
+ * is idempotent in the image pixels, it is not idempotent in the
+ * actual file bytes; tests comparing file bytes before and after
+ * a GIF read/write cycle will fail. So for GIF we uncompress
+ * the two images and compare the actual pixels. PNG is both
+ * lossless and idempotent in file bytes on read/write, so it is
+ * not necessary to compare pixels. (Comparing pixels requires
+ * decompression, and thus would increase the regression test
+ * time. JPEG is lossy and not idempotent in the image pixels,
+ * so no tests are constructed that would require it. */
+ findFileFormat(localname, &format);
+ if (format == IFF_GIF) {
+ same = 0;
+ pix1 = pixRead(localname);
+ pix2 = pixRead(namebuf);
+ pixEqual(pix1, pix2, &same);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ } else {
+ filesAreIdentical(localname, namebuf, &same);
+ }
+ if (!same) {
+ fprintf(rp->fp, "Failure in %s_reg, index %d: comparing %s with %s\n",
+ rp->testname, rp->index, localname, namebuf);
+ lept_stderr("Failure in %s_reg, index %d: comparing %s with %s\n",
+ rp->testname, rp->index, localname, namebuf);
+ rp->success = FALSE;
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief regTestCompareFiles()
+ *
+ * \param[in] rp regtest parameters
+ * \param[in] index1 of one output file from reg test
+ * \param[in] index2 of another output file from reg test
+ * \return 0 if OK, 1 on error a failure in comparison is not an error
+ *
+ * <pre>
+ * Notes:
+ * (1) This only does something in "compare" mode.
+ * (2) The canonical format of the golden filenames is:
+ * /tmp/lept/golden/[root of main name]_golden.[index].
+ * [ext of localname]
+ * e.g.,
+ * /tmp/lept/golden/maze_golden.0.png
+ * </pre>
+ */
+l_ok
+regTestCompareFiles(L_REGPARAMS *rp,
+ l_int32 index1,
+ l_int32 index2)
+{
+char *name1, *name2;
+char namebuf[256];
+l_int32 same;
+SARRAY *sa;
+
+ 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);
+ lept_stderr("Failure in %s_reg, index %d: comparing %s with %s\n",
+ rp->testname, rp->index, name1, name2);
+ rp->success = FALSE;
+ }
+
+ LEPT_FREE(name1);
+ LEPT_FREE(name2);
+ return 0;
+}
+
+
+/*!
+ * \brief regTestWritePixAndCheck()
+ *
+ * \param[in] rp regtest parameters
+ * \param[in] pix to be written
+ * \param[in] format of output pix
+ * \return 0 if OK, 1 on error a failure in comparison is not an error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function makes it easy to write the pix in a numbered
+ * sequence of files, and either to:
+ * (a) write the golden file ("generate" arg to regression test)
+ * (b) make a local file and "compare" with the golden file
+ * (c) make a local file and "display" the results
+ * (2) The canonical format of the local filename is:
+ * /tmp/lept/regout/[root of main name].[count].[format extension]
+ * e.g., for scale_reg,
+ * /tmp/lept/regout/scale.0.png
+ * The golden file name mirrors this in the usual way.
+ * (3) The check is done between the written files, which requires
+ * the files to be identical. The exception is for GIF, which
+ * only requires that all pixels in the decoded pix are identical.
+ * </pre>
+ */
+l_ok
+regTestWritePixAndCheck(L_REGPARAMS *rp,
+ PIX *pix,
+ l_int32 format)
+{
+char namebuf[256];
+
+ 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);
+ }
+
+ /* Use bmp format for testing if library for requested
+ * format for jpeg, png or tiff is not available */
+ changeFormatForMissingLib(&format);
+
+ /* Generate the local file name */
+ snprintf(namebuf, sizeof(namebuf), "/tmp/lept/regout/%s.%02d.%s",
+ rp->testname, rp->index + 1, ImageFileFormatExtensions[format]);
+
+ /* Write the local file */
+ if (pixGetDepth(pix) < 8)
+ pixSetPadBits(pix, 0);
+ pixWrite(namebuf, pix, format);
+
+ /* Either write the golden file ("generate") or check the
+ local file against an existing golden file ("compare") */
+ regTestCheckFile(rp, namebuf);
+
+ return 0;
+}
+
+
+/*!
+ * \brief regTestWriteDataAndCheck()
+ *
+ * \param[in] rp regtest parameters
+ * \param[in] data to be written
+ * \param[in] nbytes of data to be written
+ * \param[in] ext filename extension (e.g.: "ba", "pta")
+ * \return 0 if OK, 1 on error a failure in comparison is not an error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function makes it easy to write data in a numbered
+ * sequence of files, and either to:
+ * (a) write the golden file ("generate" arg to regression test)
+ * (b) make a local file and "compare" with the golden file
+ * (c) make a local file and "display" the results
+ * (2) The canonical format of the local filename is:
+ * /tmp/lept/regout/[root of main name].[count].[ext]
+ * e.g., for the first boxaa in quadtree_reg,
+ * /tmp/lept/regout/quadtree.0.baa
+ * The golden file name mirrors this in the usual way.
+ * (3) The data can be anything. It is most useful for serialized
+ * output of data, such as boxa, pta, etc.
+ * (4) The file extension is arbitrary. It is included simply
+ * to make the content type obvious when examining written files.
+ * (5) The check is done between the written files, which requires
+ * the files to be identical.
+ * </pre>
+ */
+l_ok
+regTestWriteDataAndCheck(L_REGPARAMS *rp,
+ void *data,
+ size_t nbytes,
+ const char *ext)
+{
+char namebuf[256];
+
+ PROCNAME("regTestWriteDataAndCheck");
+
+ if (!rp)
+ return ERROR_INT("rp not defined", procName, 1);
+ if (!data || nbytes == 0) {
+ rp->success = FALSE;
+ return ERROR_INT("data not defined or size == 0", procName, 1);
+ }
+
+ /* Generate the local file name */
+ snprintf(namebuf, sizeof(namebuf), "/tmp/lept/regout/%s.%02d.%s",
+ rp->testname, rp->index + 1, ext);
+
+ /* Write the local file */
+ l_binaryWrite(namebuf, "w", data, nbytes);
+
+ /* Either write the golden file ("generate") or check the
+ local file against an existing golden file ("compare") */
+ regTestCheckFile(rp, namebuf);
+ return 0;
+}
+
+
+/*!
+ * \brief regTestGenLocalFilename()
+ *
+ * \param[in] rp regtest parameters
+ * \param[in] index use -1 for current index
+ * \param[in] format of image; e.g., IFF_PNG
+ * \return filename if OK, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is used to get the name of a file in the regout
+ * subdirectory, that has been made and is used to test against
+ * the golden file. You can either specify a particular index
+ * value, or with %index == -1, this returns the most recently
+ * written file. The latter case lets you read a pix from a
+ * file that has just been written with regTestWritePixAndCheck(),
+ * which is useful for testing formatted read/write functions.
+ *
+ * </pre>
+ */
+char *
+regTestGenLocalFilename(L_REGPARAMS *rp,
+ l_int32 index,
+ l_int32 format)
+{
+char buf[64];
+l_int32 ind;
+
+ PROCNAME("regTestGenLocalFilename");
+
+ if (!rp)
+ return (char *)ERROR_PTR("rp not defined", procName, NULL);
+
+ ind = (index >= 0) ? index : rp->index;
+ snprintf(buf, sizeof(buf), "/tmp/lept/regout/%s.%02d.%s",
+ rp->testname, ind, ImageFileFormatExtensions[format]);
+ return stringNew(buf);
+}
+
+
+/*!
+ * \brief getRootNameFromArgv0()
+ *
+ * \param[in] argv0
+ * \return root name without the '_reg', or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For example, from psioseg_reg, we want to extract
+ * just 'psioseg' as the root.
+ * (2) In unix with autotools, the executable is not X,
+ * but ./.libs/lt-X. So in addition to stripping out the
+ * last 4 characters of the tail, we have to check for
+ * the '-' and strip out the "lt-" prefix if we find it.
+ * </pre>
+ */
+static char *
+getRootNameFromArgv0(const char *argv0)
+{
+l_int32 len;
+char *root;
+
+ 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);
+ }
+ len -= 4; /* remove the "_reg" suffix */
+ }
+#else
+ if (strstr(root, ".exe") != NULL)
+ len -= 4;
+ if (strstr(root, "_reg") == root + len - 4)
+ len -= 4;
+#endif /* ! _WIN32 */
+
+ root[len] = '\0'; /* terminate */
+ return root;
+}
diff --git a/leptonica/src/regutils.h b/leptonica/src/regutils.h
new file mode 100644
index 00000000..2f1d5e4a
--- /dev/null
+++ b/leptonica/src/regutils.h
@@ -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.
+ *====================================================================*/
+
+#ifndef LEPTONICA_REGUTILS_H
+#define LEPTONICA_REGUTILS_H
+
+/*!
+ * \file regutils.h
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+
+/*----------------------------------------------------------------------------*
+ * Regression test parameter packer *
+ *----------------------------------------------------------------------------*/
+
+/*! 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 */
+/*! Regtest Mode */
+enum {
+ L_REG_GENERATE = 0,
+ L_REG_COMPARE = 1,
+ L_REG_DISPLAY = 2
+};
+
+
+#endif /* LEPTONICA_REGUTILS_H */
+
diff --git a/leptonica/src/rop.c b/leptonica/src/rop.c
new file mode 100644
index 00000000..e7b8939f
--- /dev/null
+++ b/leptonica/src/rop.c
@@ -0,0 +1,572 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file rop.c
+ * <pre>
+ * 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()
+ *
+ * Checking for invalid crop box
+ * static l_int32 checkRasteropCrop()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static l_int32 checkRasteropCrop(l_int32 pixw, l_int32 pixh, l_int32 dx,
+ l_int32 dy, l_int32 dw, l_int32 dh);
+
+
+/*--------------------------------------------------------------------*
+ * General rasterop (basic pix interface) *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixRasterop()
+ *
+ * \param[in] pixd dest pix
+ * \param[in] dx x val of UL corner of dest rectangle
+ * \param[in] dy y val of UL corner of dest rectangle
+ * \param[in] dw width of dest rectangle
+ * \param[in] dh height of dest rectangle
+ * \param[in] op op code
+ * \param[in] pixs src pix
+ * \param[in] sx x val of UL corner of src rectangle
+ * \param[in] sy y val of UL corner of src rectangle
+ * \return 0 if OK; 1 on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 dpw, dph, dpd, spw, sph, spd;
+
+ PROCNAME("pixRasterop");
+
+ if (!pixd)
+ return ERROR_INT("pixd not defined", procName, 1);
+
+ if (op == PIX_DST) /* no-op */
+ return 0;
+
+ pixGetDimensions(pixd, &dpw, &dph, &dpd);
+#if 0
+ if (checkRasteropCrop(dpw, dph, dx, dy, dw, dh)) {
+ L_WARNING("dest crop box out of bounds\n", procName);
+ return 1;
+ }
+#endif
+
+ /* Check if operation is only on dest */
+ if (op == PIX_CLR || op == PIX_SET || op == PIX_NOT(PIX_DST)) {
+ rasteropUniLow(pixGetData(pixd), dpw, dph, dpd, pixGetWpl(pixd),
+ dx, dy, dw, dh, op);
+ return 0;
+ }
+
+ /* Two-image rasterop; the depths must match */
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+ pixGetDimensions(pixs, &spw, &sph, &spd);
+ if (dpd != spd)
+ return ERROR_INT("depths of pixs and pixd differ", procName, 1);
+#if 0
+ if (checkRasteropCrop(spw, sph, sx, sy, dw, dh)) {
+ L_WARNING("source crop box out of bounds\n", procName);
+ return 1;
+ }
+#endif
+
+ rasteropLow(pixGetData(pixd), dpw, dph, dpd, pixGetWpl(pixd),
+ dx, dy, dw, dh, op,
+ pixGetData(pixs), spw, sph, pixGetWpl(pixs), sx, sy);
+ return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ * In-place full band translation *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixRasteropVip()
+ *
+ * \param[in] pixd in-place
+ * \param[in] bx left edge of vertical band
+ * \param[in] bw width of vertical band
+ * \param[in] vshift vertical shift of band; vshift > 0 is down
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixRasteropHip()
+ *
+ * \param[in] pixd in-place operation
+ * \param[in] by top of horizontal band
+ * \param[in] bh height of horizontal band
+ * \param[in] hshift horizontal shift of band; hshift > 0 is to right
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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) *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixTranslate()
+ *
+ * \param[in] pixd [optional] destination: this can be null,
+ * equal to pixs, or different from pixs
+ * \param[in] pixs
+ * \param[in] hshift horizontal shift; hshift > 0 is to right
+ * \param[in] vshift vertical shift; vshift > 0 is down
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixRasteropIP()
+ *
+ * \param[in] pixd in-place translation
+ * \param[in] hshift horizontal shift; hshift > 0 is to right
+ * \param[in] vshift vertical shift; vshift > 0 is down
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief pixRasteropFullImage()
+ *
+ * \param[in] pixd
+ * \param[in] pixs
+ * \param[in] op any of the op-codes
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Checking for invalid crop box *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief checkRasteropCrop()
+ *
+ * \param[in] pixw, pixh pix dimensions
+ * \param[in] x, y, w, h crop box parameters
+ * \return 0 if OK, 1 if the crop box does not intersect with the pix.
+ *
+ * <pre>
+ * Notes:
+ * (1) The widths and heights must all be positive, but %x and %y
+ * can take on any value.
+ * (2) This works for checking both the source and dest regions.
+ * (3) This has been used to verify rasteropLow() cropping is correct.
+ * It is not needed for pre-filtering in pixRasterop().
+ * </pre>
+ */
+static l_int32
+checkRasteropCrop(l_int32 pixw,
+ l_int32 pixh,
+ l_int32 x,
+ l_int32 y,
+ l_int32 w,
+ l_int32 h)
+{
+ PROCNAME("checkRasteropCrop");
+
+ if (pixw < 1 || pixh < 1 || w < 1 || h < 1)
+ return ERROR_INT("dimension is <= 0", procName, 1);
+
+ if (x + w <= 0 || y + h <= 0)
+ return ERROR_INT("box to left or above pix", procName, 1);
+
+ if (x >= pixw || y >= pixh)
+ return ERROR_INT("box to right or below pix", procName, 1);
+
+ return 0;
+}
diff --git a/leptonica/src/roplow.c b/leptonica/src/roplow.c
new file mode 100644
index 00000000..8463e785
--- /dev/null
+++ b/leptonica/src/roplow.c
@@ -0,0 +1,2506 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file roplow.c
+ * <pre>
+ * 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()
+ *
+ * Low level in-place full height vertical block transfer
+ * void rasteropVipLow()
+ *
+ * Low level in-place full width horizontal block transfer
+ * void rasteropHipLow()
+ * static void shiftDataHorizontalLow()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Static helpers */
+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 void shiftDataHorizontalLow(l_uint32 *datad, l_int32 wpld,
+ l_uint32 *datas, l_int32 wpls,
+ l_int32 shift);
+
+#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 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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief rasteropUniLow()
+ *
+ * \param[in] datad ptr to dest image data
+ * \param[in] dpixw width of dest
+ * \param[in] dpixh height of dest
+ * \param[in] depth depth of src and dest
+ * \param[in] dwpl wpl of dest
+ * \param[in] dx x val of UL corner of dest rectangle
+ * \param[in] dy y val of UL corner of dest rectangle
+ * \param[in] dw width of dest rectangle
+ * \param[in] dh height of dest rectangle
+ * \param[in] 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);
+}
+
+
+
+/*--------------------------------------------------------------------*
+ * Static low-level uni rasterop with word alignment *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief rasteropUniWordAlignedLow()
+ *
+ * \param[in] datad ptr to dest image data
+ * \param[in] dwpl wpl of dest
+ * \param[in] dx x val of UL corner of dest rectangle
+ * \param[in] dy y val of UL corner of dest rectangle
+ * \param[in] dw width of dest rectangle
+ * \param[in] dh height of dest rectangle
+ * \param[in] 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:
+ lept_stderr("Operation %d not permitted here!\n", op);
+ }
+}
+
+
+/*--------------------------------------------------------------------*
+ * Static low-level uni rasterop without word alignment *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief rasteropUniGeneralLow()
+ *
+ * \param[in] datad ptr to dest image data
+ * \param[in] dwpl wpl of dest
+ * \param[in] dx x val of UL corner of dest rectangle
+ * \param[in] dy y val of UL corner of dest rectangle
+ * \param[in] dw width of dest rectangle
+ * \param[in] dh height of dest rectangle
+ * \param[in] 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? */
+ dfwmask = 0;
+ 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:
+ lept_stderr("Operation %d not permitted here!\n", op);
+ }
+}
+
+
+/*--------------------------------------------------------------------*
+ * Low-level src and dest rasterops *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief rasteropLow()
+ *
+ * \param[in] datad ptr to dest image data
+ * \param[in] dpixw width of dest
+ * \param[in] dpixh height of dest
+ * \param[in] depth depth of src and dest
+ * \param[in] dwpl wpl of dest
+ * \param[in] dx x val of UL corner of dest rectangle
+ * \param[in] dy y val of UL corner of dest rectangle
+ * \param[in] dw width of dest rectangle
+ * \param[in] dh height of dest rectangle
+ * \param[in] op op code
+ * \param[in] datas ptr to src image data
+ * \param[in] spixw width of src
+ * \param[in] spixh height of src
+ * \param[in] swpl wpl of src
+ * \param[in] sx x val of UL corner of src rectangle
+ * \param[in] 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 *
+ * -------------------------------------------------------*/
+ /* 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 */
+
+ /* 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;
+
+#if 0
+ lept_stderr("dx = %d, dy = %d, dw = %d, dh = %d, sx = %d, sy = %d\n",
+ dx, dy, dw, dh, sx, sy);
+#endif
+
+ /* -------------------------------------------------------*
+ * 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);
+}
+
+
+/*--------------------------------------------------------------------*
+ * Static low-level rasterop with vertical word alignment *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief rasteropWordAlignedLow()
+ *
+ * \param[in] datad ptr to dest image data
+ * \param[in] dwpl wpl of dest
+ * \param[in] dx x val of UL corner of dest rectangle
+ * \param[in] dy y val of UL corner of dest rectangle
+ * \param[in] dw width of dest rectangle
+ * \param[in] dh height of dest rectangle
+ * \param[in] op op code
+ * \param[in] datas ptr to src image data
+ * \param[in] swpl wpl of src
+ * \param[in] sx x val of UL corner of src rectangle
+ * \param[in] 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:
+ lept_stderr("Operation %d invalid\n", op);
+ }
+}
+
+
+
+/*--------------------------------------------------------------------*
+ * Static low-level rasterop with vertical word alignment *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief rasteropVAlignedLow()
+ *
+ * \param[in] datad ptr to dest image data
+ * \param[in] dwpl wpl of dest
+ * \param[in] dx x val of UL corner of dest rectangle
+ * \param[in] dy y val of UL corner of dest rectangle
+ * \param[in] dw width of dest rectangle
+ * \param[in] dh height of dest rectangle
+ * \param[in] op op code
+ * \param[in] datas ptr to src image data
+ * \param[in] swpl wpl of src
+ * \param[in] sx x val of UL corner of src rectangle
+ * \param[in] 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? */
+ dfwmask = 0;
+ 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:
+ lept_stderr("Operation %x invalid\n", op);
+ }
+}
+
+
+/*--------------------------------------------------------------------*
+ * Static low-level rasterop without vertical word alignment *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief rasteropGeneralLow()
+ *
+ * \param[in] datad ptr to dest image data
+ * \param[in] dwpl wpl of dest
+ * \param[in] dx x val of UL corner of dest rectangle
+ * \param[in] dy y val of UL corner of dest rectangle
+ * \param[in] dw width of dest rectangle
+ * \param[in] dh height of dest rectangle
+ * \param[in] op op code
+ * \param[in] datas ptr to src image data
+ * \param[in] swpl wpl of src
+ * \param[in] sx x val of UL corner of src rectangle
+ * \param[in] 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, and 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 overhang,
+ * the src is shifted to the left, and depending on the width of
+ * transferred pixels, it may also be necessary to shift pixels
+ * in from the next src word, in order to fill the dest word.
+ * An interesting case is where the src overhang equals the width,
+ * dw, of the block. Then all the pixels necessary to fill the first
+ * dest word can be taken from the first src word, up to the last
+ * src pixel in the word, and no pixels from the next src word are
+ * required. Consider this simple example, where a single pixel from
+ * the src is transferred to the dest:
+ * pix1 = pixCreate(32, 1, 1);
+ * pix2 = pixCreate(32, 1, 1);
+ * pixRasterop(pix1, 30, 0, 1, 1, PIX_SRC, pix2, 31, 0);
+ * Here, the pixel at the right end of the src image (sx = 31)
+ * is shifted one bit to the left (to dx = 30). The width (1) equals
+ * the src overhang (1), and no pixels from the next word are required.
+ * (This must be true because there is only one 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 0
+ lept_stderr("shang = %d, dhang = %d\n", shang, dhang);
+#endif
+
+ 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];
+ }
+
+#if 0
+ lept_stderr("sleftshift = %d, srightshift = %d\n", sleftshift, srightshift);
+#endif
+
+ /* Is the first dest word partial? */
+ dfwmask = 0;
+ 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; /* shift by sleftshift */
+ /* Do we have enough bits from the current src word? */
+ if (dw <= shang)
+ sfwaddb = 0; /* yes: we have enough bits */
+ else
+ sfwaddb = 1; /* no: rshift in next src word by srightshift */
+ } else {
+ sfwshiftdir = SHIFT_RIGHT; /* 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:
+ lept_stderr("Operation %x invalid\n", op);
+ }
+}
+
+
+/*--------------------------------------------------------------------*
+ * Low level in-place full height vertical block transfer *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief rasteropVipLow()
+ *
+ * \param[in] data ptr to image data
+ * \param[in] pixw width
+ * \param[in] pixh height
+ * \param[in] depth depth
+ * \param[in] wpl wpl
+ * \param[in] x x val of UL corner of rectangle
+ * \param[in] w width of rectangle
+ * \param[in] shift + shifts data downward in vertical column
+ * \return 0 if OK; 1 on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+ }
+ }
+}
+
+
+
+/*--------------------------------------------------------------------*
+ * Low level in-place full width horizontal block transfer *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief rasteropHipLow()
+ *
+ * \param[in] data ptr to image data
+ * \param[in] pixh height
+ * \param[in] depth depth
+ * \param[in] wpl wpl
+ * \param[in] y y val of UL corner of rectangle
+ * \param[in] h height of rectangle
+ * \param[in] shift + shifts data to the left in a horizontal column
+ * \return 0 if OK; 1 on error.
+ *
+ * <pre>
+ * 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 shiftDataHorizontalLow()
+ * to do the in-place rasterop on each line.
+ * </pre>
+ */
+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);
+ }
+}
+
+
+/*!
+ * \brief shiftDataHorizontalLow()
+ *
+ * \param[in] datad ptr to beginning of dest line
+ * \param[in] wpld wpl of dest
+ * \param[in] datas ptr to beginning of src line
+ * \param[in] wpls wpl of src
+ * \param[in] shift horizontal shift of block; >0 is to right
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static 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;
+ }
+ }
+}
diff --git a/leptonica/src/rotate.c b/leptonica/src/rotate.c
new file mode 100644
index 00000000..27ea9b67
--- /dev/null
+++ b/leptonica/src/rotate.c
@@ -0,0 +1,598 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file rotate.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+extern l_float32 AlphaMaskBorderVals[2];
+static const l_float32 MinAngleToRotate = 0.001f; /* radians; ~0.06 deg */
+static const l_float32 Max1BppShearAngle = 0.06f; /* radians; ~3 deg */
+static const l_float32 LimitShearAngle = 0.35f; /* radians; ~20 deg */
+
+/*------------------------------------------------------------------*
+ * General rotation about the center *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotate()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 32 bpp rgb
+ * \param[in] angle radians; clockwise is positive
+ * \param[in] type L_ROTATE_AREA_MAP, L_ROTATE_SHEAR, L_ROTATE_SAMPLING
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \param[in] width original width; use 0 to avoid embedding
+ * \param[in] height original height; use 0 to avoid embedding
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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 *pix1, *pix2, *pix3, *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) < MinAngleToRotate)
+ 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) > Max1BppShearAngle) {
+ 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) > LimitShearAngle && 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)
+ pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+ else
+ pix1 = pixClone(pixs);
+ cmap = pixGetColormap(pix1);
+
+ /* 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 */
+ pix2 = pixEmbedForRotation(pix1, 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(pix2);
+ if (type == L_ROTATE_AREA_MAP && d < 8)
+ pix3 = pixConvertTo8(pix2, FALSE);
+ else
+ pix3 = pixClone(pix2);
+
+ /* Do the rotation: shear, sampling or area mapping */
+ pixGetDimensions(pix3, &w, &h, &d);
+ if (type == L_ROTATE_SHEAR) {
+ pixd = pixRotateShearCenter(pix3, angle, incolor);
+ } else if (type == L_ROTATE_SAMPLING) {
+ pixd = pixRotateBySampling(pix3, 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(pix3, angle, fillval);
+ else /* d == 32 */
+ pixd = pixRotateAMColor(pix3, angle, fillval);
+ }
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixEmbedForRotation()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 32 bpp rgb
+ * \param[in] angle radians; clockwise is positive
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \param[in] width original width; use 0 to avoid embedding
+ * \param[in] height original height; use 0 to avoid embedding
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) < MinAngleToRotate)
+ 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotateBySampling()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp rgb; can be cmapped
+ * \param[in] xcen x value of center of rotation
+ * \param[in] ycen y value of center of rotation
+ * \param[in] angle radians; clockwise is positive
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) < MinAngleToRotate)
+ 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotateBinaryNice()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] angle radians; clockwise is positive; about the center
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixRotateBinaryNice(PIX *pixs,
+ l_float32 angle,
+ l_int32 incolor)
+{
+PIX *pix1, *pix2, *pix3, *pix4, *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);
+
+ pix1 = pixConvertTo8(pixs, 0);
+ pix2 = pixBlockconv(pix1, 1, 1); /* smallest blur allowed */
+ pix3 = pixRotateAM(pix2, angle, incolor);
+ pix4 = pixUnsharpMasking(pix3, 1, 1.0); /* sharpen a bit */
+ pixd = pixThresholdToBinary(pix4, 128);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Rotation including alpha (blend) component *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotateWithAlpha()
+ *
+ * \param[in] pixs 32 bpp rgb or cmapped
+ * \param[in] angle radians; clockwise is positive
+ * \param[in] pixg [optional] 8 bpp, can be null
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/rotateam.c b/leptonica/src/rotateam.c
new file mode 100644
index 00000000..f14255f6
--- /dev/null
+++ b/leptonica/src/rotateam.c
@@ -0,0 +1,1132 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file rotateam.c
+ * <pre>
+ *
+ * Grayscale and color rotation for area mapping (== interpolation)
+ *
+ * Rotation about the image center
+ * PIX *pixRotateAM()
+ * PIX *pixRotateAMColor()
+ * PIX *pixRotateAMGray()
+ * static void rotateAMColorLow()
+ * static void rotateAMGrayLow()
+ *
+ * Rotation about the UL corner of the image
+ * PIX *pixRotateAMCorner()
+ * PIX *pixRotateAMColorCorner()
+ * PIX *pixRotateAMGrayCorner()
+ * static void rotateAMColorCornerLow()
+ * static void rotateAMGrayCornerLow()
+ *
+ * Faster color rotation about the image center
+ * PIX *pixRotateAMColorFast()
+ * static void rotateAMColorFastLow()
+ *
+ * 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.
+ *
+ * Consider again the comparison of image quality between sampling
+ * and area mapping. With sampling, sharp edges such as found in
+ * text images remain sharp. However, sampling artifacts such as
+ * characters randomly bouncing up and down by one pixel, or
+ * one pixel horizontal shear lines going through a line of text
+ * (causing the characters to look like badly rendered italic),
+ * are highly visible. It does not help to sample the source pixel
+ * with the largest area covering each dest pixel; the result has
+ * the same ugly sampling artifacts.
+ *
+ * With area mapping, these annoying artifacts are avoided, but the
+ * blurring of edges makes small text a bit more difficult to read.
+ * However, if you are willing to do more computation, you can have
+ * the best of both worlds: no sampling artifacts and sharp edges.
+ * Use area mapping to avoid sampling issues, and follow it with
+ * unsharp masking. Experiment with the sharpening parameters.
+ * I have found that a small amount of sharpening is sufficient to
+ * restore the sharp edges in text; e.g.,
+ * pix2 = pixUnsharpMasking(pix1, 1, 0.3);
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h> /* required for sin and tan */
+#include "allheaders.h"
+
+static 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);
+static 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);
+static 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);
+static 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);
+
+static 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);
+
+static const l_float32 MinAngleToRotate = 0.001f; /* radians; ~0.06 deg */
+
+
+/*------------------------------------------------------------------*
+ * Rotation about the center *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotateAM()
+ *
+ * \param[in] pixs 2, 4, 8 bpp gray or colormapped, or 32 bpp RGB
+ * \param[in] angle radians; clockwise is positive
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) < MinAngleToRotate)
+ 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;
+}
+
+
+/*!
+ * \brief pixRotateAMColor()
+ *
+ * \param[in] pixs 32 bpp
+ * \param[in] angle radians; clockwise is positive
+ * \param[in] colorval e.g., 0 to bring in BLACK, 0xffffff00 for WHITE
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) < MinAngleToRotate)
+ 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;
+}
+
+
+/*!
+ * \brief pixRotateAMGray()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] angle radians; clockwise is positive
+ * \param[in] grayval 0 to bring in BLACK, 255 for WHITE
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) < MinAngleToRotate)
+ 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;
+}
+
+
+static 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);
+ }
+ }
+}
+
+
+static 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);
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------*
+ * Rotation about the UL corner *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotateAMCorner()
+ *
+ * \param[in] pixs 1, 2, 4, 8 bpp gray or colormapped, or 32 bpp RGB
+ * \param[in] angle radians; clockwise is positive
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) < MinAngleToRotate)
+ 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;
+}
+
+
+/*!
+ * \brief pixRotateAMColorCorner()
+ *
+ * \param[in] pixs
+ * \param[in] angle radians; clockwise is positive
+ * \param[in] fillval e.g., 0 to bring in BLACK, 0xffffff00 for WHITE
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) < MinAngleToRotate)
+ 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;
+}
+
+
+/*!
+ * \brief pixRotateAMGrayCorner()
+ *
+ * \param[in] pixs
+ * \param[in] angle radians; clockwise is positive
+ * \param[in] grayval 0 to bring in BLACK, 255 for WHITE
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) < MinAngleToRotate)
+ 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;
+}
+
+
+static 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);
+ }
+ }
+}
+
+
+static 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);
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------*
+ * Fast RGB color rotation about center *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotateAMColorFast()
+ *
+ * \param[in] pixs
+ * \param[in] angle radians; clockwise is positive
+ * \param[in] colorval e.g., 0 to bring in BLACK, 0xffffff00 for WHITE
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) < MinAngleToRotate)
+ 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;
+}
+
+
+/*!
+ * \brief 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.
+ */
+static 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:
+ lept_stderr("shouldn't get here\n");
+ break;
+ }
+ }
+ }
+}
diff --git a/leptonica/src/rotateorth.c b/leptonica/src/rotateorth.c
new file mode 100644
index 00000000..2c83d1e0
--- /dev/null
+++ b/leptonica/src/rotateorth.c
@@ -0,0 +1,715 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file rotateorth.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotateOrth()
+ *
+ * \param[in] pixs all depths
+ * \param[in] 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotate180()
+ *
+ * \param[in] pixd [optional]; can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs all depths
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotate90()
+ *
+ * \param[in] pixs all depths
+ * \param[in] direction clockwise = 1, counterclockwise = -1
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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);
+ pixCopySpp(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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixFlipLR()
+ *
+ * \param[in] pixd [optional]; can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs all depths
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * }
+ * }
+ * </pre>
+ */
+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) {
+ if (tab) LEPT_FREE(tab);
+ 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixFlipTB()
+ *
+ * \param[in] pixd [optional]; can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs all depths
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief 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;
+
+ tab = (l_uint8 *)LEPT_CALLOC(256, sizeof(l_uint8));
+ 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;
+}
+
+
+/*!
+ * \brief 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;
+
+ tab = (l_uint8 *)LEPT_CALLOC(256, sizeof(l_uint8));
+ for (i = 0; i < 256; i++)
+ tab[i] = ((0xc0 & i) >> 6) |
+ ((0x30 & i) >> 2) |
+ ((0x0c & i) << 2) |
+ ((0x03 & i) << 6);
+ return tab;
+}
+
+
+/*!
+ * \brief 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;
+
+ tab = (l_uint8 *)LEPT_CALLOC(256, sizeof(l_uint8));
+ for (i = 0; i < 256; i++)
+ tab[i] = ((0xf0 & i) >> 4) | ((0x0f & i) << 4);
+ return tab;
+}
diff --git a/leptonica/src/rotateshear.c b/leptonica/src/rotateshear.c
new file mode 100644
index 00000000..5ba74b3d
--- /dev/null
+++ b/leptonica/src/rotateshear.c
@@ -0,0 +1,498 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file rotateshear.c
+ * <pre>
+ *
+ * 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.
+ * When not in-place, the new pix is initialized with white or black
+ * pixels by pixSetBlackOrWhite(), which also works for cmapped pix.
+ * But for in-place, this initialization is not possible, so
+ * in-place shear operations on cmapped pix are not allowed.
+ *
+ * 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!
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include <string.h>
+#include "allheaders.h"
+
+ /* Angle limits:
+ * angle < MinAngleToRotate ==> clone
+ * angle > MaxTwoShearAngle ==> warning for 2-angle shears
+ * angle > MaxThreeShearAngle ==> warning for 3-angle shears
+ * angle > MaxShearAngle ==> error
+ */
+static const l_float32 MinAngleToRotate = 0.001f; /* radians; ~0.06 deg */
+static const l_float32 MaxTwoShearAngle = 0.06f; /* radians; ~3 deg */
+static const l_float32 MaxThreeShearAngle = 0.35f; /* radians; ~20 deg */
+static const l_float32 MaxShearAngle = 0.50f; /* radians; ~29 deg */
+
+/*------------------------------------------------------------------*
+ * Rotations about an arbitrary point *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotateShear()
+ *
+ * \param[in] pixs any depth; cmap ok
+ * \param[in] xcen x value for which there is no horizontal shear
+ * \param[in] ycen y value for which there is no vertical shear
+ * \param[in] angle radians
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return pixd, or NULL on error.
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+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) > MaxShearAngle) {
+ L_ERROR("%6.2f radians; too large for shear rotation\n", procName,
+ L_ABS(angle));
+ return NULL;
+ }
+ if (L_ABS(angle) < MinAngleToRotate)
+ return pixClone(pixs);
+
+ if (L_ABS(angle) <= MaxTwoShearAngle)
+ return pixRotate2Shear(pixs, xcen, ycen, angle, incolor);
+ else
+ return pixRotate3Shear(pixs, xcen, ycen, angle, incolor);
+}
+
+
+/*!
+ * \brief pixRotate2Shear()
+ *
+ * \param[in] pixs any depth; cmap ok
+ * \param[in] xcen, ycen center of rotation
+ * \param[in] angle radians
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return pixd, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This rotates the image about the given point, using the 2-shear
+ * method. It should only be used for angles no larger than
+ * MaxTwoShearAngle. 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.
+ * </pre>
+ */
+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) > MaxShearAngle) {
+ L_ERROR("%6.2f radians; too large for shear rotation\n", procName,
+ L_ABS(angle));
+ return NULL;
+ }
+ if (L_ABS(angle) < MinAngleToRotate)
+ return pixClone(pixs);
+ if (L_ABS(angle) > MaxTwoShearAngle)
+ 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);
+ pixd = pixVShear(NULL, pix1, xcen, angle, incolor);
+ pixDestroy(&pix1);
+ if (!pixd)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+ 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;
+}
+
+
+/*!
+ * \brief pixRotate3Shear()
+ *
+ * \param[in] pixs any depth; cmap ok
+ * \param[in] xcen, ycen center of rotation
+ * \param[in] angle radians
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return pixd, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This rotates the image about the given point, using the 3-shear
+ * method. It should only be used for angles smaller than
+ * MaxThreeShearAngle. 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.
+ * </pre>
+ */
+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) > MaxShearAngle) {
+ L_ERROR("%6.2f radians; too large for shear rotation\n", procName,
+ L_ABS(angle));
+ return NULL;
+ }
+ if (L_ABS(angle) < MinAngleToRotate)
+ return pixClone(pixs);
+ if (L_ABS(angle) > MaxThreeShearAngle) {
+ 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) {
+ pixDestroy(&pixd);
+ 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotateShearIP()
+ *
+ * \param[in] pixs any depth; no cmap
+ * \param[in] xcen, ycen center of rotation
+ * \param[in] angle radians
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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 MaxThreeShearAngle.
+ * 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.
+ * </pre>
+ */
+l_ok
+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) > MaxThreeShearAngle) {
+ 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixRotateShearCenter()
+ *
+ * \param[in] pixs any depth; cmap ok
+ * \param[in] angle radians
+ * \param[in] 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);
+}
+
+
+/*!
+ * \brief pixRotateShearCenterIP()
+ *
+ * \param[in] pixs any depth; no cmap
+ * \param[in] angle radians
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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/leptonica/src/runlength.c b/leptonica/src/runlength.c
new file mode 100644
index 00000000..8f947a13
--- /dev/null
+++ b/leptonica/src/runlength.c
@@ -0,0 +1,814 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file runlength.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixStrokeWidthTransform()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] color 0 for white runs, 1 for black runs
+ * \param[in] depth of pixd: 8 or 16 bpp
+ * \param[in] nangles 2, 4, 6 or 8
+ * \return pixd 8 or 16 bpp, or NULL on error
+ *
+ * <pre>
+ * 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) %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).
+ * </pre>
+ */
+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.1415926535f;
+ 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;
+}
+
+
+/*!
+ * \brief pixFindMinRunsOrthogonal()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] angle in radians
+ * \param[in] depth of pixd: 8 or 16 bpp
+ * \return pixd 8 or 16 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixRunlengthTransform()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] color 0 for white runs, 1 for black runs
+ * \param[in] direction L_HORIZONTAL_RUNS, L_VERTICAL_RUNS
+ * \param[in] depth 8 or 16 bpp
+ * \return pixd 8 or 16 bpp, or NULL on error
+ *
+ * <pre>
+ * 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) %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().
+ * </pre>
+ */
+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 (bufsize > 1000000) {
+ L_ERROR("largest image dimension = %d; too big\n", procName, bufsize);
+ return NULL;
+ }
+
+ if ((pixd = pixCreate(w, h, depth)) == NULL)
+ return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+ datad = pixGetData(pixd);
+ wpld = pixGetWpl(pixd);
+
+ start = (l_int32 *)LEPT_CALLOC(maxsize, sizeof(l_int32));
+ end = (l_int32 *)LEPT_CALLOC(maxsize, sizeof(l_int32));
+ buffer = (l_int32 *)LEPT_CALLOC(bufsize, sizeof(l_int32));
+
+ /* 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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixFindHorizontalRuns()
+ *
+ * \param[in] pix 1 bpp
+ * \param[in] y line to traverse
+ * \param[in] xstart returns array of start positions for fg runs
+ * \param[in] xend returns array of end positions for fg runs
+ * \param[out] pn the number of runs found
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds foreground horizontal runs on a single scanline.
+ * (2) To find background runs, use pixInvert() before applying
+ * this function.
+ * (3) %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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixFindVerticalRuns()
+ *
+ * \param[in] pix 1 bpp
+ * \param[in] x line to traverse
+ * \param[in] ystart returns array of start positions for fg runs
+ * \param[in] yend returns array of end positions for fg runs
+ * \param[out] pn the number of runs found
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds foreground vertical runs on a single scanline.
+ * (2) To find background runs, use pixInvert() before applying
+ * this function.
+ * (3) %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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixFindMaxRuns()
+ *
+ * \param[in] pix 1 bpp
+ * \param[in] direction L_HORIZONTAL_RUNS or L_VERTICAL_RUNS
+ * \param[out] pnastart [optional] start locations of longest runs
+ * \return na of lengths of runs, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds the longest foreground runs by row or column
+ * (2) To find background runs, use pixInvert() before applying
+ * this function.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFindMaxHorizontalRunOnLine()
+ *
+ * \param[in] pix 1 bpp
+ * \param[in] y line to traverse
+ * \param[out] pxstart [optional] start position
+ * \param[out] psize the size of the run
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds the longest foreground horizontal run on a scanline.
+ * (2) To find background runs, use pixInvert() before applying
+ * this function.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixFindMaxVerticalRunOnLine()
+ *
+ * \param[in] pix 1 bpp
+ * \param[in] x column to traverse
+ * \param[out] pystart [optional] start position
+ * \param[out] psize the size of the run
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds the longest foreground vertical run on a scanline.
+ * (2) To find background runs, use pixInvert() before applying
+ * this function.
+ * </pre>
+ */
+l_ok
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief runlengthMembershipOnLine()
+ *
+ * \param[in] buffer into which full line of data is placed
+ * \param[in] size full size of line; w or h
+ * \param[in] depth 8 or 16 bpp
+ * \param[in] start array of start positions for fg runs
+ * \param[in] end array of end positions for fg runs
+ * \param[in] n the number of runs
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief makeMSBitLocTab()
+ *
+ * \param[in] bitval either 0 or 1
+ * \return table: for an input byte, the MS bit location, starting at 0
+ * with the MSBit in the byte, or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_int32 *
+makeMSBitLocTab(l_int32 bitval)
+{
+l_int32 i, j;
+l_int32 *tab;
+l_uint8 byte, mask;
+
+ tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+ 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/leptonica/src/sarray1.c b/leptonica/src/sarray1.c
new file mode 100644
index 00000000..322f8d34
--- /dev/null
+++ b/leptonica/src/sarray1.c
@@ -0,0 +1,2070 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file sarray1.c
+ * <pre>
+ *
+ * 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()
+ *
+ * Concatenate strings uniformly within the sarray
+ * SARRAY *sarrayConcatUniformly()
+ *
+ * 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()
+ *
+ * Serialize for I/O
+ * SARRAY *sarrayRead()
+ * SARRAY *sarrayReadStream()
+ * SARRAY *sarrayReadMem()
+ * l_int32 sarrayWrite()
+ * l_int32 sarrayWriteStream()
+ * l_int32 sarrayWriteMem()
+ * 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. In the following,
+ * "str_not_owned" means the returned handle does not own the string,
+ * and "str_owned" means the returned handle owns the string.
+ * - To extract a string from an Sarray in order to inspect it
+ * or to make a copy of it later, get a handle to it:
+ * copyflag = L_NOCOPY.
+ * In this case, you must neither free the string nor put it
+ * directly in another array:
+ * str-not-owned = sarrayGetString(sa, index, L_NOCOPY);
+ * - To extract a copy of a string from an Sarray, use:
+ * str-owned = sarrayGetString(sa, index, L_COPY);
+ * ~ To insert a string that is in one array into another
+ * array (always leaving the first array intact), there are
+ * two options:
+ * (1) use copyflag = L_COPY to make an immediate copy,
+ * which you then add to the second array by insertion:
+ * 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; you then 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).
+ * sarrayAddString() transfers ownership to the Sarray, so never
+ * use L_INSERT if the string is owned by another array.
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#ifndef _WIN32
+#include <dirent.h> /* unix only */
+#include <sys/stat.h>
+#include <limits.h> /* needed for realpath() */
+#include <stdlib.h> /* needed for realpath() */
+#endif /* ! _WIN32 */
+#include "allheaders.h"
+
+static const l_uint32 MaxPtrArraySize = 25000000; /* 25 million */
+static const l_int32 InitialPtrArraySize = 50; /*!< n'importe quoi */
+
+ /* Static functions */
+static l_int32 sarrayExtendArray(SARRAY *sa);
+
+
+/*--------------------------------------------------------------------------*
+ * String array create/destroy/copy/extend *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief sarrayCreate()
+ *
+ * \param[in] n 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 > MaxPtrArraySize)
+ n = InitialPtrArraySize;
+
+ sa = (SARRAY *)LEPT_CALLOC(1, sizeof(SARRAY));
+ if ((sa->array = (char **)LEPT_CALLOC(n, sizeof(char *))) == NULL) {
+ sarrayDestroy(&sa);
+ return (SARRAY *)ERROR_PTR("ptr array not made", procName, NULL);
+ }
+
+ sa->nalloc = n;
+ sa->n = 0;
+ sa->refcount = 1;
+ return sa;
+}
+
+
+/*!
+ * \brief sarrayCreateInitialized()
+ *
+ * \param[in] n size of string ptr array to be alloc'd
+ * \param[in] initstr string to be initialized on the full array
+ * \return sarray, or NULL on error
+ */
+SARRAY *
+sarrayCreateInitialized(l_int32 n,
+ const 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;
+}
+
+
+/*!
+ * \brief sarrayCreateWordsFromString()
+ *
+ * \param[in] string
+ * \return sarray, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds the number of word substrings, creates an sarray
+ * of this size, and puts copies of each substring into the sarray.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sarrayCreateLinesFromString()
+ *
+ * \param[in] string
+ * \param[in] blankflag 0 to exclude blank lines; 1 to include
+ * \return sarray, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ sarrayDestroy(&sa);
+ 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) {
+ sarrayDestroy(&sa);
+ LEPT_FREE(cstring);
+ return (SARRAY *)ERROR_PTR("substring not made",
+ procName, NULL);
+ }
+ sarrayAddString(sa, substring, L_INSERT);
+/* lept_stderr("substring = %s\n", substring); */
+ startptr = i + 1;
+ }
+ }
+ if (startptr < size) { /* no newline at end of last line */
+ if ((substring = stringNew(cstring + startptr)) == NULL) {
+ sarrayDestroy(&sa);
+ LEPT_FREE(cstring);
+ return (SARRAY *)ERROR_PTR("substring not made",
+ procName, NULL);
+ }
+ sarrayAddString(sa, substring, L_INSERT);
+/* lept_stderr("substring = %s\n", substring); */
+ }
+ LEPT_FREE(cstring);
+ } else { /* remove blank lines; use strtok */
+ sarraySplitString(sa, string, "\r\n");
+ }
+
+ return sa;
+}
+
+
+/*!
+ * \brief sarrayDestroy()
+ *
+ * \param[in,out] psa will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) Decrements the ref count and, if 0, destroys the sarray.
+ * (2) Always nulls the input ptr.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sarrayCopy()
+ *
+ * \param[in] sa string array
+ * \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;
+}
+
+
+/*!
+ * \brief sarrayClone()
+ *
+ * \param[in] sa string array
+ * \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;
+}
+
+
+/*!
+ * \brief sarrayAddString()
+ *
+ * \param[in] sa string array
+ * \param[in] string string to be added
+ * \param[in] copyflag L_INSERT, L_NOCOPY or L_COPY
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See usage comments at the top of this file. L_INSERT is
+ * equivalent to L_NOCOPY.
+ * </pre>
+ */
+l_ok
+sarrayAddString(SARRAY *sa,
+ const 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_NOCOPY && copyflag != L_COPY)
+ return ERROR_INT("invalid copyflag", procName, 1);
+
+ n = sarrayGetCount(sa);
+ if (n >= sa->nalloc) {
+ if (sarrayExtendArray(sa))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+
+ if (copyflag == L_COPY)
+ sa->array[n] = stringNew(string);
+ else /* L_INSERT or L_NOCOPY */
+ sa->array[n] = (char *)string;
+ sa->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief sarrayExtendArray()
+ *
+ * \param[in] sa string array
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Doubles the size of the string ptr array.
+ * (2) The max number of strings is 25M.
+ * </pre>
+ */
+static l_int32
+sarrayExtendArray(SARRAY *sa)
+{
+size_t oldsize, newsize;
+
+ PROCNAME("sarrayExtendArray");
+
+ if (!sa)
+ return ERROR_INT("sa not defined", procName, 1);
+ if (sa->nalloc > MaxPtrArraySize) /* belt & suspenders */
+ return ERROR_INT("sa has too many ptrs", procName, 1);
+ oldsize = sa->nalloc * sizeof(char *);
+ newsize = 2 * oldsize;
+ if (newsize > 8 * MaxPtrArraySize) /* ptrs for 25 million strings */
+ return ERROR_INT("newsize > 200 MB; too large", procName, 1);
+
+ if ((sa->array = (char **)reallocNew((void **)&sa->array,
+ oldsize, newsize)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+
+ sa->nalloc *= 2;
+ return 0;
+}
+
+
+/*!
+ * \brief sarrayRemoveString()
+ *
+ * \param[in] sa string array
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief sarrayReplaceString()
+ *
+ * \param[in] sa string array
+ * \param[in] index of string within sarray to be replaced
+ * \param[in] newstr string to replace existing one
+ * \param[in] copyflag L_INSERT, L_COPY
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief sarrayClear()
+ *
+ * \param[in] sa string array
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief sarrayGetCount()
+ *
+ * \param[in] sa string array
+ * \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;
+}
+
+
+/*!
+ * \brief sarrayGetArray()
+ *
+ * \param[in] sa string array
+ * \param[out] pnalloc [optional] number allocated string ptrs
+ * \param[out] pn [optional] number allocated strings
+ * \return ptr to string array, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Caution: the returned array is not a copy, so caller
+ * must not destroy it!
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sarrayGetString()
+ *
+ * \param[in] sa string array
+ * \param[in] index to the index-th string
+ * \param[in] copyflag L_NOCOPY or L_COPY
+ * \return string, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See usage comments at the top of this file.
+ * (2) To get a pointer to the string itself, use L_NOCOPY.
+ * To get a copy of the string, use L_COPY.
+ * </pre>
+ */
+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]);
+}
+
+
+/*!
+ * \brief sarrayGetRefCount()
+ *
+ * \param[in] sa string array
+ * \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;
+}
+
+
+/*!
+ * \brief sarrayChangeRefCount()
+ *
+ * \param[in] sa string array
+ * \param[in] delta change to be applied
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief sarrayToString()
+ *
+ * \param[in] sa string array
+ * \param[in] addnlflag flag: 0 adds nothing to each substring
+ * 1 adds '\n' to each substring
+ * 2 adds ' ' to each substring
+ * 3 adds ',' to each substring
+ * \return dest string, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Concatenates all the strings in the sarray, preserving
+ * all white space.
+ * (2) If addnlflag != 0, adds '\n', ' ' or ',' 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?
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief sarrayToStringRange()
+ *
+ * \param[in] sa string array
+ * \param[in] first index of first string to use; starts with 0
+ * \param[in] nstrings number of strings to append into the result; use
+ * 0 to append to the end of the sarray
+ * \param[in] addnlflag flag: 0 adds nothing to each substring
+ * 1 adds '\n' to each substring
+ * 2 adds ' ' to each substring
+ * 3 adds ',' to each substring
+ * \return dest string, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Concatenates the specified strings in the sarray, preserving
+ * all white space.
+ * (2) If addnlflag != 0, adds '\n', ' ' or ',' after each substring.
+ * (3) If the sarray is empty, this returns a string with just
+ * the character corresponding to %addnlflag.
+ * </pre>
+ */
+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 && addnlflag != 3)
+ 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");
+ if (addnlflag == 2)
+ return stringNew(" ");
+ else /* addnlflag == 3) */
+ return stringNew(",");
+ } else {
+ return (char *)ERROR_PTR("first not valid", procName, NULL);
+ }
+ }
+
+ /* Determine the range of string indices to be used */
+ 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;
+
+ /* Determine the size of the output string */
+ 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);
+
+ /* Construct the output */
+ 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++;
+ } else if (addnlflag == 3) {
+ dest[index] = ',';
+ index++;
+ }
+ }
+
+ return dest;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Concatenate strings uniformly within the sarray *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief sarrayConcatUniformly()
+ *
+ * \param[in] sa string array
+ * \param[in] n number of strings in output sarray
+ * \param[in] addnlflag flag: 0 adds nothing to each substring
+ * 1 adds '\n' to each substring
+ * 2 adds ' ' to each substring
+ * 3 adds ',' to each substring
+ * \return dest sarray, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Divides %sa into %n essentially equal sets of strings,
+ * concatenates each set individually, and makes an output
+ * sarray with the %n concatenations. %n must not exceed the
+ * number of strings in %sa.
+ * (2) If addnlflag != 0, adds '\n', ' ' or ',' after each substring.
+ * </pre>
+ */
+SARRAY *
+sarrayConcatUniformly(SARRAY *sa,
+ l_int32 n,
+ l_int32 addnlflag)
+{
+l_int32 i, first, ntot, nstr;
+char *str;
+NUMA *na;
+SARRAY *saout;
+
+ PROCNAME("sarrayConcatUniformly");
+
+ if (!sa)
+ return (SARRAY *)ERROR_PTR("sa not defined", procName, NULL);
+ ntot = sarrayGetCount(sa);
+ if (n < 1)
+ return (SARRAY *)ERROR_PTR("n must be >= 1", procName, NULL);
+ if (n > ntot) {
+ L_ERROR("n = %d > ntot = %d\n", procName, n, ntot);
+ return NULL;
+ }
+ if (addnlflag != 0 && addnlflag != 1 && addnlflag != 2 && addnlflag != 3)
+ return (SARRAY *)ERROR_PTR("invalid addnlflag", procName, NULL);
+
+ saout = sarrayCreate(0);
+ na = numaGetUniformBinSizes(ntot, n);
+ for (i = 0, first = 0; i < n; i++) {
+ numaGetIValue(na, i, &nstr);
+ str = sarrayToStringRange(sa, first, nstr, addnlflag);
+ sarrayAddString(saout, str, L_INSERT);
+ first += nstr;
+ }
+ numaDestroy(&na);
+ return saout;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Join 2 sarrays *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief sarrayJoin()
+ *
+ * \param[in] sa1 to be added to
+ * \param[in] sa2 append to sa1
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Copies of the strings in sarray2 are added to sarray1.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief sarrayAppendRange()
+ *
+ * \param[in] sa1 to be added to
+ * \param[in] sa2 append specified range of strings in sa2 to sa1
+ * \param[in] start index of first string of sa2 to append
+ * \param[in] end index of last string of sa2 to append;
+ * -1 to append to end of array
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief sarrayPadToSameSize()
+ *
+ * \param[in] sa1, sa2
+ * \param[in] padstring
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+sarrayPadToSameSize(SARRAY *sa1,
+ SARRAY *sa2,
+ const 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief sarrayConvertWordsToLines()
+ *
+ * \param[in] sa sa of individual words
+ * \param[in] linesize max num of chars in each line
+ * \return saout sa of formatted lines, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) 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.
+ * (2) 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.
+ * </pre>
+ */
+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);
+
+ saout = sarrayCreate(0);
+ n = sarrayGetCount(sa);
+ totlen = 0;
+ sal = NULL;
+ for (i = 0; i < n; i++) {
+ if (!sal)
+ sal = sarrayCreate(0);
+ 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);
+ sal = sarrayCreate(0);
+ 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 *
+ *----------------------------------------------------------------------*/
+/*
+ * \brief sarraySplitString()
+ *
+ * \param[in] sa to append to; typically empty initially
+ * \param[in] str string to split; not changed
+ * \param[in] separators characters that split input string
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This uses strtokSafe(). See the notes there in utils.c.
+ * </pre>
+ */
+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 */
+ saveptr = NULL;
+ 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief sarraySelectBySubstring()
+ *
+ * \param[in] sain input sarray
+ * \param[in] substr [optional] substring for matching; can be NULL
+ * \return saout output sarray, filtered with substring or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sarraySelectByRange()
+ *
+ * \param[in] sain input sarray
+ * \param[in] first index of first string to be selected
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sarrayParseRange()
+ *
+ * \param[in] sa input sarray
+ * \param[in] start index to start range search
+ * \param[out] pactualstart index of actual start; may be > 'start'
+ * \param[out] pend index of end
+ * \param[out] pnewstart index of start of next range
+ * \param[in] substr substring for matching at beginning of string
+ * \param[in] 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
+ *
+ * <pre>
+ * 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))
+ * lept_stderr("start = %d, end = %d\n", actstart, end);
+ * </pre>
+ */
+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;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Serialize for I/O *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief sarrayRead()
+ *
+ * \param[in] 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);
+ sa = sarrayReadStream(fp);
+ fclose(fp);
+ if (!sa)
+ return (SARRAY *)ERROR_PTR("sa not read", procName, NULL);
+ return sa;
+}
+
+
+/*!
+ * \brief sarrayReadStream()
+ *
+ * \param[in] fp file stream
+ * \return sarray, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) We store the size of each string along with the string.
+ * The limit on the number of strings is 25M.
+ * The limit on the size of any string is 2^30 bytes.
+ * (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.
+ * (3) It is OK for the sarray to be empty.
+ * </pre>
+ */
+SARRAY *
+sarrayReadStream(FILE *fp)
+{
+char *stringbuf;
+l_int32 i, n, size, index, bufsize, version, ignore, success;
+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 (n < 0)
+ return (SARRAY *)ERROR_PTR("num string ptrs <= 0", procName, NULL);
+ if (n > MaxPtrArraySize)
+ return (SARRAY *)ERROR_PTR("too many string ptrs", procName, NULL);
+ if (n == 0) L_INFO("the sarray is empty\n", procName);
+
+ success = TRUE;
+ if ((sa = sarrayCreate(n)) == NULL)
+ return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+ bufsize = 512 + 1;
+ stringbuf = (char *)LEPT_CALLOC(bufsize, sizeof(char));
+
+ for (i = 0; i < n; i++) {
+ /* Get the size of the stored string */
+ if ((fscanf(fp, "%d[%d]:", &index, &size) != 2) || (size > (1 << 30))) {
+ success = FALSE;
+ L_ERROR("error on string size\n", procName);
+ goto cleanup;
+ }
+ /* 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) {
+ success = FALSE;
+ L_ERROR("error reading string\n", procName);
+ goto cleanup;
+ }
+ /* 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");
+
+cleanup:
+ LEPT_FREE(stringbuf);
+ if (!success) sarrayDestroy(&sa);
+ return sa;
+}
+
+
+/*!
+ * \brief sarrayReadMem()
+ *
+ * \param[in] data serialization in ascii
+ * \param[in] size of data; can use strlen to get it
+ * \return sarray, or NULL on error
+ */
+SARRAY *
+sarrayReadMem(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+SARRAY *sa;
+
+ PROCNAME("sarrayReadMem");
+
+ if (!data)
+ return (SARRAY *)ERROR_PTR("data not defined", procName, NULL);
+ if ((fp = fopenReadFromMemory(data, size)) == NULL)
+ return (SARRAY *)ERROR_PTR("stream not opened", procName, NULL);
+
+ sa = sarrayReadStream(fp);
+ fclose(fp);
+ if (!sa) L_ERROR("sarray not read\n", procName);
+ return sa;
+}
+
+
+/*!
+ * \brief sarrayWrite()
+ *
+ * \param[in] filename
+ * \param[in] sa string array
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+sarrayWrite(const char *filename,
+ SARRAY *sa)
+{
+l_int32 ret;
+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);
+ ret = sarrayWriteStream(fp, sa);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("sa not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief sarrayWriteStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] sa string array
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This appends a '\n' to each string, which is stripped
+ * off by sarrayReadStream().
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief sarrayWriteMem()
+ *
+ * \param[out] pdata data of serialized sarray; ascii
+ * \param[out] psize size of returned data
+ * \param[in] sa
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Serializes a sarray in memory and puts the result in a buffer.
+ * </pre>
+ */
+l_ok
+sarrayWriteMem(l_uint8 **pdata,
+ size_t *psize,
+ SARRAY *sa)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("sarrayWriteMem");
+
+ 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 (!sa)
+ return ERROR_INT("sa not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+ if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ ret = sarrayWriteStream(fp, sa);
+#else
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+ #endif /* _WIN32 */
+ ret = sarrayWriteStream(fp, sa);
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+#endif /* HAVE_FMEMOPEN */
+ fclose(fp);
+ return ret;
+}
+
+
+/*!
+ * \brief sarrayAppend()
+ *
+ * \param[in] filename
+ * \param[in] sa
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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)) {
+ fclose(fp);
+ return ERROR_INT("sa not appended to stream", procName, 1);
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Directory filenames *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief getNumberedPathnamesInDirectory()
+ *
+ * \param[in] dirname directory name
+ * \param[in] substr [optional] substring filter on filenames; can be NULL
+ * \param[in] numpre number of characters in name before number
+ * \param[in] numpost number of characters in name after the number,
+ * up to a dot before an extension
+ * \param[in] maxnum only consider page numbers up to this value
+ * \return sarray of numbered pathnames, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ sarrayDestroy(&sa);
+ return sarrayCreate(1);
+ }
+
+ saout = convertSortedToNumberedPathnames(sa, numpre, numpost, maxnum);
+ sarrayDestroy(&sa);
+ return saout;
+}
+
+
+/*!
+ * \brief getSortedPathnamesInDirectory()
+ *
+ * \param[in] dirname directory name
+ * \param[in] substr [optional] substring filter on filenames; can be NULL
+ * \param[in] first 0-based
+ * \param[in] nfiles use 0 for all to the end
+ * \return sarray of sorted pathnames, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 = pathJoin(dirname, fname);
+ sarrayAddString(saout, fullname, L_INSERT);
+ }
+
+ sarrayDestroy(&safiles);
+ return saout;
+}
+
+
+/*!
+ * \brief convertSortedToNumberedPathnames()
+ *
+ * \param[in] sa sorted pathnames including zero-padded integers
+ * \param[in] numpre number of characters in name before number
+ * \param[in] numpost number of characters in name after the number,
+ * up to a dot before an extension
+ * \param[in] maxnum only consider page numbers up to this value
+ * \return sarray of numbered pathnames, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Typically, numpre = numpost = 0; e.g., when the filename
+ * just has a number followed by an optional extension.
+ * </pre>
+ */
+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, "");
+ 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;
+}
+
+
+/*!
+ * \brief getFilenamesInDirectory()
+ *
+ * \param[in] dirname directory name
+ * \return sarray of file names, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+
+#ifndef _WIN32
+
+SARRAY *
+getFilenamesInDirectory(const char *dirname)
+{
+char dir[PATH_MAX + 1];
+char *realdir, *stat_path, *ignore;
+size_t size;
+SARRAY *safiles;
+DIR *pdir;
+struct dirent *pdirentry;
+int dfd, stat_ret;
+struct stat st;
+
+ PROCNAME("getFilenamesInDirectory");
+
+ if (!dirname)
+ return (SARRAY *)ERROR_PTR("dirname not defined", procName, NULL);
+ if (dirname[0] == '\0')
+ return (SARRAY *)ERROR_PTR("dirname is empty", procName, NULL);
+
+ /* Who would have thought it was this fiddly to open a directory
+ and get the files inside? fstatat() works with relative
+ directory paths, and stat() requires using the absolute path.
+ realpath works as follows for files and directories:
+ * If the file or directory exists, realpath returns its path;
+ else it returns NULL.
+ * If the second arg to realpath is passed in, the canonical path
+ is returned there. Use a buffer of sufficient size. If the
+ second arg is NULL, the path is malloc'd and returned if the
+ file or directory exists.
+ We pass in a buffer for the second arg, and check that the canonical
+ directory path was made. The existence of the directory is checked
+ later, after its actual path is returned by genPathname(). */
+ dir[0] = '\0'; /* init empty in case realpath() fails to write it */
+ ignore = realpath(dirname, dir);
+ if (dir[0] == '\0')
+ return (SARRAY *)ERROR_PTR("dir not made", procName, NULL);
+ realdir = genPathname(dir, NULL);
+ if ((pdir = opendir(realdir)) == NULL) {
+ LEPT_FREE(realdir);
+ return (SARRAY *)ERROR_PTR("pdir not opened", procName, NULL);
+ }
+ safiles = sarrayCreate(0);
+ dfd = dirfd(pdir);
+ while ((pdirentry = readdir(pdir))) {
+#if HAVE_FSTATAT
+ stat_ret = fstatat(dfd, pdirentry->d_name, &st, 0);
+#else
+ size = strlen(realdir) + strlen(pdirentry->d_name) + 2;
+ if (size > PATH_MAX) {
+ L_ERROR("size = %zu too large; skipping\n", procName, size);
+ continue;
+ }
+ stat_path = (char *)LEPT_CALLOC(size, 1);
+ snprintf(stat_path, size, "%s/%s", realdir, pdirentry->d_name);
+ stat_ret = stat(stat_path, &st);
+ LEPT_FREE(stat_path);
+#endif
+ if (stat_ret == 0 && S_ISDIR(st.st_mode))
+ continue;
+ sarrayAddString(safiles, pdirentry->d_name, L_COPY);
+ }
+ closedir(pdir);
+ LEPT_FREE(realdir);
+ 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/leptonica/src/sarray2.c b/leptonica/src/sarray2.c
new file mode 100644
index 00000000..ec8a683f
--- /dev/null
+++ b/leptonica/src/sarray2.c
@@ -0,0 +1,730 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file sarray2.c
+ * <pre>
+ *
+ * Sort
+ * SARRAY *sarraySort()
+ * SARRAY *sarraySortByIndex()
+ * l_int32 stringCompareLexical()
+ *
+ * Set operations using aset (rbtree)
+ * SARRAY *sarrayUnionByAset()
+ * SARRAY *sarrayRemoveDupsByAset()
+ * SARRAY *sarrayIntersectionByAset()
+ * L_ASET *l_asetCreateFromSarray()
+ *
+ * Set operations using hashing (dnahash)
+ * l_int32 sarrayRemoveDupsByHash()
+ * SARRAY *sarrayIntersectionByHash()
+ * l_int32 sarrayFindStringByHash()
+ * L_DNAHASH *l_dnaHashCreateFromSarray()
+ *
+ * Miscellaneous operations
+ * SARRAY *sarrayGenerateIntegers()
+ * l_int32 sarrayLookupCSKV()
+ *
+ *
+ * We have two implementations of set operations on an array of strings:
+ *
+ * (1) Using an underlying tree (rbtree)
+ * This uses a good 64 bit hashing function for the key,
+ * that is not expected to have hash collisions (and we do
+ * not test for them). The tree is built up of the hash
+ * values, and if the hash is found in the tree, it is
+ * assumed that the string has already been found.
+ *
+ * (2) Using an underlying hashing of the keys (dnahash)
+ * This uses a fast 64 bit hashing function for the key,
+ * which is then hashed into a bucket (a dna in a dnaHash).
+ * Because hash collisions can occur, the index into the
+ * sarray for the string that gave rise to that key is stored,
+ * and the dna (bucket) is traversed, using the stored indices
+ * to determine if that string had already been seen.
+ *
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*----------------------------------------------------------------------*
+ * Sort *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief sarraySort()
+ *
+ * \param[in] saout output sarray; can be NULL or equal to sain
+ * \param[in] sain input sarray
+ * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING
+ * \return saout output sarray, sorted by ascii value, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sarraySortByIndex()
+ *
+ * \param[in] sain
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief stringCompareLexical()
+ *
+ * \param[in] str1
+ * \param[in] str2
+ * \return 1 if str1 > str2 lexically; 0 otherwise
+ *
+ * <pre>
+ * Notes:
+ * (1) If the lexical values are identical, return a 0, to
+ * indicate that no swapping is required to sort the strings.
+ * </pre>
+ */
+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;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Set operations using aset (rbtree) *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief sarrayUnionByAset()
+ *
+ * \param[in] sa1, sa2
+ * \return sad with the union of the string set, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sarrayRemoveDupsByAset()
+ *
+ * \param[in] sas
+ * \return sad with duplicates removed, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sarrayIntersectionByAset()
+ *
+ * \param[in] sa1, sa2
+ * \return sad with the intersection of the string set, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Algorithm: put the larger sarray into a set, using the string
+ * hashes as the key values. Then run through the smaller 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_asetCreateFromSarray()
+ *
+ * \param[in] sa
+ * \return set using a string hash into a uint64 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;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Set operations using hashing (dnahash) *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief sarrayRemoveDupsByHash()
+ *
+ * \param[in] sas
+ * \param[out] psad unique set of strings; duplicates removed
+ * \param[out] pdahash [optional] dnahash used for lookup
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+sarrayRemoveDupsByHash(SARRAY *sas,
+ SARRAY **psad,
+ L_DNAHASH **pdahash)
+{
+char *str;
+l_int32 i, n, index, items;
+l_uint32 nsize;
+l_uint64 key;
+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;
+}
+
+
+/*!
+ * \brief sarrayIntersectionByHash()
+ *
+ * \param[in] sa1, sa2
+ * \return sad intersection of the strings, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is faster than sarrayIntersectionByAset(), because the
+ * bucket lookup is O(n).
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sarrayFindStringByHash()
+ *
+ * \param[in] sa
+ * \param[in] dahash built from sa
+ * \param[in] str arbitrary string
+ * \param[out] pindex index into %sa if %str is in %sa; -1 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Fast lookup in dnaHash associated with a sarray, to see if a
+ * random string %str is already stored in the hash table.
+ * (2) We use a strong hash function to minimize the chance that
+ * two different strings hash to the same key value.
+ * (3) We select the number of buckets to be about 5% of the size
+ * of the input sarray, so that when fully populated, each
+ * bucket (dna) will have about 20 entries, each being an index
+ * into sa. In lookup, after hashing to the key, and then
+ * again to the bucket, we traverse the bucket (dna), using the
+ * index into sa to check if %str has been found before.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_dnaHashCreateFromSarray()
+ *
+ * \param[in] 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 */
+/* lept_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;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Miscellaneous operations *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief sarrayGenerateIntegers()
+ *
+ * \param[in] n
+ * \return sa of printed numbers, 1 - n, or NULL on error
+ */
+SARRAY *
+sarrayGenerateIntegers(l_int32 n)
+{
+char buf[32];
+l_int32 i;
+SARRAY *sa;
+
+ PROCNAME("sarrayGenerateIntegers");
+
+ if ((sa = sarrayCreate(n)) == NULL)
+ return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+ for (i = 0; i < n; i++) {
+ snprintf(buf, sizeof(buf), "%d", i);
+ sarrayAddString(sa, buf, L_COPY);
+ }
+ return sa;
+}
+
+
+/*!
+ * \brief sarrayLookupCSKV()
+ *
+ * \param[in] sa of strings, each being a comma-separated pair
+ * of strings, the first being a key and the
+ * second a value
+ * \param[in] keystring an input string to match with each key in %sa
+ * \param[out] pvalstring the returned value string corresponding to the
+ * input key string, if found; otherwise NULL
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The input %sa can have other strings that are not in
+ * comma-separated key-value format. These will be ignored.
+ * (2) This returns a copy of the first value string in %sa whose
+ * key string matches the input %keystring.
+ * (3) White space is not ignored; all white space before the ','
+ * is used for the keystring in matching. This allows the
+ * key and val strings to have white space (e.g., multiple words).
+ * </pre>
+ */
+l_ok
+sarrayLookupCSKV(SARRAY *sa,
+ const char *keystring,
+ char **pvalstring)
+{
+char *key, *val, *str;
+l_int32 i, n;
+SARRAY *sa1;
+
+ PROCNAME("sarrayLookupCSKV");
+
+ if (!pvalstring)
+ return ERROR_INT("&valstring not defined", procName, 1);
+ *pvalstring = NULL;
+ if (!sa)
+ return ERROR_INT("sa not defined", procName, 1);
+ if (!keystring)
+ return ERROR_INT("keystring not defined", procName, 1);
+
+ n = sarrayGetCount(sa);
+ for (i = 0; i < n; i++) {
+ str = sarrayGetString(sa, i, L_NOCOPY);
+ sa1 = sarrayCreate(2);
+ sarraySplitString(sa1, str, ",");
+ if (sarrayGetCount(sa1) != 2) {
+ sarrayDestroy(&sa1);
+ continue;
+ }
+ key = sarrayGetString(sa1, 0, L_NOCOPY);
+ val = sarrayGetString(sa1, 1, L_NOCOPY);
+ if (!strcmp(key, keystring)) {
+ *pvalstring = stringNew(val);
+ sarrayDestroy(&sa1);
+ return 0;
+ }
+ sarrayDestroy(&sa1);
+ }
+
+ return 0;
+}
diff --git a/leptonica/src/scale1.c b/leptonica/src/scale1.c
new file mode 100644
index 00000000..8ff87fb3
--- /dev/null
+++ b/leptonica/src/scale1.c
@@ -0,0 +1,3764 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file scale1.c
+ * <pre>
+ * Top-level scaling
+ * PIX *pixScale()
+ * PIX *pixScaleToSizeRel()
+ * PIX *pixScaleToSize()
+ * PIX *pixScaleToResolution()
+ * PIX *pixScaleGeneral()
+ *
+ * Linearly interpreted (usually up-) scaling
+ * PIX *pixScaleLI()
+ * PIX *pixScaleColorLI()
+ * PIX *pixScaleColor2xLI()
+ * PIX *pixScaleColor4xLI()
+ * PIX *pixScaleGrayLI()
+ * PIX *pixScaleGray2xLI()
+ * PIX *pixScaleGray4xLI()
+ *
+ * Upscale 2x followed by binarization
+ * PIX *pixScaleGray2xLIThresh()
+ * PIX *pixScaleGray2xLIDither()
+ *
+ * Upscale 4x followed by binarization
+ * PIX *pixScaleGray4xLIThresh()
+ * PIX *pixScaleGray4xLIDither()
+ *
+ * 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 *pixScaleSmoothToSize()
+ * PIX *pixScaleRGBToGray2() [special 2x reduction to gray]
+ *
+ * Downscaling with (antialias) area mapping
+ * PIX *pixScaleAreaMap()
+ * PIX *pixScaleAreaMap2()
+ * PIX *pixScaleAreaMapToSize()
+ *
+ * Binary scaling by closest pixel sampling
+ * PIX *pixScaleBinary()
+ *
+ * Low-level static functions:
+ *
+ * Color (interpolated) scaling: general case
+ * static void scaleColorLILow()
+ *
+ * Grayscale (interpolated) scaling: general case
+ * static void scaleGrayLILow()
+ *
+ * Color (interpolated) scaling: 2x upscaling
+ * static void scaleColor2xLILow()
+ * static void scaleColor2xLILineLow()
+ *
+ * Grayscale (interpolated) scaling: 2x upscaling
+ * static void scaleGray2xLILow()
+ * static void scaleGray2xLILineLow()
+ *
+ * Grayscale (interpolated) scaling: 4x upscaling
+ * static void scaleGray4xLILow()
+ * static void scaleGray4xLILineLow()
+ *
+ * Grayscale and color scaling by closest pixel sampling
+ * static l_int32 scaleBySamplingLow()
+ *
+ * Color and grayscale downsampling with (antialias) lowpass filter
+ * static l_int32 scaleSmoothLow()
+ * static void scaleRGBToGray2Low()
+ *
+ * Color and grayscale downsampling with (antialias) area mapping
+ * static l_int32 scaleColorAreaMapLow()
+ * static l_int32 scaleGrayAreaMapLow()
+ * static l_int32 scaleAreaMapLow2()
+ *
+ * Binary scaling by closest pixel sampling
+ * static l_int32 scaleBinaryLow()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static 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);
+static 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);
+static void scaleColor2xLILow(l_uint32 *datad, l_int32 wpld, l_uint32 *datas,
+ l_int32 ws, l_int32 hs, l_int32 wpls);
+static void scaleColor2xLILineLow(l_uint32 *lined, l_int32 wpld,
+ l_uint32 *lines, l_int32 ws, l_int32 wpls,
+ l_int32 lastlineflag);
+static void scaleGray2xLILow(l_uint32 *datad, l_int32 wpld, l_uint32 *datas,
+ l_int32 ws, l_int32 hs, l_int32 wpls);
+static void scaleGray2xLILineLow(l_uint32 *lined, l_int32 wpld,
+ l_uint32 *lines, l_int32 ws, l_int32 wpls,
+ l_int32 lastlineflag);
+static void scaleGray4xLILow(l_uint32 *datad, l_int32 wpld, l_uint32 *datas,
+ l_int32 ws, l_int32 hs, l_int32 wpls);
+static void scaleGray4xLILineLow(l_uint32 *lined, l_int32 wpld,
+ l_uint32 *lines, l_int32 ws, l_int32 wpls,
+ l_int32 lastlineflag);
+static 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);
+static 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);
+static 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);
+static 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);
+static 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);
+static void scaleAreaMapLow2(l_uint32 *datad, l_int32 wd, l_int32 hd,
+ l_int32 wpld, l_uint32 *datas, l_int32 d,
+ l_int32 wpls);
+static 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);
+
+#ifndef NO_CONSOLE_IO
+#define DEBUG_OVERFLOW 0
+#define DEBUG_UNROLLING 0
+#endif /* ~NO_CONSOLE_IO */
+
+/*------------------------------------------------------------------*
+ * Top level scaling dispatcher *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixScale()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16 and 32 bpp
+ * \param[in] 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 five methods, depending on the scale factors:
+ * 1. antialiased subsampling (lowpass filtering followed by
+ * subsampling, implemented by convolution, for tiny scale factors:
+ * min(scalex, scaley) < 0.02.
+ * 2. antialiased subsampling (implemented by area mapping, for
+ * small scale factors:
+ * max(scalex, scaley) < 0.2 and min(scalex, scaley) >= 0.02.
+ * 3. antialiased subsampling with sharpening, for scale factors
+ * between 0.2 and 0.7
+ * 4. linear interpolation with sharpening, for scale factors between
+ * 0.7 and 1.4
+ * 5. 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.
+ */
+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);
+}
+
+
+/*!
+ * \brief pixScaleToSizeRel()
+ *
+ * \param[in] pixs
+ * \param[in] delw change in width, in pixels; 0 means no change
+ * \param[in] delh change in height, in pixels; 0 means no change
+ * \return pixd, or NULL on error
+ */
+PIX *
+pixScaleToSizeRel(PIX *pixs,
+ l_int32 delw,
+ l_int32 delh)
+{
+l_int32 w, h, wd, hd;
+
+ PROCNAME("pixScaleToSizeRel");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+ if (delw == 0 && delh == 0)
+ return pixCopy(NULL, pixs);
+
+ pixGetDimensions(pixs, &w, &h, NULL);
+ wd = w + delw;
+ hd = h + delh;
+ if (wd <= 0 || hd <= 0)
+ return (PIX *)ERROR_PTR("pix dimension reduced to 0", procName, NULL);
+
+ return pixScaleToSize(pixs, wd, hd);
+}
+
+
+/*!
+ * \brief pixScaleToSize()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16 and 32 bpp
+ * \param[in] wd target width; use 0 if using height as target
+ * \param[in] hd target height; use 0 if using width as target
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixScaleToResolution()
+ *
+ * \param[in] pixs
+ * \param[in] target desired resolution
+ * \param[in] assumed assumed resolution if not defined; typ. 300.
+ * \param[out] pscalefact [optional] actual scaling factor used
+ * \return pixd, or NULL on error
+ */
+PIX *
+pixScaleToResolution(PIX *pixs,
+ l_float32 target,
+ l_float32 assumed,
+ l_float32 *pscalefact)
+{
+l_int32 xres;
+l_float32 factor;
+
+ PROCNAME("pixScaleToResolution");
+
+ if (pscalefact) *pscalefact = 1.0;
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+ if (target <= 0)
+ return (PIX *)ERROR_PTR("target resolution <= 0", procName, NULL);
+
+ xres = pixGetXRes(pixs);
+ if (xres <= 0) {
+ if (assumed == 0)
+ return pixCopy(NULL, pixs);
+ xres = assumed;
+ }
+ factor = target / (l_float32)xres;
+ if (pscalefact) *pscalefact = factor;
+
+ return pixScale(pixs, factor, factor);
+}
+
+
+/*!
+ * \brief pixScaleGeneral()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16 and 32 bpp
+ * \param[in] scalex must be > 0.0
+ * \param[in] scaley must be > 0.0
+ * \param[in] sharpfract use 0.0 to skip sharpening
+ * \param[in] sharpwidth halfwidth of low-pass filter; typ. 1 or 2
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixScale() for usage.
+ * (2) This interface may change in the future, as other special
+ * cases are added.
+ * (3) For tiny scaling factors
+ * minscale < 0.02: use a simple lowpass filter
+ * (4) 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
+ * (5) To avoid sharpening for grayscale and color images with
+ * scaling factors between 0.2 and 1.4, call this function
+ * with %sharpfract == 0.0.
+ * (6) 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.
+ * </pre>
+ */
+PIX *
+pixScaleGeneral(PIX *pixs,
+ l_float32 scalex,
+ l_float32 scaley,
+ l_float32 sharpfract,
+ l_int32 sharpwidth)
+{
+l_int32 d;
+l_float32 maxscale, minscale;
+PIX *pix1, *pix2, *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 ((pix1 = pixConvertTo8Or32(pixs, L_CLONE, 0)) == NULL)
+ return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+
+ /* Scale (up or down) */
+ d = pixGetDepth(pix1);
+ maxscale = L_MAX(scalex, scaley);
+ minscale = L_MIN(scalex, scaley);
+ if (maxscale < 0.7) { /* use low-pass filter for anti-aliasing */
+ if (minscale < 0.02) { /* whole-pixel low-pass filter */
+ pix2 = pixScaleSmooth(pix1, scalex, scaley);
+ } else { /* fractional pixel low-pass filter */
+ pix2 = pixScaleAreaMap(pix1, scalex, scaley);
+ }
+ if (maxscale > 0.2 && sharpfract > 0.0 && sharpwidth > 0) {
+ pixd = pixUnsharpMasking(pix2, sharpwidth, sharpfract);
+ } else {
+ pixd = pixClone(pix2);
+ }
+ } else { /* use linear interpolation */
+ if (d == 8) {
+ pix2 = pixScaleGrayLI(pix1, scalex, scaley);
+ } else { /* d == 32 */
+ pix2 = pixScaleColorLI(pix1, scalex, scaley);
+ }
+ if (maxscale < 1.4 && sharpfract > 0.0 && sharpwidth > 0) {
+ pixd = pixUnsharpMasking(pix2, sharpwidth, sharpfract);
+ } else {
+ pixd = pixClone(pix2);
+ }
+ }
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixCopyText(pixd, pixs);
+ pixCopyInputFormat(pixd, pixs);
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Scaling by linear interpolation *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleLI()
+ *
+ * \param[in] pixs 2, 4, 8 or 32 bpp; with or without colormap
+ * \param[in] scalex must be >= 0.7
+ * \param[in] scaley must be >= 0.7
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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 both scale factors are smaller than 0.7, we issue a warning
+ * and call pixScaleGeneral(), which will invoke area mapping
+ * without sharpening.
+ * (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.
+ * </pre>
+ */
+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 pixScaleGeneral(pixs, scalex, scaley, 0.0, 0);
+ }
+ 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, L_CLONE, 0)) == NULL)
+ return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+ d = pixGetDepth(pixt);
+ if (d == 8)
+ pixd = pixScaleGrayLI(pixt, scalex, scaley);
+ else /* d == 32 */
+ pixd = pixScaleColorLI(pixt, scalex, scaley);
+
+ pixDestroy(&pixt);
+ pixCopyInputFormat(pixd, pixs);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixScaleColorLI()
+ *
+ * \param[in] pixs 32 bpp, representing rgb
+ * \param[in] scalex must be >= 0.7
+ * \param[in] scaley must be >= 0.7
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If both scale factors are smaller than 0.7, we issue a warning
+ * and call pixScaleGeneral(), which will invoke area mapping
+ * without sharpening. This is particularly important for
+ * document images with sharp edges.
+ * (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.
+ * </pre>
+ */
+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 pixScaleGeneral(pixs, scalex, scaley, 0.0, 0);
+ }
+
+ /* 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;
+}
+
+
+/*!
+ * \brief pixScaleColor2xLI()
+ *
+ * \param[in] pixs 32 bpp, representing rgb
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixScaleColor4xLI()
+ *
+ * \param[in] pixs 32 bpp, representing rgb
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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) 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.
+ * </pre>
+ */
+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) {
+ L_ERROR("pixd not made\n", procName);
+ } else {
+ if (pixGetSpp(pixs) == 4)
+ pixScaleAndTransferAlpha(pixd, pixs, 4.0, 4.0);
+ pixCopyInputFormat(pixd, pixs);
+ }
+
+ pixDestroy(&pixrs);
+ pixDestroy(&pixgs);
+ pixDestroy(&pixbs);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixScaleGrayLI()
+ *
+ * \param[in] pixs 8 bpp grayscale, no cmap
+ * \param[in] scalex must be >= 0.7
+ * \param[in] scaley must be >= 0.7
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function is appropriate for upscaling magnification, where the
+ * scale factor is > 1, as well as for a small amount of downscaling
+ * reduction, with scale factor >= 0.7. If the scale factor is < 0.7,
+ * the best result is obtained by area mapping.
+ * (2) Here are some details:
+ * - 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.
+ * - 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.
+ * </pre>
+ */
+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 pixScaleGeneral(pixs, scalex, scaley, 0.0, 0);
+ }
+
+ /* 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;
+}
+
+
+/*!
+ * \brief pixScaleGray2xLI()
+ *
+ * \param[in] pixs 8 bpp grayscale, not cmapped
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixScaleGray4xLI()
+ *
+ * \param[in] pixs 8 bpp grayscale, not cmapped
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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;
+}
+
+
+/*------------------------------------------------------------------*
+ * Scale 2x followed by binarization *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleGray2xLIThresh()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \param[in] thresh between 0 and 256
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ LEPT_FREE(lineb);
+ 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;
+}
+
+
+/*!
+ * \brief pixScaleGray2xLIDither()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 = NULL; /* 2 intermediate buffer lines */
+l_uint32 *linebp = NULL; /* 1 intermediate buffer line */
+l_uint32 *bufs = NULL; /* 2 source buffer lines */
+PIX *pixd = NULL;
+
+ 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) {
+ L_ERROR("lineb not made\n", procName);
+ goto cleanup;
+ }
+
+ /* Make line buffer for 1 line of virtual intermediate image */
+ if ((linebp = (l_uint32 *)LEPT_CALLOC(wplb, sizeof(l_uint32))) == NULL) {
+ L_ERROR("linebp not made\n", procName);
+ goto cleanup;
+ }
+
+ /* Make dest binary image */
+ if ((pixd = pixCreate(wd, hd, 1)) == NULL) {
+ L_ERROR("pixd not made\n", procName);
+ goto cleanup;
+ }
+ 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 */
+
+cleanup:
+ LEPT_FREE(bufs);
+ LEPT_FREE(lineb);
+ LEPT_FREE(linebp);
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Scale 4x followed by binarization *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleGray4xLIThresh()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] thresh between 0 and 256
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ LEPT_FREE(lineb);
+ 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;
+}
+
+
+/*!
+ * \brief pixScaleGray4xLIDither()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 = NULL; /* 4 intermediate buffer lines */
+l_uint32 *linebp = NULL; /* 1 intermediate buffer line */
+l_uint32 *bufs = NULL; /* 2 source buffer lines */
+PIX *pixd = NULL;
+
+ 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) {
+ L_ERROR("lineb not made\n", procName);
+ goto cleanup;
+ }
+
+ /* Make line buffer for 1 line of virtual intermediate image */
+ if ((linebp = (l_uint32 *)LEPT_CALLOC(wplb, sizeof(l_uint32))) == NULL) {
+ L_ERROR("linebp not made\n", procName);
+ goto cleanup;
+ }
+
+ /* Make dest binary image */
+ if ((pixd = pixCreate(wd, hd, 1)) == NULL) {
+ L_ERROR("pixd not made\n", procName);
+ goto cleanup;
+ }
+ 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);
+
+cleanup:
+ LEPT_FREE(bufs);
+ LEPT_FREE(lineb);
+ LEPT_FREE(linebp);
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Scaling by closest pixel sampling *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleBySampling()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp
+ * \param[in] scalex must be > 0.0
+ * \param[in] scaley must be > 0.0
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixScaleBySamplingToSize()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16 and 32 bpp
+ * \param[in] wd target width; use 0 if using height as target
+ * \param[in] hd target height; use 0 if using width as target
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixScaleByIntSampling()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp
+ * \param[in] factor integer subsampling
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Simple interface to pixScaleBySampling(), for
+ * isotropic integer reduction.
+ * (2) If %factor == 1, returns a copy.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleRGBToGrayFast()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] factor integer reduction factor >= 1
+ * \param[in] color one of COLOR_RED, COLOR_GREEN, COLOR_BLUE
+ * \return pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixScaleRGBToBinaryFast()
+ *
+ * \param[in] pixs 32 bpp RGB
+ * \param[in] factor integer reduction factor >= 1
+ * \param[in] thresh binarization threshold
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixScaleGrayToBinaryFast()
+ *
+ * \param[in] pixs 8 bpp grayscale
+ * \param[in] factor integer reduction factor >= 1
+ * \param[in] thresh binarization threshold
+ * \return pixd 1 bpp, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleSmooth()
+ *
+ * \param[in] pix 2, 4, 8 or 32 bpp; and 2, 4, 8 bpp with colormap
+ * \param[in] scalex must be < 0.7
+ * \param[in] scaley must be < 0.7
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function should only be used when the scale factors are less
+ * than 0.7. If either scale factor is >= 0.7, issue a warning
+ * and call pixScaleGeneral(), which will invoke linear interpolation
+ * without sharpening.
+ * (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.
+ * (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.
+ * </pre>
+ */
+PIX *
+pixScaleSmooth(PIX *pix,
+ l_float32 scalex,
+ l_float32 scaley)
+{
+l_int32 ws, hs, d, wd, hd, wpls, wpld, isize;
+l_uint32 val;
+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 pixScaleGeneral(pix, scalex, scaley, 0.0, 0);
+ }
+ 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; clone if possible; result is either 8 or 32 bpp */
+ if ((pixs = pixConvertTo8Or32(pix, L_CLONE, 0)) == NULL)
+ return (PIX *)ERROR_PTR("pixs not made", procName, NULL);
+ d = pixGetDepth(pixs);
+
+ /* 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_MIN(10000, L_MAX(2, (l_int32)(size + 0.5)));
+
+ pixGetDimensions(pixs, &ws, &hs, NULL);
+ if ((ws < isize) || (hs < isize)) {
+ pixd = pixCreate(1, 1, d);
+ pixGetPixel(pixs, ws / 2, hs / 2, &val);
+ pixSetPixel(pixd, 0, 0, val);
+ L_WARNING("ridiculously small scaling factor %f\n", procName, minscale);
+ pixDestroy(&pixs);
+ return pixd;
+ }
+
+ datas = pixGetData(pixs);
+ wpls = pixGetWpl(pixs);
+ wd = L_MAX(1, (l_int32)(scalex * (l_float32)ws + 0.5));
+ hd = L_MAX(1, (l_int32)(scaley * (l_float32)hs + 0.5));
+ 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;
+}
+
+
+/*!
+ * \brief pixScaleSmoothToSize()
+ *
+ * \param[in] pixs 2, 4, 8 or 32 bpp; and 2, 4, 8 bpp with colormap
+ * \param[in] wd target width; use 0 if using height as target
+ * \param[in] hd target height; use 0 if using width as target
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes in pixScaleSmooth().
+ * (2) 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.
+ * </pre>
+ */
+PIX *
+pixScaleSmoothToSize(PIX *pixs,
+ l_int32 wd,
+ l_int32 hd)
+{
+l_int32 w, h;
+l_float32 scalex, scaley;
+
+ PROCNAME("pixScaleSmoothToSize");
+
+ 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 pixScaleSmooth(pixs, scalex, scaley);
+}
+
+
+/*!
+ * \brief pixScaleRGBToGray2()
+ *
+ * \param[in] pixs 32 bpp rgb
+ * \param[in] 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleAreaMap()
+ *
+ * \param[in] pix 2, 4, 8 or 32 bpp; and 2, 4, 8 bpp with colormap
+ * \param[in] scalex must be < 0.7; minimum is 0.02
+ * \param[in] scaley must be < 0.7; minimum is 0.02
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a low-pass filter that averages over fractional pixels.
+ * It should only be used when the scale factors are less than 0.7.
+ * If either scale factor is greater than or equal to 0.7, we
+ * issue a warning and call pixScaleGeneral(), which will invoke
+ * linear interpolation without sharpening.
+ * (2) The minimum scale factor allowed for area mapping reduction
+ * is 0.02. Various overflows will occur when scale factors are
+ * less than about 1/256. If a scale factor smaller than 0.02
+ * is given, we use pixScaleSmooth(), which is a low-pass filter
+ * that averages over entire pixels.
+ * (3) 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 call pixScaleGeneral().
+ * (4) This is faster than pixScale() because it does not do sharpening.
+ * (5) 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.
+ * (6) pixScaleAreaMap2() is typically about 7x faster for the special
+ * case of 2x reduction for color images, and about 9x faster
+ * for grayscale images. Surprisingly, the improvement in speed
+ * when using a cascade of 2x reductions for small scale factors is
+ * less than one might expect, and in most situations gives
+ * poorer image quality. But see (6).
+ * (7) For reductions between 0.35 and 0.5, a 2x area map reduction
+ * followed by using pixScaleGeneral() on a 2x larger scalefactor
+ * (which further reduces the image size using bilinear interpolation)
+ * would give a significant speed increase, with little loss of
+ * quality, but this is not enabled as it would break too many tests.
+ * For scaling factors below 0.35, scaling atomically is nearly
+ * as fast as using a cascade of 2x scalings, and gives
+ * better results.
+ * </pre>
+ */
+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, minscale;
+PIX *pixs, *pixd, *pix1, *pix2, *pix3;
+
+ 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);
+
+ minscale = L_MIN(scalex, scaley);
+ if (minscale < 0.02) { /* too small for area mapping */
+ L_WARNING("tiny scaling factor; using pixScaleSmooth()\n", procName);
+ return pixScaleSmooth(pix, scalex, scaley);
+ }
+
+ maxscale = L_MAX(scalex, scaley);
+ if (maxscale >= 0.7) { /* too large for area mapping */
+ L_WARNING("scaling factor >= 0.7; do regular scaling\n", procName);
+ return pixScaleGeneral(pix, scalex, scaley, 0.0, 0);
+ }
+
+ /* Special cases: 2x, 4x, 8x, 16x reduction */
+ if (scalex == 0.5 && scaley == 0.5)
+ return pixScaleAreaMap2(pix);
+ if (scalex == 0.25 && scaley == 0.25) {
+ pix1 = pixScaleAreaMap2(pix);
+ pixd = pixScaleAreaMap2(pix1);
+ pixDestroy(&pix1);
+ return pixd;
+ }
+ if (scalex == 0.125 && scaley == 0.125) {
+ pix1 = pixScaleAreaMap2(pix);
+ pix2 = pixScaleAreaMap2(pix1);
+ pixd = pixScaleAreaMap2(pix2);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ return pixd;
+ }
+ if (scalex == 0.0625 && scaley == 0.0625) {
+ pix1 = pixScaleAreaMap2(pix);
+ pix2 = pixScaleAreaMap2(pix1);
+ pix3 = pixScaleAreaMap2(pix2);
+ pixd = pixScaleAreaMap2(pix3);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ return pixd;
+ }
+
+#if 0 /* Not enabled because it breaks too many tests that rely on exact
+ * pixel matches. */
+ /* Special case where it is significantly faster to downscale first
+ * by 2x, with relatively little degradation in image quality. */
+ if (scalex > 0.35 && scalex < 0.5) {
+ pix1 = pixScaleAreaMap2(pix);
+ pixd = pixScaleAreaMap(pix1, 2.0 * scalex, 2.0 * scaley);
+ pixDestroy(&pix1);
+ return pixd;
+ }
+#endif
+
+ /* 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;
+}
+
+
+/*!
+ * \brief pixScaleAreaMap2()
+ *
+ * \param[in] pix 2, 4, 8 or 32 bpp; and 2, 4, 8 bpp with colormap
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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) Compared to the general pixScaleAreaMap(), for this function
+ * gray processing is about 14x faster and color processing
+ * is about 4x faster. Consequently, pixScaleAreaMap2() is
+ * incorporated into the general area map scaling function,
+ * for the special cases of 2x, 4x, 8x and 16x reduction.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixScaleAreaMapToSize()
+ *
+ * \param[in] pixs 2, 4, 8 or 32 bpp; and 2, 4, 8 bpp with colormap
+ * \param[in] wd target width; use 0 if using height as target
+ * \param[in] hd target height; use 0 if using width as target
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes in pixScaleAreaMap().
+ * (2) 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.
+ * </pre>
+ */
+PIX *
+pixScaleAreaMapToSize(PIX *pixs,
+ l_int32 wd,
+ l_int32 hd)
+{
+l_int32 w, h;
+l_float32 scalex, scaley;
+
+ PROCNAME("pixScaleAreaMapToSize");
+
+ 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 pixScaleAreaMap(pixs, scalex, scaley);
+}
+
+
+/*------------------------------------------------------------------*
+ * Binary scaling by closest pixel sampling *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleBinary()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] scalex must be > 0.0
+ * \param[in] scaley must be > 0.0
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This function samples from the source without
+ * filtering. As a result, aliasing will occur for
+ * subsampling (scalex and scaley < 1.0).
+ * </pre>
+ */
+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;
+}
+
+
+/* ================================================================ *
+ * Low level static functions *
+ * ================================================================ */
+
+/*------------------------------------------------------------------*
+ * General linear interpolated color scaling *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleColorLILow()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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.
+ * </pre>
+ */
+static 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_uint32 v00r, v01r, v10r, v11r, v00g, v01g, v10g, v11g;
+l_uint32 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;
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------*
+ * General linear interpolated gray scaling *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleGrayLILow()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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.
+ * </pre>
+ */
+static 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);
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------*
+ * 2x linear interpolated color scaling *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleColor2xLILow()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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
+ *
+ * (2) 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.
+ * </pre>
+ */
+static 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);
+}
+
+
+/*!
+ * \brief scaleColor2xLILineLow()
+ *
+ * \param[in] lined ptr to top destline, to be made from current src line
+ * \param[in] wpld
+ * \param[in] lines ptr to current src line
+ * \param[in] ws
+ * \param[in] wpls
+ * \param[in] lastlineflag 1 if last src line; 0 otherwise
+ * \return void
+ */
+static 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_uint32 rval1, rval2, rval3, rval4, gval1, gval2, gval3, gval4;
+l_uint32 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 */
+ }
+}
+
+
+/*------------------------------------------------------------------*
+ * 2x linear interpolated gray scaling *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleGray2xLILow()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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
+ *
+ * (2) 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.
+ * </pre>
+ */
+static 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);
+}
+
+
+/*!
+ * \brief scaleGray2xLILineLow()
+ *
+ * \param[in] lined ptr to top destline, to be made from current src line
+ * \param[in] wpld
+ * \param[in] lines ptr to current src line
+ * \param[in] ws
+ * \param[in] wpls
+ * \param[in] lastlineflag 1 if last src line; 0 otherwise
+ * \return void
+ */
+static 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_uint32 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) {\
+ lept_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 */
+ }
+}
+
+
+/*------------------------------------------------------------------*
+ * 4x linear interpolated gray scaling *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleGray4xLILow()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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
+ *
+ * (2) 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.
+ * </pre>
+ */
+static 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);
+}
+
+
+/*!
+ * \brief scaleGray4xLILineLow()
+ *
+ * \param[in] lined ptr to top destline, to be made from current src line
+ * \param[in] wpld
+ * \param[in] lines ptr to current src line
+ * \param[in] ws
+ * \param[in] wpls
+ * \param[in] lastlineflag 1 if last src line; 0 otherwise
+ * \return void
+ */
+static 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 */
+ }
+}
+
+
+/*------------------------------------------------------------------*
+ * Grayscale and color scaling by closest pixel sampling *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleBySamplingLow()
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static 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;
+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 */
+ memset(datad, 0, 4LL * hd * wpld);
+
+ /* 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) {
+ LEPT_FREE(srow);
+ 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(lined, prevlined, 4 * wpld);
+ }
+ prevlines = lines;
+ }
+
+ LEPT_FREE(srow);
+ LEPT_FREE(scol);
+ return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ * Color and grayscale downsampling with (antialias) smoothing *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleSmoothLow()
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static 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(datad, 0, 4LL * 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 labeled 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) {
+ LEPT_FREE(srow);
+ 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);
+ composeRGBPixel(rval, gval, bval, lined + j);
+ }
+ }
+ }
+
+ LEPT_FREE(srow);
+ LEPT_FREE(scol);
+ return 0;
+}
+
+
+/*!
+ * \brief scaleRGBToGray2Low()
+ *
+ * <pre>
+ * Notes:
+ * (1) This function is called with 32 bpp RGB src and 8 bpp,
+ * half-resolution dest. The weights should add to 1.0.
+ * </pre>
+ */
+static 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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleColorAreaMapLow()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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.
+ * </pre>
+ */
+static 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) lept_stderr("rval ovfl: %d\n", rval);
+ if (gval > 255) lept_stderr("gval ovfl: %d\n", gval);
+ if (bval > 255) lept_stderr("bval ovfl: %d\n", bval);
+#endif /* DEBUG_OVERFLOW */
+ composeRGBPixel(rval, gval, bval, lined + j);
+ }
+ }
+}
+
+
+/*!
+ * \brief scaleGrayAreaMapLow()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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.
+ * </pre>
+ */
+static 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) lept_stderr("val overflow: %d\n", val);
+#endif /* DEBUG_OVERFLOW */
+ SET_DATA_BYTE(lined, j, val);
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------*
+ * 2x area mapped downscaling *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleAreaMapLow2()
+ *
+ * <pre>
+ * Notes:
+ * (1) This function is called with either 8 bpp gray or 32 bpp RGB.
+ * The result is a 2x reduced dest.
+ * </pre>
+ */
+static 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;
+ }
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------*
+ * Binary scaling by closest pixel sampling *
+ *------------------------------------------------------------------*/
+/*
+ * \brief scaleBinaryLow()
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+static 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;
+l_int32 xs, prevxs, sval;
+l_int32 *srow, *scol;
+l_uint32 *lines, *prevlines, *lined, *prevlined;
+l_float32 wratio, hratio;
+
+ PROCNAME("scaleBinaryLow");
+
+ /* Clear dest */
+ memset(datad, 0, 4LL * hd * wpld);
+
+ /* 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) {
+ LEPT_FREE(srow);
+ 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(lined, prevlined, 4 * wpld);
+ }
+ prevlines = lines;
+ }
+
+ LEPT_FREE(srow);
+ LEPT_FREE(scol);
+ return 0;
+}
diff --git a/leptonica/src/scale2.c b/leptonica/src/scale2.c
new file mode 100644
index 00000000..43cd1ad1
--- /dev/null
+++ b/leptonica/src/scale2.c
@@ -0,0 +1,2347 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file scale2.c
+ * <pre>
+ * 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()
+ *
+ * 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()
+ *
+ * Low-level static functions:
+ *
+ * Scale-to-gray 2x
+ * static void scaleToGray2Low()
+ * static l_uint32 *makeSumTabSG2()
+ * static l_uint8 *makeValTabSG2()
+ *
+ * Scale-to-gray 3x
+ * static void scaleToGray3Low()
+ * static l_uint32 *makeSumTabSG3()
+ * static l_uint8 *makeValTabSG3()
+ *
+ * Scale-to-gray 4x
+ * static void scaleToGray4Low()
+ * static l_uint32 *makeSumTabSG4()
+ * static l_uint8 *makeValTabSG4()
+ *
+ * Scale-to-gray 6x
+ * static void scaleToGray6Low()
+ * static l_uint8 *makeValTabSG6()
+ *
+ * Scale-to-gray 8x
+ * static void scaleToGray8Low()
+ * static l_uint8 *makeValTabSG8()
+ *
+ * Scale-to-gray 16x
+ * static void scaleToGray16Low()
+ *
+ * Grayscale mipmap
+ * static l_int32 scaleMipmapLow()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static 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);
+static l_uint32 *makeSumTabSG2(void);
+static l_uint8 *makeValTabSG2(void);
+static 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);
+static l_uint32 *makeSumTabSG3(void);
+static l_uint8 *makeValTabSG3(void);
+static 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);
+static l_uint32 *makeSumTabSG4(void);
+static l_uint8 *makeValTabSG4(void);
+static 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);
+static l_uint8 *makeValTabSG6(void);
+static 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);
+static l_uint8 *makeValTabSG8(void);
+static void scaleToGray16Low(l_uint32 *datad, l_int32 wd, l_int32 hd,
+ l_int32 wpld, l_uint32 *datas, l_int32 wpls,
+ l_int32 *tab8);
+static 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);
+
+extern l_float32 AlphaMaskBorderVals[2];
+
+
+/*------------------------------------------------------------------*
+ * Scale-to-gray (1 bpp --> 8 bpp; arbitrary downscaling) *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleToGray()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] scalefactor reduction: must be > 0.0 and < 1.0
+ * \return pixd 8 bpp, scaled down by scalefactor in each direction,
+ * or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+/* lept_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 */
+/* lept_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 */
+/* lept_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 */
+/* lept_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 */
+/* lept_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 */
+/* lept_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 */
+/* lept_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;
+}
+
+
+/*!
+ * \brief pixScaleToGrayFast()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] scalefactor reduction: must be > 0.0 and < 1.0
+ * \return pixd 8 bpp, scaled down by scalefactor in each direction,
+ * or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.0001f;
+
+ /* 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) *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleToGray2()
+ *
+ * \param[in] 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);
+ pixSetPadBits(pixs, 0);
+ pixCopyInputFormat(pixd, pixs);
+ pixCopyResolution(pixd, pixs);
+ pixScaleResolution(pixd, 0.5, 0.5);
+ datas = pixGetData(pixs);
+ datad = pixGetData(pixd);
+ wpls = pixGetWpl(pixs);
+ wpld = pixGetWpl(pixd);
+
+ sumtab = makeSumTabSG2();
+ valtab = makeValTabSG2();
+ scaleToGray2Low(datad, wd, hd, wpld, datas, wpls, sumtab, valtab);
+ LEPT_FREE(sumtab);
+ LEPT_FREE(valtab);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixScaleToGray3()
+ *
+ * \param[in] pixs 1 bpp
+ * \return pixd 8 bpp, scaled down by 3x in each direction,
+ * or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.33333f, 0.33333f);
+ datas = pixGetData(pixs);
+ datad = pixGetData(pixd);
+ wpls = pixGetWpl(pixs);
+ wpld = pixGetWpl(pixd);
+
+ sumtab = makeSumTabSG3();
+ valtab = makeValTabSG3();
+ scaleToGray3Low(datad, wd, hd, wpld, datas, wpls, sumtab, valtab);
+ LEPT_FREE(sumtab);
+ LEPT_FREE(valtab);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixScaleToGray4()
+ *
+ * \param[in] pixs 1 bpp
+ * \return pixd 8 bpp, scaled down by 4x in each direction,
+ * or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) The width of pixd is truncated is truncated to a factor of 2.
+ * </pre>
+ */
+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);
+
+ sumtab = makeSumTabSG4();
+ valtab = makeValTabSG4();
+ scaleToGray4Low(datad, wd, hd, wpld, datas, wpls, sumtab, valtab);
+ LEPT_FREE(sumtab);
+ LEPT_FREE(valtab);
+ return pixd;
+}
+
+
+
+/*!
+ * \brief pixScaleToGray6()
+ *
+ * \param[in] pixs 1 bpp
+ * \return pixd 8 bpp, scaled down by 6x in each direction,
+ * or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) The width of pixd is truncated is truncated to a factor of 8.
+ * </pre>
+ */
+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.16667f, 0.16667f);
+ datas = pixGetData(pixs);
+ datad = pixGetData(pixd);
+ wpls = pixGetWpl(pixs);
+ wpld = pixGetWpl(pixd);
+
+ tab8 = makePixelSumTab8();
+ valtab = makeValTabSG6();
+ scaleToGray6Low(datad, wd, hd, wpld, datas, wpls, tab8, valtab);
+ LEPT_FREE(tab8);
+ LEPT_FREE(valtab);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixScaleToGray8()
+ *
+ * \param[in] 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);
+
+ tab8 = makePixelSumTab8();
+ valtab = makeValTabSG8();
+ scaleToGray8Low(datad, wd, hd, wpld, datas, wpls, tab8, valtab);
+ LEPT_FREE(tab8);
+ LEPT_FREE(valtab);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixScaleToGray16()
+ *
+ * \param[in] 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);
+
+ tab8 = makePixelSumTab8();
+ scaleToGray16Low(datad, wd, hd, wpld, datas, wpls, tab8);
+ LEPT_FREE(tab8);
+ return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ * Scale-to-gray mipmap(1 bpp --> 8 bpp, arbitrary reduction) *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleToGrayMipmap()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] scalefactor reduction: must be > 0.0 and < 1.0
+ * \return pixd 8 bpp, scaled down by scalefactor in each direction,
+ * or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleMipmap()
+ *
+ * \param[in] pixs1 high res 8 bpp, no cmap
+ * \param[in] pixs2 low res -- 2x reduced -- 8 bpp, no cmap
+ * \param[in] 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.
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes in pixScaleToGrayMipmap().
+ * (2) This function suffers from aliasing effects that are
+ * easily seen in document images.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief pixExpandReplicate()
+ *
+ * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp
+ * \param[in] 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, 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:
+ lept_stderr("invalid depth\n");
+ }
+
+ if (d == 32 && pixGetSpp(pixs) == 4)
+ pixScaleAndTransferAlpha(pixd, pixs, (l_float32)factor,
+ (l_float32)factor);
+ return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ * Downscaling using min or max *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleGrayMinMax()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \param[in] xfact x downscaling factor; integer
+ * \param[in] yfact y downscaling factor; integer
+ * \param[in] type L_CHOOSE_MIN, L_CHOOSE_MAX, L_CHOOSE_MAXDIFF
+ * \return pixd 8 bpp
+ *
+ * <pre>
+ * 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_MAXDIFF 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.
+ * </pre>
+ */
+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_MAXDIFF)
+ 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_MAXDIFF) {
+ 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_MAXDIFF) {
+ 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_MAXDIFF */
+ SET_DATA_BYTE(lined, j, maxval - minval);
+ }
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixScaleGrayMinMax2()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \param[in] type L_CHOOSE_MIN, L_CHOOSE_MAX, L_CHOOSE_MAXDIFF
+ * \return pixd 8 bpp downscaled by 2x
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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_MAXDIFF)
+ 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_MAXDIFF) {
+ minval = 255;
+ for (k = 0; k < 4; k++) {
+ if (val[k] < minval)
+ minval = val[k];
+ }
+ }
+ if (type == L_CHOOSE_MAX || type == L_CHOOSE_MAXDIFF) {
+ 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_MAXDIFF */
+ SET_DATA_BYTE(lined, j, maxval - minval);
+ }
+ }
+
+ return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ * Grayscale downscaling using rank value *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleGrayRankCascade()
+ *
+ * \param[in] pixs 8 bpp, not cmapped
+ * \param[in] level1, level2 ...
+ * \param[in] level3, level4 rank thresholds, in set {0, 1, 2, 3, 4}
+ * \return pixd 8 bpp, downscaled by up to 16x
+ *
+ * <pre>
+ * Notes:
+ * (1) This performs up to four cascaded 2x rank reductions.
+ * (2) Use level = 0 to truncate the cascade.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixScaleGrayRank2()
+ *
+ * \param[in] pixs 8 bpp, no cmap
+ * \param[in] rank 1 (darkest), 2, 3, 4 (lightest)
+ * \return pixd 8 bpp, downscaled by 2x
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleAndTransferAlpha()
+ *
+ * \param[in] pixd 32 bpp, scaled image
+ * \param[in] pixs 32 bpp, original unscaled image
+ * \param[in] scalex must be > 0.0
+ * \param[in] scaley must be > 0.0
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This scales the alpha component of pixs and inserts into pixd.
+ * </pre>
+ */
+l_ok
+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 *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief pixScaleWithAlpha()
+ *
+ * \param[in] pixs 32 bpp rgb or cmapped
+ * \param[in] scalex must be > 0.0
+ * \param[in] scaley must be > 0.0
+ * \param[in] pixg [optional] 8 bpp, can be null
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/* ================================================================ *
+ * Low level static functions *
+ * ================================================================ */
+
+/*------------------------------------------------------------------*
+ * Scale-to-gray 2x *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleToGray2Low()
+ *
+ * \param[in] datad dest data
+ * \param[in] wd, hd dest width, height
+ * \param[in] wpld dest words/line
+ * \param[in] datas src data
+ * \param[in] wpls src words/line
+ * \param[in] sumtab made from makeSumTabSG2()
+ * \param[in] valtab made from makeValTabSG2()
+ * \return 0 if OK; 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) 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.
+ * </pre>
+ */
+static 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)]);
+ }
+ }
+
+ }
+}
+
+
+/*!
+ * \brief makeSumTabSG2()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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().
+ * </pre>
+ */
+static 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("tab not made", 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;
+}
+
+
+/*!
+ * \brief makeValTabSG2()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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}
+ * </pre>
+ */
+static 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("tab not made", procName, NULL);
+ for (i = 0; i < 5; i++)
+ tab[i] = 255 - (i * 255) / 4;
+ return tab;
+}
+
+
+/*------------------------------------------------------------------*
+ * Scale-to-gray 3x *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleToGray3Low()
+ *
+ * \param[in] datad dest data
+ * \param[in] wd, hd dest width, height
+ * \param[in] wpld dest words/line
+ * \param[in] datas src data
+ * \param[in] wpls src words/line
+ * \param[in] sumtab made from makeSumTabSG3()
+ * \param[in] valtab made from makeValTabSG3()
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) 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.
+ * (2) 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).
+ * </pre>
+ */
+static 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)]);
+ }
+ }
+}
+
+
+
+/*!
+ * \brief makeSumTabSG3()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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().
+ * </pre>
+ */
+static 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("tab not made", 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;
+}
+
+
+/*!
+ * \brief makeValTabSG3()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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 [0,...,9]
+ * </pre>
+ */
+static 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("tab not made", procName, NULL);
+ for (i = 0; i < 10; i++)
+ tab[i] = 0xff - (i * 255) / 9;
+ return tab;
+}
+
+
+/*------------------------------------------------------------------*
+ * Scale-to-gray 4x *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleToGray4Low()
+ *
+ * \param[in] datad dest data
+ * \param[in] wd, hd dest width, height
+ * \param[in] wpld dest words/line
+ * \param[in] datas src data
+ * \param[in] wpls src words/line
+ * \param[in] sumtab made from makeSumTabSG4()
+ * \param[in] valtab made from makeValTabSG4()
+ * \return 0 if OK; 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) 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.
+ * </pre>
+ */
+static 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)]);
+ }
+ }
+}
+
+
+/*!
+ * \brief makeSumTabSG4()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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().
+ * </pre>
+ */
+static 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("tab not made", 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;
+}
+
+
+/*!
+ * \brief makeValTabSG4()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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 [0,...,16]
+ * </pre>
+ */
+static 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("tab not made", procName, NULL);
+ for (i = 0; i < 17; i++)
+ tab[i] = 0xff - (i * 255) / 16;
+ return tab;
+}
+
+
+/*------------------------------------------------------------------*
+ * Scale-to-gray 6x *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleToGray6Low()
+ *
+ * \param[in] datad dest data
+ * \param[in] wd, hd dest width, height
+ * \param[in] wpld dest words/line
+ * \param[in] datas src data
+ * \param[in] wpls src words/line
+ * \param[in] tab8 made from makePixelSumTab8()
+ * \param[in] valtab made from makeValTabSG6()
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) 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.
+ * (2) 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).
+ * </pre>
+ */
+static 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)]);
+ }
+ }
+}
+
+
+/*!
+ * \brief makeValTabSG6()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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 [0,...,36]
+ * </pre>
+ */
+static 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("tab not made", procName, NULL);
+ for (i = 0; i < 37; i++)
+ tab[i] = 0xff - (i * 255) / 36;
+ return tab;
+}
+
+
+/*------------------------------------------------------------------*
+ * Scale-to-gray 8x *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleToGray8Low()
+ *
+ * \param[in] datad dest data
+ * \param[in] wd, hd dest width, height
+ * \param[in] wpld dest words/line
+ * \param[in] datas src data
+ * \param[in] wpls src words/line
+ * \param[in] tab8 made from makePixelSumTab8()
+ * \param[in] valtab made from makeValTabSG8()
+ * \return 0 if OK; 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) 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 and 255: 0 for all 64 bits ON and 255
+ * for all 64 bits OFF.
+ * </pre>
+ */
+static 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]);
+ }
+ }
+}
+
+
+/*!
+ * \brief makeValTabSG8()
+ *
+ * <pre>
+ * Notes:
+ * (1) 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 [0,...,64]
+ * </pre>
+ */
+static 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("tab not made", procName, NULL);
+ for (i = 0; i < 65; i++)
+ tab[i] = 0xff - (i * 255) / 64;
+ return tab;
+}
+
+
+/*------------------------------------------------------------------*
+ * Scale-to-gray 16x *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleToGray16Low()
+ *
+ * \param[in] datad dest data
+ * \param[in] wd, hd dest width, height
+ * \param[in] wpld dest words/line
+ * \param[in] datas src data
+ * \param[in] wpls src words/line
+ * \param[in] tab8 made from makePixelSumTab8()
+ * \return 0 if OK; 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) 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.
+ * </pre>
+ */
+static 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);
+ }
+ }
+}
+
+
+
+/*------------------------------------------------------------------*
+ * Grayscale mipmap *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief scaleMipmapLow()
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes in scale.c for pixScaleToGrayMipmap(). This function
+ * is here for pedagogical reasons. It gives poor results on document
+ * images because of aliasing.
+ * </pre>
+ */
+static 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(datad, 0, 4LL * 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 labeled 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) {
+ LEPT_FREE(srow);
+ 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/leptonica/src/seedfill.c b/leptonica/src/seedfill.c
new file mode 100644
index 00000000..b667ad17
--- /dev/null
+++ b/leptonica/src/seedfill.c
@@ -0,0 +1,3456 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file seedfill.c
+ * <pre>
+ *
+ * Binary seedfill (source: Luc Vincent)
+ * PIX *pixSeedfillBinary()
+ * PIX *pixSeedfillBinaryRestricted()
+ * static void seedfillBinaryLow()
+ *
+ * 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()
+ * static void seedfillGrayLow()
+ * static void seedfillGrayInvLow()
+
+ *
+ * Gray seedfill (source: Luc Vincent: sequential-reconstruction algorithm)
+ * l_int32 pixSeedfillGraySimple()
+ * l_int32 pixSeedfillGrayInvSimple()
+ * static void seedfillGrayLowSimple()
+ * static void seedfillGrayInvLowSimple()
+ *
+ * Gray seedfill variations
+ * PIX *pixSeedfillGrayBasin()
+ *
+ * Distance function (source: Luc Vincent)
+ * PIX *pixDistanceFunction()
+ * static void distanceFunctionLow()
+ *
+ * Seed spread (based on distance function)
+ * PIX *pixSeedspread()
+ * static void seedspreadLow()
+ *
+ * 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.)
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+struct L_Pixel
+{
+ l_int32 x;
+ l_int32 y;
+};
+typedef struct L_Pixel L_PIXEL;
+
+static void seedfillBinaryLow(l_uint32 *datas, l_int32 hs, l_int32 wpls,
+ l_uint32 *datam, l_int32 hm, l_int32 wplm,
+ l_int32 connectivity);
+static void seedfillGrayLow(l_uint32 *datas, l_int32 w, l_int32 h,
+ l_int32 wpls, l_uint32 *datam, l_int32 wplm,
+ l_int32 connectivity);
+static void seedfillGrayInvLow(l_uint32 *datas, l_int32 w, l_int32 h,
+ l_int32 wpls, l_uint32 *datam, l_int32 wplm,
+ l_int32 connectivity);
+static void seedfillGrayLowSimple(l_uint32 *datas, l_int32 w, l_int32 h,
+ l_int32 wpls, l_uint32 *datam, l_int32 wplm,
+ l_int32 connectivity);
+static void seedfillGrayInvLowSimple(l_uint32 *datas, l_int32 w, l_int32 h,
+ l_int32 wpls, l_uint32 *datam,
+ l_int32 wplm, l_int32 connectivity);
+static void distanceFunctionLow(l_uint32 *datad, l_int32 w, l_int32 h,
+ l_int32 d, l_int32 wpld, l_int32 connectivity);
+static void seedspreadLow(l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld,
+ l_uint32 *datat, l_int32 wplt, l_int32 connectivity);
+
+
+static l_int32 pixQualifyLocalMinima(PIX *pixs, PIX *pixm, l_int32 maxval);
+
+#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 MaxIters = 40;
+
+
+/*-----------------------------------------------------------------------*
+ * Vincent's Iterative Binary Seedfill method *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixSeedfillBinary()
+ *
+ * \param[in] pixd [optional]; can be null, equal to pixs,
+ * or different from pixs; 1 bpp
+ * \param[in] pixs 1 bpp seed
+ * \param[in] pixm 1 bpp filling mask
+ * \param[in] connectivity 4 or 8
+ * \return pixd always
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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);
+ pixSetPadBits(pixd, 0); /* be safe: */
+ pixSetPadBits(pixm, 0); /* avoid using uninitialized memory */
+
+ /* 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);
+
+
+ for (i = 0; i < MaxIters; i++) {
+ pixCopy(pixt, pixd);
+ seedfillBinaryLow(datad, hd, wpld, datam, hm, wplm, connectivity);
+ pixEqual(pixd, pixt, &boolval);
+ if (boolval == 1) {
+#if DEBUG_PRINT_ITERS
+ lept_stderr("Binary seed fill converged: %d iters\n", i + 1);
+#endif /* DEBUG_PRINT_ITERS */
+ break;
+ }
+ }
+
+ pixDestroy(&pixt);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixSeedfillBinaryRestricted()
+ *
+ * \param[in] pixd [optional]; can be null, equal to pixs,
+ * or different from pixs; 1 bpp
+ * \param[in] pixs 1 bpp seed
+ * \param[in] pixm 1 bpp filling mask
+ * \param[in] connectivity 4 or 8
+ * \param[in] xmax max distance in x direction of fill into mask
+ * \param[in] ymax max distance in y direction of fill into mask
+ * \return pixd always
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief 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.
+ */
+static 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);
+ }
+}
+
+
+/*!
+ * \brief pixHolesByFilling()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ pixDestroy(&pixd);
+ 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;
+}
+
+
+/*!
+ * \brief pixFillClosedBorders()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] connectivity filling connectivity 4 or 8
+ * \return pixd all topologically outer closed borders are filled
+ * as connected comonents, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ pixDestroy(&pixd);
+ return (PIX *)ERROR_PTR("pixsi not made", procName, NULL);
+ }
+
+ pixSeedfillBinary(pixd, pixd, pixsi, connectivity);
+ pixInvert(pixd, pixd);
+ pixDestroy(&pixsi);
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixExtractBorderConnComps()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] connectivity 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;
+}
+
+
+/*!
+ * \brief pixRemoveBorderConnComps()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] connectivity filling connectivity 4 or 8
+ * \return pixd all pixels in the src that are not touching the
+ * border or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This removes all fg components touching the border.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixFillBgFromBorder()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] connectivity filling connectivity 4 or 8
+ * \return pixd with the background c.c. touching the border
+ * filled to foreground, or NULL on error
+ *
+ * <pre>
+ * 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:
+ * \code
+ * pix1 = <initial image>
+ * for (i = 0; i < 2 * n; i++) {
+ * pix2 = pixFillBgFromBorder(pix1, 8);
+ * pixInvert(pix2, pix2);
+ * pixDestroy(&pix1);
+ * pix1 = pix2;
+ * }
+ * \endcode
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixFillHolesToBoundingRect()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] minsize min number of pixels in the hole
+ * \param[in] maxhfract max hole area as fraction of fg pixels in the cc
+ * \param[in] minfgfract min fg area as fraction of bounding rectangle
+ * \return pixd with some holes possibly filled and some c.c. possibly
+ * expanded to their bounding rects, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This does not fill holes that are smaller in area than 'minsize'.
+ * Use %minsize = 0 and %maxhfract = 1.0 to fill all holes.
+ * (2) This does not fill holes with an area larger than
+ * %maxhfract times the fg area of the c.c.
+ * Use 1.0 to fill all holes.
+ * (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. Use 1.0 to skip expanding to 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.
+ * </pre>
+ */
+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);
+ maxhfract = L_MIN(L_MAX(maxhfract, 0.0), 1.0);
+ minfgfract = L_MIN(L_MAX(minfgfract, 0.0), 1.0);
+
+ 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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixSeedfillGray()
+ *
+ * \param[in] pixs 8 bpp seed; filled in place
+ * \param[in] pixm 8 bpp filling mask
+ * \param[in] connectivity 4 or 8
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSeedfillGrayInv()
+ *
+ * \param[in] pixs 8 bpp seed; filled in place
+ * \param[in] pixm 8 bpp filling mask
+ * \param[in] connectivity 4 or 8
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief 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.
+ */
+static 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);
+ }
+
+ lqueueDestroy(&lq_pixel, TRUE);
+}
+
+
+/*!
+ * \brief 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 propagated 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.
+ */
+static 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);
+ }
+
+ lqueueDestroy(&lq_pixel, TRUE);
+}
+
+
+/*-----------------------------------------------------------------------*
+ * Vincent's Iterative Grayscale Seedfill method *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixSeedfillGraySimple()
+ *
+ * \param[in] pixs 8 bpp seed; filled in place
+ * \param[in] pixm 8 bpp filling mask
+ * \param[in] connectivity 4 or 8
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 < MaxIters; 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;
+}
+
+
+/*!
+ * \brief pixSeedfillGrayInvSimple()
+ *
+ * \param[in] pixs 8 bpp seed; filled in place
+ * \param[in] pixm 8 bpp filling mask
+ * \param[in] connectivity 4 or 8
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 < MaxIters; 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;
+}
+
+
+/*!
+ * \brief 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.
+ */
+static 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);
+ }
+}
+
+
+/*!
+ * \brief 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.
+ */
+static 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);
+ }
+}
+
+
+/*-----------------------------------------------------------------------*
+ * Gray seedfill variations *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixSeedfillGrayBasin()
+ *
+ * \param[in] pixb binary mask giving seed locations
+ * \param[in] pixm 8 bpp basin-type filling mask
+ * \param[in] delta amount of seed value above mask
+ * \param[in] connectivity 4 or 8
+ * \return pixd filled seed if OK, NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixDistanceFunction()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] connectivity 4 or 8
+ * \param[in] outdepth 8 or 16 bits for pixd
+ * \param[in] boundcond L_BOUNDARY_BG, L_BOUNDARY_FG
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief distanceFunctionLow()
+ */
+static 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);
+ }
+}
+
+
+/*-----------------------------------------------------------------------*
+ * Seed spread (based on distance function) *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixSeedspread()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] connectivity 4 or 8
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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, L_LS_BYTE) 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;
+}
+
+
+/*!
+ * \brief seedspreadLow()
+ *
+ * See pixSeedspread() for a brief description of the algorithm here.
+ */
+static 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;
+ }
+}
+
+
+/*-----------------------------------------------------------------------*
+ * Local extrema *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixLocalExtrema()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] maxmin max allowed for the min in a 3x3 neighborhood;
+ * use 0 for default which is to have no upper bound
+ * \param[in] minmax min allowed for the max in a 3x3 neighborhood;
+ * use 0 for default which is to have no lower bound
+ * \param[out] ppixmin [optional] mask of local minima
+ * \param[out] ppixmax [optional] mask of local maxima
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixQualifyLocalMinima()
+ *
+ * \param[in] pixs 8 bpp image from which pixm has been extracted
+ * \param[in] pixm 1 bpp mask of values equal to min in 3x3 neighborhood
+ * \param[in] 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
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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 *pix1, *pix2, *pix3;
+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);
+ pix1 = pixaGetPix(pixa, k, L_COPY);
+ pix2 = pixAddBorder(pix1, 1, 0);
+ pix3 = pixDilateBrick(NULL, pix2, 3, 3);
+ pixXor(pix3, pix3, pix2); /* exterior boundary pixels */
+ datac = pixGetData(pix3);
+ wplc = pixGetWpl(pix3);
+ nextOnPixelInRaster(pix1, 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, pix1, 0, 0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ 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, pix1, 0, 0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ }
+
+ boxaDestroy(&boxa);
+ pixaDestroy(&pixa);
+ return 0;
+}
+
+
+/*!
+ * \brief pixSelectedLocalExtrema()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] mindist -1 for keeping all pixels; >= 0 specifies distance
+ * \param[out] ppixmin mask of local minima
+ * \param[out] ppixmax mask of local maxima
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixFindEqualValues()
+ *
+ * \param[in] pixs1 8 bpp
+ * \param[in] pixs2 8 bpp
+ * \return pixd 1 bpp mask, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixSelectMinInConnComp()
+ *
+ * \param[in] pixs 8 bpp
+ * \param[in] pixm 1 bpp
+ * \param[out] ppta pta of min pixel locations
+ * \param[out] pnav [optional] numa of minima values
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixRemoveSeededComponents()
+ *
+ * \param[in] pixd [optional]; can be null or equal to pixm; 1 bpp
+ * \param[in] pixs 1 bpp seed
+ * \param[in] pixm 1 bpp filling mask
+ * \param[in] connectivity 4 or 8
+ * \param[in] bordersize amount of border clearing
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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/leptonica/src/sel1.c b/leptonica/src/sel1.c
new file mode 100644
index 00000000..492972cd
--- /dev/null
+++ b/leptonica/src/sel1.c
@@ -0,0 +1,2446 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file sel1.c
+ * <pre>
+ *
+ * Basic ops on Sels and Selas
+ *
+ * Create/destroy/copy:
+ * SELA *selaCreate()
+ * void selaDestroy()
+ * SEL *selCreate()
+ * void selDestroy()
+ * SEL *selCopy()
+ * SEL *selCreateBrick()
+ * SEL *selCreateComb()
+ *
+ * Helper proc:
+ * l_int32 **create2dIntArray()
+ *
+ * Extension of sela:
+ * SELA *selaAddSel()
+ * static l_int32 selaExtendArray()
+ *
+ * Accessors:
+ * l_int32 selaGetCount()
+ * SEL *selaGetSel()
+ * char *selGetName()
+ * l_int32 selSetName()
+ * l_int32 selaFindSelByName()
+ * l_int32 selGetElement()
+ * l_int32 selSetElement()
+ * l_int32 selGetParameters()
+ * l_int32 selSetOrigin()
+ * l_int32 selGetTypeAtOrigin()
+ * char *selaGetBrickName()
+ * char *selaGetCombName()
+ * static char *selaComputeCompositeParameters()
+ * l_int32 getCompositeParameters()
+ * SARRAY *selaGetSelnames()
+ *
+ * Max translations for erosion and hmt
+ * l_int32 selFindMaxTranslations()
+ *
+ * Rotation by multiples of 90 degrees
+ * SEL *selRotateOrth()
+ *
+ * Sela and Sel serialized I/O
+ * SELA *selaRead()
+ * SELA *selaReadStream()
+ * SEL *selRead()
+ * SEL *selReadStream()
+ * l_int32 selaWrite()
+ * l_int32 selaWriteStream()
+ * l_int32 selWrite()
+ * l_int32 selWriteStream()
+ *
+ * Building custom hit-miss sels from compiled strings
+ * SEL *selCreateFromString()
+ * char *selPrintToString() [for debugging]
+ *
+ * Building custom hit-miss sels from a simple file format
+ * SELA *selaCreateFromFile()
+ * static SEL *selCreateFromSArray()
+ *
+ * Making hit-only sels from Pta and Pix
+ * SEL *selCreateFromPta()
+ * SEL *selCreateFromPix()
+ *
+ * Making hit-miss sels from Pix and image files
+ * SEL *selReadFromColorImage()
+ * SEL *selCreateFromColorPix()
+ SELA *selaCreateFromColorPixa()
+ *
+ * Printable display of sel
+ * PIX *selDisplayInPix()
+ * PIX *selaDisplayInPix()
+ *
+ * Usage notes:
+ * In this file we have seven functions that make sels:
+ * (1) selCreate(), with input (h, w, [name])
+ * The generic function. Roll your own, using selSetElement().
+ * (2) selCreateBrick(), with input (h, w, cy, cx, val)
+ * The most popular function. Makes a rectangular sel of
+ * all hits, misses or don't-cares. We have many morphology
+ * operations that create a sel of all hits, use it, and
+ * destroy it.
+ * (3) selCreateFromString() with input (text, h, w, [name])
+ * Adam Langley's clever function, allows you to make a hit-miss
+ * sel from a string in code that is geometrically laid out
+ * just like the actual sel.
+ * (4) selaCreateFromFile() with input (filename)
+ * This parses a simple file format to create an array of
+ * hit-miss sels. The sel data uses the same encoding
+ * as in (3), with geometrical layout enforced.
+ * (5) selCreateFromPta() with input (pta, cy, cx, [name])
+ * Another way to make a sel with only hits.
+ * (6) selCreateFromPix() with input (pix, cy, cx, [name])
+ * Yet another way to make a sel from hits.
+ * (7) selCreateFromColorPix() with input (pix, name).
+ * Another way to make a general hit-miss sel, starting with
+ * an image editor.
+ * In addition, there are three functions in selgen.c that
+ * automatically generate a hit-miss sel from a pix and
+ * a number of parameters. This is useful for problems like
+ * "find all patterns that look like this one."
+ *
+ * Consistency, being the hobgoblin of small minds,
+ * is adhered to here in the dimensioning and accessing of sels.
+ * Everything is done in standard matrix (row, column) order.
+ * When we set specific elements in a sel, we likewise use
+ * (row, col) ordering:
+ * selSetElement(), with input (row, col, type)
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Bounds on sel ptr array size */
+static const l_uint32 MaxPtrArraySize = 10000;
+static const l_int32 InitialPtrArraySize = 50; /*!< n'importe quoi */
+
+ /* Bounds on kernel size */
+static const l_uint32 MaxKernelSize = 10000;
+
+ /* Bounds on pix template size */
+static const l_uint32 MaxPixTemplateSize = 100;
+static const l_uint32 MaxPixTemplateHits = 1000;
+
+ /* Static functions */
+static l_int32 selaExtendArray(SELA *sela);
+static SEL *selCreateFromSArray(SARRAY *sa, l_int32 first, l_int32 last);
+
+struct CompParameterMap
+{
+ l_int32 size;
+ l_int32 size1;
+ l_int32 size2;
+ char selnameh1[20];
+ char selnameh2[20];
+ char selnamev1[20];
+ char selnamev2[20];
+};
+
+static const struct CompParameterMap comp_parameter_map[] =
+ { { 2, 2, 1, "sel_2h", "", "sel_2v", "" },
+ { 3, 3, 1, "sel_3h", "", "sel_3v", "" },
+ { 4, 2, 2, "sel_2h", "sel_comb_4h", "sel_2v", "sel_comb_4v" },
+ { 5, 5, 1, "sel_5h", "", "sel_5v", "" },
+ { 6, 3, 2, "sel_3h", "sel_comb_6h", "sel_3v", "sel_comb_6v" },
+ { 7, 7, 1, "sel_7h", "", "sel_7v", "" },
+ { 8, 4, 2, "sel_4h", "sel_comb_8h", "sel_4v", "sel_comb_8v" },
+ { 9, 3, 3, "sel_3h", "sel_comb_9h", "sel_3v", "sel_comb_9v" },
+ { 10, 5, 2, "sel_5h", "sel_comb_10h", "sel_5v", "sel_comb_10v" },
+ { 11, 4, 3, "sel_4h", "sel_comb_12h", "sel_4v", "sel_comb_12v" },
+ { 12, 4, 3, "sel_4h", "sel_comb_12h", "sel_4v", "sel_comb_12v" },
+ { 13, 4, 3, "sel_4h", "sel_comb_12h", "sel_4v", "sel_comb_12v" },
+ { 14, 7, 2, "sel_7h", "sel_comb_14h", "sel_7v", "sel_comb_14v" },
+ { 15, 5, 3, "sel_5h", "sel_comb_15h", "sel_5v", "sel_comb_15v" },
+ { 16, 4, 4, "sel_4h", "sel_comb_16h", "sel_4v", "sel_comb_16v" },
+ { 17, 4, 4, "sel_4h", "sel_comb_16h", "sel_4v", "sel_comb_16v" },
+ { 18, 6, 3, "sel_6h", "sel_comb_18h", "sel_6v", "sel_comb_18v" },
+ { 19, 5, 4, "sel_5h", "sel_comb_20h", "sel_5v", "sel_comb_20v" },
+ { 20, 5, 4, "sel_5h", "sel_comb_20h", "sel_5v", "sel_comb_20v" },
+ { 21, 7, 3, "sel_7h", "sel_comb_21h", "sel_7v", "sel_comb_21v" },
+ { 22, 11, 2, "sel_11h", "sel_comb_22h", "sel_11v", "sel_comb_22v" },
+ { 23, 6, 4, "sel_6h", "sel_comb_24h", "sel_6v", "sel_comb_24v" },
+ { 24, 6, 4, "sel_6h", "sel_comb_24h", "sel_6v", "sel_comb_24v" },
+ { 25, 5, 5, "sel_5h", "sel_comb_25h", "sel_5v", "sel_comb_25v" },
+ { 26, 5, 5, "sel_5h", "sel_comb_25h", "sel_5v", "sel_comb_25v" },
+ { 27, 9, 3, "sel_9h", "sel_comb_27h", "sel_9v", "sel_comb_27v" },
+ { 28, 7, 4, "sel_7h", "sel_comb_28h", "sel_7v", "sel_comb_28v" },
+ { 29, 6, 5, "sel_6h", "sel_comb_30h", "sel_6v", "sel_comb_30v" },
+ { 30, 6, 5, "sel_6h", "sel_comb_30h", "sel_6v", "sel_comb_30v" },
+ { 31, 6, 5, "sel_6h", "sel_comb_30h", "sel_6v", "sel_comb_30v" },
+ { 32, 8, 4, "sel_8h", "sel_comb_32h", "sel_8v", "sel_comb_32v" },
+ { 33, 11, 3, "sel_11h", "sel_comb_33h", "sel_11v", "sel_comb_33v" },
+ { 34, 7, 5, "sel_7h", "sel_comb_35h", "sel_7v", "sel_comb_35v" },
+ { 35, 7, 5, "sel_7h", "sel_comb_35h", "sel_7v", "sel_comb_35v" },
+ { 36, 6, 6, "sel_6h", "sel_comb_36h", "sel_6v", "sel_comb_36v" },
+ { 37, 6, 6, "sel_6h", "sel_comb_36h", "sel_6v", "sel_comb_36v" },
+ { 38, 6, 6, "sel_6h", "sel_comb_36h", "sel_6v", "sel_comb_36v" },
+ { 39, 13, 3, "sel_13h", "sel_comb_39h", "sel_13v", "sel_comb_39v" },
+ { 40, 8, 5, "sel_8h", "sel_comb_40h", "sel_8v", "sel_comb_40v" },
+ { 41, 7, 6, "sel_7h", "sel_comb_42h", "sel_7v", "sel_comb_42v" },
+ { 42, 7, 6, "sel_7h", "sel_comb_42h", "sel_7v", "sel_comb_42v" },
+ { 43, 7, 6, "sel_7h", "sel_comb_42h", "sel_7v", "sel_comb_42v" },
+ { 44, 11, 4, "sel_11h", "sel_comb_44h", "sel_11v", "sel_comb_44v" },
+ { 45, 9, 5, "sel_9h", "sel_comb_45h", "sel_9v", "sel_comb_45v" },
+ { 46, 9, 5, "sel_9h", "sel_comb_45h", "sel_9v", "sel_comb_45v" },
+ { 47, 8, 6, "sel_8h", "sel_comb_48h", "sel_8v", "sel_comb_48v" },
+ { 48, 8, 6, "sel_8h", "sel_comb_48h", "sel_8v", "sel_comb_48v" },
+ { 49, 7, 7, "sel_7h", "sel_comb_49h", "sel_7v", "sel_comb_49v" },
+ { 50, 10, 5, "sel_10h", "sel_comb_50h", "sel_10v", "sel_comb_50v" },
+ { 51, 10, 5, "sel_10h", "sel_comb_50h", "sel_10v", "sel_comb_50v" },
+ { 52, 13, 4, "sel_13h", "sel_comb_52h", "sel_13v", "sel_comb_52v" },
+ { 53, 9, 6, "sel_9h", "sel_comb_54h", "sel_9v", "sel_comb_54v" },
+ { 54, 9, 6, "sel_9h", "sel_comb_54h", "sel_9v", "sel_comb_54v" },
+ { 55, 11, 5, "sel_11h", "sel_comb_55h", "sel_11v", "sel_comb_55v" },
+ { 56, 8, 7, "sel_8h", "sel_comb_56h", "sel_8v", "sel_comb_56v" },
+ { 57, 8, 7, "sel_8h", "sel_comb_56h", "sel_8v", "sel_comb_56v" },
+ { 58, 8, 7, "sel_8h", "sel_comb_56h", "sel_8v", "sel_comb_56v" },
+ { 59, 10, 6, "sel_10h", "sel_comb_60h", "sel_10v", "sel_comb_60v" },
+ { 60, 10, 6, "sel_10h", "sel_comb_60h", "sel_10v", "sel_comb_60v" },
+ { 61, 10, 6, "sel_10h", "sel_comb_60h", "sel_10v", "sel_comb_60v" },
+ { 62, 9, 7, "sel_9h", "sel_comb_63h", "sel_9v", "sel_comb_63v" },
+ { 63, 9, 7, "sel_9h", "sel_comb_63h", "sel_9v", "sel_comb_63v" } };
+
+
+
+/*------------------------------------------------------------------------*
+ * Create / Destroy / Copy *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief selaCreate()
+ *
+ * \param[in] n initial number of sel ptrs; use 0 for default
+ * \return sela, or NULL on error
+ */
+SELA *
+selaCreate(l_int32 n)
+{
+SELA *sela;
+
+ PROCNAME("selaCreate");
+
+ if (n <= 0 || n > MaxPtrArraySize)
+ n = InitialPtrArraySize;
+
+ /* Make array of sel ptrs */
+ sela = (SELA *)LEPT_CALLOC(1, sizeof(SELA));
+ sela->nalloc = n;
+ sela->n = 0;
+ if ((sela->sel = (SEL **)LEPT_CALLOC(n, sizeof(SEL *))) == NULL) {
+ LEPT_FREE(sela);
+ return (SELA *)ERROR_PTR("sel ptrs not made", procName, NULL);
+ }
+ return sela;
+}
+
+
+/*!
+ * \brief selaDestroy()
+ *
+ * \param[in,out] psela will be set to null before returning
+ * \return void
+ */
+void
+selaDestroy(SELA **psela)
+{
+SELA *sela;
+l_int32 i;
+
+ if (!psela) return;
+ if ((sela = *psela) == NULL)
+ return;
+
+ for (i = 0; i < sela->n; i++)
+ selDestroy(&sela->sel[i]);
+ LEPT_FREE(sela->sel);
+ LEPT_FREE(sela);
+ *psela = NULL;
+}
+
+
+/*!
+ * \brief selCreate()
+ *
+ * \param[in] height
+ * \param[in] width
+ * \param[in] name [optional] sel name; can be null
+ * \return sel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) selCreate() initializes all values to 0.
+ * (2) After this call, (cy,cx) and nonzero data values must be
+ * assigned. If a text name is not assigned here, it will
+ * be needed later when the sel is put into a sela.
+ * </pre>
+ */
+SEL *
+selCreate(l_int32 height,
+ l_int32 width,
+ const char *name)
+{
+SEL *sel;
+
+ PROCNAME("selCreate");
+
+ if ((sel = (SEL *)LEPT_CALLOC(1, sizeof(SEL))) == NULL)
+ return (SEL *)ERROR_PTR("sel not made", procName, NULL);
+ if (name)
+ sel->name = stringNew(name);
+ sel->sy = height;
+ sel->sx = width;
+ if ((sel->data = create2dIntArray(height, width)) == NULL) {
+ LEPT_FREE(sel->name);
+ LEPT_FREE(sel);
+ return (SEL *)ERROR_PTR("data not allocated", procName, NULL);
+ }
+
+ return sel;
+}
+
+
+/*!
+ * \brief selDestroy()
+ *
+ * \param[in,out] psel will be set to null before returning
+ * \return void
+ */
+void
+selDestroy(SEL **psel)
+{
+l_int32 i;
+SEL *sel;
+
+ PROCNAME("selDestroy");
+
+ if (psel == NULL) {
+ L_WARNING("ptr address is NULL!\n", procName);
+ return;
+ }
+ if ((sel = *psel) == NULL)
+ return;
+
+ for (i = 0; i < sel->sy; i++)
+ LEPT_FREE(sel->data[i]);
+ LEPT_FREE(sel->data);
+ if (sel->name)
+ LEPT_FREE(sel->name);
+ LEPT_FREE(sel);
+ *psel = NULL;
+}
+
+
+/*!
+ * \brief selCopy()
+ *
+ * \param[in] sel
+ * \return a copy of the sel, or NULL on error
+ */
+SEL *
+selCopy(SEL *sel)
+{
+l_int32 sx, sy, cx, cy, i, j;
+SEL *csel;
+
+ PROCNAME("selCopy");
+
+ if (!sel)
+ return (SEL *)ERROR_PTR("sel not defined", procName, NULL);
+
+ if ((csel = (SEL *)LEPT_CALLOC(1, sizeof(SEL))) == NULL)
+ return (SEL *)ERROR_PTR("csel not made", procName, NULL);
+ selGetParameters(sel, &sy, &sx, &cy, &cx);
+ csel->sy = sy;
+ csel->sx = sx;
+ csel->cy = cy;
+ csel->cx = cx;
+
+ if ((csel->data = create2dIntArray(sy, sx)) == NULL) {
+ LEPT_FREE(csel);
+ return (SEL *)ERROR_PTR("sel data not made", procName, NULL);
+ }
+
+ for (i = 0; i < sy; i++)
+ for (j = 0; j < sx; j++)
+ csel->data[i][j] = sel->data[i][j];
+
+ if (sel->name)
+ csel->name = stringNew(sel->name);
+
+ return csel;
+}
+
+
+/*!
+ * \brief selCreateBrick()
+ *
+ * \param[in] h, w height, width
+ * \param[in] cy, cx origin, relative to UL corner at 0,0
+ * \param[in] type SEL_HIT, SEL_MISS, or SEL_DONT_CARE
+ * \return sel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a rectangular sel of all hits, misses or don't cares.
+ * </pre>
+ */
+SEL *
+selCreateBrick(l_int32 h,
+ l_int32 w,
+ l_int32 cy,
+ l_int32 cx,
+ l_int32 type)
+{
+l_int32 i, j;
+SEL *sel;
+
+ PROCNAME("selCreateBrick");
+
+ if (h <= 0 || w <= 0)
+ return (SEL *)ERROR_PTR("h and w must both be > 0", procName, NULL);
+ if (type != SEL_HIT && type != SEL_MISS && type != SEL_DONT_CARE)
+ return (SEL *)ERROR_PTR("invalid sel element type", procName, NULL);
+
+ if ((sel = selCreate(h, w, NULL)) == NULL)
+ return (SEL *)ERROR_PTR("sel not made", procName, NULL);
+ selSetOrigin(sel, cy, cx);
+ for (i = 0; i < h; i++)
+ for (j = 0; j < w; j++)
+ sel->data[i][j] = type;
+
+ return sel;
+}
+
+
+/*!
+ * \brief selCreateComb()
+ *
+ * \param[in] factor1 contiguous space between comb tines
+ * \param[in] factor2 number of comb tines
+ * \param[in] direction L_HORIZ, L_VERT
+ * \return sel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This generates a comb Sel of hits with the origin as
+ * near the center as possible.
+ * (2) In use, this is complemented by a brick sel of size %factor1,
+ * Both brick and comb sels are made by selectComposableSels().
+ * </pre>
+ */
+SEL *
+selCreateComb(l_int32 factor1,
+ l_int32 factor2,
+ l_int32 direction)
+{
+l_int32 i, size, z;
+SEL *sel;
+
+ PROCNAME("selCreateComb");
+
+ if (factor1 < 1 || factor2 < 1)
+ return (SEL *)ERROR_PTR("factors must be >= 1", procName, NULL);
+ if (direction != L_HORIZ && direction != L_VERT)
+ return (SEL *)ERROR_PTR("invalid direction", procName, NULL);
+
+ size = factor1 * factor2;
+ if (direction == L_HORIZ) {
+ if ((sel = selCreate(1, size, NULL)) == NULL)
+ return (SEL *)ERROR_PTR("horiz sel not made", procName, NULL);
+ selSetOrigin(sel, 0, size / 2);
+ } else {
+ if ((sel = selCreate(size, 1, NULL)) == NULL)
+ return (SEL *)ERROR_PTR("vert sel not made", procName, NULL);
+ selSetOrigin(sel, size / 2, 0);
+ }
+
+ /* Lay down the elements of the comb */
+ for (i = 0; i < factor2; i++) {
+ z = factor1 / 2 + i * factor1;
+/* lept_stderr("i = %d, factor1 = %d, factor2 = %d, z = %d\n",
+ i, factor1, factor2, z); */
+ if (direction == L_HORIZ)
+ selSetElement(sel, 0, z, SEL_HIT);
+ else
+ selSetElement(sel, z, 0, SEL_HIT);
+ }
+
+ return sel;
+}
+
+
+/*!
+ * \brief create2dIntArray()
+ *
+ * \param[in] sy rows == height
+ * \param[in] sx columns == width
+ * \return doubly indexed array i.e., an array of sy row pointers,
+ * each of which points to an array of sx ints
+ *
+ * <pre>
+ * Notes:
+ * (1) The array[sy][sx] is indexed in standard "matrix notation",
+ * with the row index first.
+ * </pre>
+ */
+l_int32 **
+create2dIntArray(l_int32 sy,
+ l_int32 sx)
+{
+l_int32 i, j, success;
+l_int32 **array;
+
+ PROCNAME("create2dIntArray");
+
+ if (sx <= 0 || sx > MaxKernelSize)
+ return (l_int32 **)ERROR_PTR("sx out of bounds", procName, NULL);
+ if (sy <= 0 || sy > MaxKernelSize)
+ return (l_int32 **)ERROR_PTR("sy out of bounds", procName, NULL);
+
+ if ((array = (l_int32 **)LEPT_CALLOC(sy, sizeof(l_int32 *))) == NULL)
+ return (l_int32 **)ERROR_PTR("ptr array not made", procName, NULL);
+ success = TRUE;
+ for (i = 0; i < sy; i++) {
+ if ((array[i] = (l_int32 *)LEPT_CALLOC(sx, sizeof(l_int32))) == NULL) {
+ success = FALSE;
+ break;
+ }
+ }
+ if (success) return array;
+
+ /* Cleanup after error */
+ for (j = 0; j < i; j++)
+ LEPT_FREE(array[j]);
+ LEPT_FREE(array);
+ return (l_int32 **)ERROR_PTR("array not made", procName, NULL);
+}
+
+
+
+/*------------------------------------------------------------------------*
+ * Extension of sela *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief selaAddSel()
+ *
+ * \param[in] sela
+ * \param[in] sel to be added
+ * \param[in] selname ignored if already defined in sel;
+ * req'd in sel when added to a sela
+ * \param[in] copyflag L_INSERT or L_COPY
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This adds a sel, either inserting or making a copy.
+ * (2) Because every sel in a sela must have a name, it copies
+ * the input name if necessary. You can input NULL for
+ * selname if the sel already has a name.
+ * </pre>
+ */
+l_ok
+selaAddSel(SELA *sela,
+ SEL *sel,
+ const char *selname,
+ l_int32 copyflag)
+{
+l_int32 n;
+SEL *csel;
+
+ PROCNAME("selaAddSel");
+
+ if (!sela)
+ return ERROR_INT("sela not defined", procName, 1);
+ if (!sel)
+ return ERROR_INT("sel not defined", procName, 1);
+ if (!sel->name && !selname)
+ return ERROR_INT("added sel must have name", procName, 1);
+ if (copyflag != L_INSERT && copyflag != L_COPY)
+ return ERROR_INT("invalid copyflag", procName, 1);
+
+ if (copyflag == L_COPY) {
+ if ((csel = selCopy(sel)) == NULL)
+ return ERROR_INT("csel not made", procName, 1);
+ } else { /* copyflag == L_INSERT */
+ csel = sel;
+ }
+ if (!csel->name)
+ csel->name = stringNew(selname);
+
+ n = selaGetCount(sela);
+ if (n >= sela->nalloc) {
+ if (selaExtendArray(sela)) {
+ if (copyflag != L_INSERT)
+ selDestroy(&csel);
+ return ERROR_INT("extension failed", procName, 1);
+ }
+ }
+
+ sela->sel[n] = csel;
+ sela->n++;
+ return 0;
+}
+
+
+/*!
+ * \brief selaExtendArray()
+ *
+ * \param[in] sela
+ * \return 0 if OK; 1 on error
+ */
+static l_int32
+selaExtendArray(SELA *sela)
+{
+ PROCNAME("selaExtendArray");
+
+ if (!sela)
+ return ERROR_INT("sela not defined", procName, 1);
+
+ if ((sela->sel = (SEL **)reallocNew((void **)&sela->sel,
+ sizeof(SEL *) * sela->nalloc,
+ 2 * sizeof(SEL *) * sela->nalloc)) == NULL)
+ return ERROR_INT("new ptr array not returned", procName, 1);
+
+ sela->nalloc = 2 * sela->nalloc;
+ return 0;
+}
+
+
+
+/*----------------------------------------------------------------------*
+ * Accessors *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief selaGetCount()
+ *
+ * \param[in] sela
+ * \return count, or 0 on error
+ */
+l_int32
+selaGetCount(SELA *sela)
+{
+ PROCNAME("selaGetCount");
+
+ if (!sela)
+ return ERROR_INT("sela not defined", procName, 0);
+
+ return sela->n;
+}
+
+
+/*!
+ * \brief selaGetSel()
+ *
+ * \param[in] sela
+ * \param[in] i index of sel to be retrieved not copied
+ * \return sel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This returns a ptr to the sel, not a copy, so the caller
+ * must not destroy it!
+ * </pre>
+ */
+SEL *
+selaGetSel(SELA *sela,
+ l_int32 i)
+{
+ PROCNAME("selaGetSel");
+
+ if (!sela)
+ return (SEL *)ERROR_PTR("sela not defined", procName, NULL);
+
+ if (i < 0 || i >= sela->n)
+ return (SEL *)ERROR_PTR("invalid index", procName, NULL);
+ return sela->sel[i];
+}
+
+
+/*!
+ * \brief selGetName()
+ *
+ * \param[in] sel
+ * \return sel name not copied, or NULL if no name or on error
+ */
+char *
+selGetName(SEL *sel)
+{
+ PROCNAME("selGetName");
+
+ if (!sel)
+ return (char *)ERROR_PTR("sel not defined", procName, NULL);
+
+ return sel->name;
+}
+
+
+/*!
+ * \brief selSetName()
+ *
+ * \param[in] sel
+ * \param[in] name [optional]; can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Always frees the existing sel name, if defined.
+ * (2) If name is not defined, just clears any existing sel name.
+ * </pre>
+ */
+l_ok
+selSetName(SEL *sel,
+ const char *name)
+{
+ PROCNAME("selSetName");
+
+ if (!sel)
+ return ERROR_INT("sel not defined", procName, 1);
+
+ return stringReplace(&sel->name, name);
+}
+
+
+/*!
+ * \brief selaFindSelByName()
+ *
+ * \param[in] sela
+ * \param[in] name sel name
+ * \param[out] pindex [optional]
+ * \param[in] psel [optional] sel (not a copy)
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+selaFindSelByName(SELA *sela,
+ const char *name,
+ l_int32 *pindex,
+ SEL **psel)
+{
+l_int32 i, n;
+char *sname;
+SEL *sel;
+
+ PROCNAME("selaFindSelByName");
+
+ if (pindex) *pindex = -1;
+ if (psel) *psel = NULL;
+
+ if (!sela)
+ return ERROR_INT("sela not defined", procName, 1);
+
+ n = selaGetCount(sela);
+ for (i = 0; i < n; i++)
+ {
+ if ((sel = selaGetSel(sela, i)) == NULL) {
+ L_WARNING("missing sel\n", procName);
+ continue;
+ }
+
+ sname = selGetName(sel);
+ if (sname && (!strcmp(name, sname))) {
+ if (pindex)
+ *pindex = i;
+ if (psel)
+ *psel = sel;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+
+/*!
+ * \brief selGetElement()
+ *
+ * \param[in] sel
+ * \param[in] row
+ * \param[in] col
+ * \param[out] ptype SEL_HIT, SEL_MISS, SEL_DONT_CARE
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+selGetElement(SEL *sel,
+ l_int32 row,
+ l_int32 col,
+ l_int32 *ptype)
+{
+ PROCNAME("selGetElement");
+
+ if (!ptype)
+ return ERROR_INT("&type not defined", procName, 1);
+ *ptype = SEL_DONT_CARE;
+ if (!sel)
+ return ERROR_INT("sel not defined", procName, 1);
+ if (row < 0 || row >= sel->sy)
+ return ERROR_INT("sel row out of bounds", procName, 1);
+ if (col < 0 || col >= sel->sx)
+ return ERROR_INT("sel col out of bounds", procName, 1);
+
+ *ptype = sel->data[row][col];
+ return 0;
+}
+
+
+/*!
+ * \brief selSetElement()
+ *
+ * \param[in] sel
+ * \param[in] row
+ * \param[in] col
+ * \param[in] type SEL_HIT, SEL_MISS, SEL_DONT_CARE
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Because we use row and column to index into an array,
+ * they are always non-negative. The location of the origin
+ * (and the type of operation) determine the actual
+ * direction of the rasterop.
+ * </pre>
+ */
+l_ok
+selSetElement(SEL *sel,
+ l_int32 row,
+ l_int32 col,
+ l_int32 type)
+{
+ PROCNAME("selSetElement");
+
+ if (!sel)
+ return ERROR_INT("sel not defined", procName, 1);
+ if (type != SEL_HIT && type != SEL_MISS && type != SEL_DONT_CARE)
+ return ERROR_INT("invalid sel element type", procName, 1);
+ if (row < 0 || row >= sel->sy)
+ return ERROR_INT("sel row out of bounds", procName, 1);
+ if (col < 0 || col >= sel->sx)
+ return ERROR_INT("sel col out of bounds", procName, 1);
+
+ sel->data[row][col] = type;
+ return 0;
+}
+
+
+/*!
+ * \brief selGetParameters()
+ *
+ * \param[in] sel
+ * \param[out] psy, psx, pcy, pcx [optional] each can be null
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+selGetParameters(SEL *sel,
+ l_int32 *psy,
+ l_int32 *psx,
+ l_int32 *pcy,
+ l_int32 *pcx)
+{
+ PROCNAME("selGetParameters");
+
+ if (psy) *psy = 0;
+ if (psx) *psx = 0;
+ if (pcy) *pcy = 0;
+ if (pcx) *pcx = 0;
+ if (!sel)
+ return ERROR_INT("sel not defined", procName, 1);
+ if (psy) *psy = sel->sy;
+ if (psx) *psx = sel->sx;
+ if (pcy) *pcy = sel->cy;
+ if (pcx) *pcx = sel->cx;
+ return 0;
+}
+
+
+/*!
+ * \brief selSetOrigin()
+ *
+ * \param[in] sel
+ * \param[in] cy, cx
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+selSetOrigin(SEL *sel,
+ l_int32 cy,
+ l_int32 cx)
+{
+ PROCNAME("selSetOrigin");
+
+ if (!sel)
+ return ERROR_INT("sel not defined", procName, 1);
+ sel->cy = cy;
+ sel->cx = cx;
+ return 0;
+}
+
+
+/*!
+ * \brief selGetTypeAtOrigin()
+ *
+ * \param[in] sel
+ * \param[out] ptype SEL_HIT, SEL_MISS, SEL_DONT_CARE
+ * \return 0 if OK; 1 on error or if origin is not found
+ */
+l_ok
+selGetTypeAtOrigin(SEL *sel,
+ l_int32 *ptype)
+{
+l_int32 sx, sy, cx, cy, i, j;
+
+ PROCNAME("selGetTypeAtOrigin");
+
+ if (!ptype)
+ return ERROR_INT("&type not defined", procName, 1);
+ *ptype = SEL_DONT_CARE; /* init */
+ if (!sel)
+ return ERROR_INT("sel not defined", procName, 1);
+
+ selGetParameters(sel, &sy, &sx, &cy, &cx);
+ for (i = 0; i < sy; i++) {
+ for (j = 0; j < sx; j++) {
+ if (i == cy && j == cx) {
+ selGetElement(sel, i, j, ptype);
+ return 0;
+ }
+ }
+ }
+
+ return ERROR_INT("sel origin not found", procName, 1);
+}
+
+
+/*!
+ * \brief selaGetBrickName()
+ *
+ * \param[in] sela
+ * \param[in] hsize, vsize of brick sel
+ * \return sel name new string, or NULL if no name or on error
+ */
+char *
+selaGetBrickName(SELA *sela,
+ l_int32 hsize,
+ l_int32 vsize)
+{
+l_int32 i, nsels, sx, sy;
+SEL *sel;
+
+ PROCNAME("selaGetBrickName");
+
+ if (!sela)
+ return (char *)ERROR_PTR("sela not defined", procName, NULL);
+
+ nsels = selaGetCount(sela);
+ for (i = 0; i < nsels; i++) {
+ sel = selaGetSel(sela, i);
+ selGetParameters(sel, &sy, &sx, NULL, NULL);
+ if (hsize == sx && vsize == sy)
+ return stringNew(selGetName(sel));
+ }
+
+ return (char *)ERROR_PTR("sel not found", procName, NULL);
+}
+
+
+/*!
+ * \brief selaGetCombName()
+ *
+ * \param[in] sela
+ * \param[in] size the product of sizes of the brick and comb parts
+ * \param[in] direction L_HORIZ, L_VERT
+ * \return sel name new string, or NULL if name not found or on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Combs are by definition 1-dimensional, either horiz or vert.
+ * (2) Use this with comb Sels; e.g., from selaAddDwaCombs().
+ * </pre>
+ */
+char *
+selaGetCombName(SELA *sela,
+ l_int32 size,
+ l_int32 direction)
+{
+char *selname;
+char combname[256];
+l_int32 i, nsels, sx, sy, found;
+SEL *sel;
+
+ PROCNAME("selaGetCombName");
+
+ if (!sela)
+ return (char *)ERROR_PTR("sela not defined", procName, NULL);
+ if (direction != L_HORIZ && direction != L_VERT)
+ return (char *)ERROR_PTR("invalid direction", procName, NULL);
+
+ /* Derive the comb name we're looking for */
+ if (direction == L_HORIZ)
+ snprintf(combname, sizeof(combname), "sel_comb_%dh", size);
+ else /* direction == L_VERT */
+ snprintf(combname, sizeof(combname), "sel_comb_%dv", size);
+
+ found = FALSE;
+ nsels = selaGetCount(sela);
+ for (i = 0; i < nsels; i++) {
+ sel = selaGetSel(sela, i);
+ selGetParameters(sel, &sy, &sx, NULL, NULL);
+ if (sy != 1 && sx != 1) /* 2-D; not a comb */
+ continue;
+ selname = selGetName(sel);
+ if (!strcmp(selname, combname)) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found)
+ return stringNew(selname);
+ else
+ return (char *)ERROR_PTR("sel not found", procName, NULL);
+}
+
+
+/* --------- Function used to generate code in this file ---------- */
+#if 0
+static void selaComputeCompositeParameters(const char *fileout);
+
+/*!
+ * \brief selaComputeCompParameters()
+ *
+ * \param[in] fileout
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) This static function was used to construct the comp_parameter_map[]
+ * array at the top of this file. It is static because it does
+ * not need to be called again. It remains here to show how
+ * the composite parameter map was computed.
+ * (2) The output file was pasted directly into comp_parameter_map[].
+ * The composite parameter map is used to quickly determine
+ * the linear decomposition parameters and sel names.
+ * </pre>
+ */
+static void
+selaComputeCompositeParameters(const char *fileout)
+{
+char *str, *nameh1, *nameh2, *namev1, *namev2;
+char buf[256];
+l_int32 size, size1, size2, len;
+SARRAY *sa;
+SELA *selabasic, *selacomb;
+
+ selabasic = selaAddBasic(NULL);
+ selacomb = selaAddDwaCombs(NULL);
+ sa = sarrayCreate(64);
+ for (size = 2; size < 64; size++) {
+ selectComposableSizes(size, &size1, &size2);
+ nameh1 = selaGetBrickName(selabasic, size1, 1);
+ namev1 = selaGetBrickName(selabasic, 1, size1);
+ if (size2 > 1) {
+ nameh2 = selaGetCombName(selacomb, size1 * size2, L_HORIZ);
+ namev2 = selaGetCombName(selacomb, size1 * size2, L_VERT);
+ } else {
+ nameh2 = stringNew("");
+ namev2 = stringNew("");
+ }
+ snprintf(buf, sizeof(buf),
+ " { %d, %d, %d, \"%s\", \"%s\", \"%s\", \"%s\" },",
+ size, size1, size2, nameh1, nameh2, namev1, namev2);
+ sarrayAddString(sa, buf, L_COPY);
+ LEPT_FREE(nameh1);
+ LEPT_FREE(nameh2);
+ LEPT_FREE(namev1);
+ LEPT_FREE(namev2);
+ }
+ str = sarrayToString(sa, 1);
+ len = strlen(str);
+ l_binaryWrite(fileout, "w", str, len + 1);
+ LEPT_FREE(str);
+ sarrayDestroy(&sa);
+ selaDestroy(&selabasic);
+ selaDestroy(&selacomb);
+}
+#endif
+/* -------------------------------------------------------------------- */
+
+
+/*!
+ * \brief getCompositeParameters()
+ *
+ * \param[in] size
+ * \param[out] psize1 [optional] brick factor size
+ * \param[out] psize2 [optional] comb factor size
+ * \param[out] pnameh1 [optional] name of horiz brick
+ * \param[out] pnameh2 [optional] name of horiz comb
+ * \param[out] pnamev1 [optional] name of vert brick
+ * \param[out] pnamev2 [optional] name of vert comb
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This uses the big lookup table at the top of this file.
+ * (2) All returned strings are copies that must be freed.
+ * </pre>
+ */
+l_ok
+getCompositeParameters(l_int32 size,
+ l_int32 *psize1,
+ l_int32 *psize2,
+ char **pnameh1,
+ char **pnameh2,
+ char **pnamev1,
+ char **pnamev2)
+{
+l_int32 index;
+
+ PROCNAME("selaGetSelnames");
+
+ if (psize1) *psize1 = 0;
+ if (psize2) *psize2 = 0;
+ if (pnameh1) *pnameh1 = NULL;
+ if (pnameh2) *pnameh2 = NULL;
+ if (pnamev1) *pnamev1 = NULL;
+ if (pnamev2) *pnamev2 = NULL;
+ if (size < 2 || size > 63)
+ return ERROR_INT("valid size range is {2 ... 63}", procName, 1);
+ index = size - 2;
+ if (psize1)
+ *psize1 = comp_parameter_map[index].size1;
+ if (psize2)
+ *psize2 = comp_parameter_map[index].size2;
+ if (pnameh1)
+ *pnameh1 = stringNew(comp_parameter_map[index].selnameh1);
+ if (pnameh2)
+ *pnameh2 = stringNew(comp_parameter_map[index].selnameh2);
+ if (pnamev1)
+ *pnamev1 = stringNew(comp_parameter_map[index].selnamev1);
+ if (pnamev2)
+ *pnamev2 = stringNew(comp_parameter_map[index].selnamev2);
+ return 0;
+}
+
+
+/*!
+ * \brief selaGetSelnames()
+ *
+ * \param[in] sela
+ * \return sa of all sel names, or NULL on error
+ */
+SARRAY *
+selaGetSelnames(SELA *sela)
+{
+char *selname;
+l_int32 i, n;
+SEL *sel;
+SARRAY *sa;
+
+ PROCNAME("selaGetSelnames");
+
+ if (!sela)
+ return (SARRAY *)ERROR_PTR("sela not defined", procName, NULL);
+ if ((n = selaGetCount(sela)) == 0)
+ return (SARRAY *)ERROR_PTR("no sels in sela", procName, NULL);
+
+ if ((sa = sarrayCreate(n)) == NULL)
+ return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+ for (i = 0; i < n; i++) {
+ sel = selaGetSel(sela, i);
+ selname = selGetName(sel);
+ sarrayAddString(sa, selname, L_COPY);
+ }
+
+ return sa;
+}
+
+
+
+/*----------------------------------------------------------------------*
+ * Max translations for erosion and hmt *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief selFindMaxTranslations()
+ *
+ * \param[in] sel
+ * \param[out] pxp, pyp, pxn, pyn max shifts
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ These are the maximum shifts for the erosion operation.
+ * For example, when j < cx, the shift of the image
+ * is +x to the cx. This is a positive xp shift.
+ * </pre>
+ */
+l_ok
+selFindMaxTranslations(SEL *sel,
+ l_int32 *pxp,
+ l_int32 *pyp,
+ l_int32 *pxn,
+ l_int32 *pyn)
+{
+l_int32 sx, sy, cx, cy, i, j;
+l_int32 maxxp, maxyp, maxxn, maxyn;
+
+ PROCNAME("selaFindMaxTranslations");
+
+ if (!pxp || !pyp || !pxn || !pyn)
+ return ERROR_INT("&xp (etc) defined", procName, 1);
+ *pxp = *pyp = *pxn = *pyn = 0;
+ if (!sel)
+ return ERROR_INT("sel not defined", procName, 1);
+ selGetParameters(sel, &sy, &sx, &cy, &cx);
+
+ maxxp = maxyp = maxxn = maxyn = 0;
+ for (i = 0; i < sy; i++) {
+ for (j = 0; j < sx; j++) {
+ if (sel->data[i][j] == 1) {
+ maxxp = L_MAX(maxxp, cx - j);
+ maxyp = L_MAX(maxyp, cy - i);
+ maxxn = L_MAX(maxxn, j - cx);
+ maxyn = L_MAX(maxyn, i - cy);
+ }
+ }
+ }
+
+ *pxp = maxxp;
+ *pyp = maxyp;
+ *pxn = maxxn;
+ *pyn = maxyn;
+
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Rotation by multiples of 90 degrees *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief selRotateOrth()
+ *
+ * \param[in] sel
+ * \param[in] quads 0 - 4; number of 90 degree cw rotations
+ * \return seld, or NULL on error
+ */
+SEL *
+selRotateOrth(SEL *sel,
+ l_int32 quads)
+{
+l_int32 i, j, ni, nj, sx, sy, cx, cy, nsx, nsy, ncx, ncy, type;
+SEL *seld;
+
+ PROCNAME("selRotateOrth");
+
+ if (!sel)
+ return (SEL *)ERROR_PTR("sel not defined", procName, NULL);
+ if (quads < 0 || quads > 4)
+ return (SEL *)ERROR_PTR("quads not in {0,1,2,3,4}", procName, NULL);
+ if (quads == 0 || quads == 4)
+ return selCopy(sel);
+
+ selGetParameters(sel, &sy, &sx, &cy, &cx);
+ if (quads == 1) { /* 90 degrees cw */
+ nsx = sy;
+ nsy = sx;
+ ncx = sy - cy - 1;
+ ncy = cx;
+ } else if (quads == 2) { /* 180 degrees cw */
+ nsx = sx;
+ nsy = sy;
+ ncx = sx - cx - 1;
+ ncy = sy - cy - 1;
+ } else { /* 270 degrees cw */
+ nsx = sy;
+ nsy = sx;
+ ncx = cy;
+ ncy = sx - cx - 1;
+ }
+ seld = selCreateBrick(nsy, nsx, ncy, ncx, SEL_DONT_CARE);
+ if (sel->name)
+ seld->name = stringNew(sel->name);
+
+ for (i = 0; i < sy; i++) {
+ for (j = 0; j < sx; j++) {
+ selGetElement(sel, i, j, &type);
+ if (quads == 1) {
+ ni = j;
+ nj = sy - i - 1;
+ } else if (quads == 2) {
+ ni = sy - i - 1;
+ nj = sx - j - 1;
+ } else { /* quads == 3 */
+ ni = sx - j - 1;
+ nj = i;
+ }
+ selSetElement(seld, ni, nj, type);
+ }
+ }
+
+ return seld;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Sela and Sel serialized I/O *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief selaRead()
+ *
+ * \param[in] fname filename
+ * \return sela, or NULL on error
+ */
+SELA *
+selaRead(const char *fname)
+{
+FILE *fp;
+SELA *sela;
+
+ PROCNAME("selaRead");
+
+ if (!fname)
+ return (SELA *)ERROR_PTR("fname not defined", procName, NULL);
+
+ if ((fp = fopenReadStream(fname)) == NULL)
+ return (SELA *)ERROR_PTR("stream not opened", procName, NULL);
+ if ((sela = selaReadStream(fp)) == NULL) {
+ fclose(fp);
+ return (SELA *)ERROR_PTR("sela not returned", procName, NULL);
+ }
+ fclose(fp);
+
+ return sela;
+}
+
+
+/*!
+ * \brief selaReadStream()
+ *
+ * \param[in] fp file stream
+ * \return sela, or NULL on error
+ */
+SELA *
+selaReadStream(FILE *fp)
+{
+l_int32 i, n, version;
+SEL *sel;
+SELA *sela;
+
+ PROCNAME("selaReadStream");
+
+ if (!fp)
+ return (SELA *)ERROR_PTR("stream not defined", procName, NULL);
+
+ if (fscanf(fp, "\nSela Version %d\n", &version) != 1)
+ return (SELA *)ERROR_PTR("not a sela file", procName, NULL);
+ if (version != SEL_VERSION_NUMBER)
+ return (SELA *)ERROR_PTR("invalid sel version", procName, NULL);
+ if (fscanf(fp, "Number of Sels = %d\n\n", &n) != 1)
+ return (SELA *)ERROR_PTR("not a sela file", procName, NULL);
+
+ if ((sela = selaCreate(n)) == NULL)
+ return (SELA *)ERROR_PTR("sela not made", procName, NULL);
+ sela->nalloc = n;
+
+ for (i = 0; i < n; i++) {
+ if ((sel = selReadStream(fp)) == NULL) {
+ selaDestroy(&sela);
+ return (SELA *)ERROR_PTR("sel not read", procName, NULL);
+ }
+ selaAddSel(sela, sel, NULL, 0);
+ }
+
+ return sela;
+}
+
+
+/*!
+ * \brief selRead()
+ *
+ * \param[in] fname filename
+ * \return sel, or NULL on error
+ */
+SEL *
+selRead(const char *fname)
+{
+FILE *fp;
+SEL *sel;
+
+ PROCNAME("selRead");
+
+ if (!fname)
+ return (SEL *)ERROR_PTR("fname not defined", procName, NULL);
+
+ if ((fp = fopenReadStream(fname)) == NULL)
+ return (SEL *)ERROR_PTR("stream not opened", procName, NULL);
+ if ((sel = selReadStream(fp)) == NULL) {
+ fclose(fp);
+ return (SEL *)ERROR_PTR("sela not returned", procName, NULL);
+ }
+ fclose(fp);
+
+ return sel;
+}
+
+
+/*!
+ * \brief selReadStream()
+ *
+ * \param[in] fp file stream
+ * \return sel, or NULL on error
+ */
+SEL *
+selReadStream(FILE *fp)
+{
+char selname[256];
+char linebuf[256];
+l_int32 sy, sx, cy, cx, i, j, version, ignore;
+SEL *sel;
+
+ PROCNAME("selReadStream");
+
+ if (!fp)
+ return (SEL *)ERROR_PTR("stream not defined", procName, NULL);
+
+ if (fscanf(fp, " Sel Version %d\n", &version) != 1)
+ return (SEL *)ERROR_PTR("not a sel file", procName, NULL);
+ if (version != SEL_VERSION_NUMBER)
+ return (SEL *)ERROR_PTR("invalid sel version", procName, NULL);
+
+ if (fgets(linebuf, sizeof(linebuf), fp) == NULL)
+ return (SEL *)ERROR_PTR("error reading into linebuf", procName, NULL);
+ sscanf(linebuf, " ------ %200s ------", selname);
+
+ if (fscanf(fp, " sy = %d, sx = %d, cy = %d, cx = %d\n",
+ &sy, &sx, &cy, &cx) != 4)
+ return (SEL *)ERROR_PTR("dimensions not read", procName, NULL);
+
+ if ((sel = selCreate(sy, sx, selname)) == NULL)
+ return (SEL *)ERROR_PTR("sel not made", procName, NULL);
+ selSetOrigin(sel, cy, cx);
+
+ for (i = 0; i < sy; i++) {
+ ignore = fscanf(fp, " ");
+ for (j = 0; j < sx; j++)
+ ignore = fscanf(fp, "%1d", &sel->data[i][j]);
+ ignore = fscanf(fp, "\n");
+ }
+ ignore = fscanf(fp, "\n");
+
+ return sel;
+}
+
+
+/*!
+ * \brief selaWrite()
+ *
+ * \param[in] fname filename
+ * \param[in] sela
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+selaWrite(const char *fname,
+ SELA *sela)
+{
+FILE *fp;
+
+ PROCNAME("selaWrite");
+
+ if (!fname)
+ return ERROR_INT("fname not defined", procName, 1);
+ if (!sela)
+ return ERROR_INT("sela not defined", procName, 1);
+
+ if ((fp = fopenWriteStream(fname, "wb")) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ selaWriteStream(fp, sela);
+ fclose(fp);
+
+ return 0;
+}
+
+
+/*!
+ * \brief selaWriteStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] sela
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+selaWriteStream(FILE *fp,
+ SELA *sela)
+{
+l_int32 i, n;
+SEL *sel;
+
+ PROCNAME("selaWriteStream");
+
+ if (!fp)
+ return ERROR_INT("stream not defined", procName, 1);
+ if (!sela)
+ return ERROR_INT("sela not defined", procName, 1);
+
+ n = selaGetCount(sela);
+ fprintf(fp, "\nSela Version %d\n", SEL_VERSION_NUMBER);
+ fprintf(fp, "Number of Sels = %d\n\n", n);
+ for (i = 0; i < n; i++) {
+ if ((sel = selaGetSel(sela, i)) == NULL)
+ continue;
+ selWriteStream(fp, sel);
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief selWrite()
+ *
+ * \param[in] fname filename
+ * \param[in] sel
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+selWrite(const char *fname,
+ SEL *sel)
+{
+FILE *fp;
+
+ PROCNAME("selWrite");
+
+ if (!fname)
+ return ERROR_INT("fname not defined", procName, 1);
+ if (!sel)
+ return ERROR_INT("sel not defined", procName, 1);
+
+ if ((fp = fopenWriteStream(fname, "wb")) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ selWriteStream(fp, sel);
+ fclose(fp);
+
+ return 0;
+}
+
+
+/*!
+ * \brief selWriteStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] sel
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+selWriteStream(FILE *fp,
+ SEL *sel)
+{
+l_int32 sx, sy, cx, cy, i, j;
+
+ PROCNAME("selWriteStream");
+
+ if (!fp)
+ return ERROR_INT("stream not defined", procName, 1);
+ if (!sel)
+ return ERROR_INT("sel not defined", procName, 1);
+ selGetParameters(sel, &sy, &sx, &cy, &cx);
+
+ fprintf(fp, " Sel Version %d\n", SEL_VERSION_NUMBER);
+ fprintf(fp, " ------ %s ------\n", selGetName(sel));
+ fprintf(fp, " sy = %d, sx = %d, cy = %d, cx = %d\n", sy, sx, cy, cx);
+ for (i = 0; i < sy; i++) {
+ fprintf(fp, " ");
+ for (j = 0; j < sx; j++)
+ fprintf(fp, "%d", sel->data[i][j]);
+ fprintf(fp, "\n");
+ }
+ fprintf(fp, "\n");
+
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Building custom hit-miss sels from compiled strings *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief selCreateFromString()
+ *
+ * \param[in] text
+ * \param[in] h, w height, width
+ * \param[in] name [optional] sel name; can be null
+ * \return sel of the given size, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The text is an array of chars (in row-major order) where
+ * each char can be one of the following:
+ * 'x': hit
+ * 'o': miss
+ * ' ': don't-care
+ * (2) When the origin falls on a hit or miss, use an upper case
+ * char (e.g., 'X' or 'O') to indicate it. When the origin
+ * falls on a don't-care, indicate this with a 'C'.
+ * The string must have exactly one origin specified.
+ * (3) The advantage of this method is that the text can be input
+ * in a format that shows the 2D layout of the Sel; e.g.,
+ * \code
+ * static const char *seltext = "x "
+ * "x Oo "
+ * "x "
+ * "xxxxx";
+ * \endcode
+ * </pre>
+ */
+SEL *
+selCreateFromString(const char *text,
+ l_int32 h,
+ l_int32 w,
+ const char *name)
+{
+SEL *sel;
+l_int32 y, x, norig;
+char ch;
+
+ PROCNAME("selCreateFromString");
+
+ if (!text || text[0] == '\0')
+ return (SEL *)ERROR_PTR("text undefined or empty", procName, NULL);
+ if (h < 1)
+ return (SEL *)ERROR_PTR("height must be > 0", procName, NULL);
+ if (w < 1)
+ return (SEL *)ERROR_PTR("width must be > 0", procName, NULL);
+ if (strlen(text) != (size_t)w * h)
+ return (SEL *)ERROR_PTR("text size != w * h", procName, NULL);
+
+ sel = selCreate(h, w, name);
+ norig = 0;
+ for (y = 0; y < h; ++y) {
+ for (x = 0; x < w; ++x) {
+ ch = *(text++);
+ switch (ch)
+ {
+ case 'X':
+ norig++;
+ selSetOrigin(sel, y, x);
+ /* fall through */
+ case 'x':
+ selSetElement(sel, y, x, SEL_HIT);
+ break;
+
+ case 'O':
+ norig++;
+ selSetOrigin(sel, y, x);
+ /* fall through */
+ case 'o':
+ selSetElement(sel, y, x, SEL_MISS);
+ break;
+
+ case 'C':
+ norig++;
+ selSetOrigin(sel, y, x);
+ /* fall through */
+ case ' ':
+ selSetElement(sel, y, x, SEL_DONT_CARE);
+ break;
+
+ case '\n':
+ /* ignored */
+ continue;
+
+ default:
+ selDestroy(&sel);
+ return (SEL *)ERROR_PTR("unknown char", procName, NULL);
+ }
+ }
+ }
+ if (norig != 1) {
+ L_ERROR("Exactly one origin must be specified; this string has %d\n",
+ procName, norig);
+ selDestroy(&sel);
+ }
+
+ return sel;
+}
+
+
+/*!
+ * \brief selPrintToString()
+ *
+ * \param[in] sel
+ * \return str string; caller must free
+ *
+ * <pre>
+ * Notes:
+ * (1) This is an inverse function of selCreateFromString.
+ * It prints a textual representation of the SEL to a malloc'd
+ * string. The format is the same as selCreateFromString
+ * except that newlines are inserted into the output
+ * between rows.
+ * (2) This is useful for debugging. However, if you want to
+ * save some Sels in a file, put them in a Sela and write
+ * them out with selaWrite(). They can then be read in
+ * with selaRead().
+ * </pre>
+ */
+char *
+selPrintToString(SEL *sel)
+{
+char is_center;
+char *str, *strptr;
+l_int32 type;
+l_int32 sx, sy, cx, cy, x, y;
+
+ PROCNAME("selPrintToString");
+
+ if (!sel)
+ return (char *)ERROR_PTR("sel not defined", procName, NULL);
+
+ selGetParameters(sel, &sy, &sx, &cy, &cx);
+ if ((str = (char *)LEPT_CALLOC(1, sy * (sx + 1) + 1)) == NULL)
+ return (char *)ERROR_PTR("calloc fail for str", procName, NULL);
+ strptr = str;
+
+ for (y = 0; y < sy; ++y) {
+ for (x = 0; x < sx; ++x) {
+ selGetElement(sel, y, x, &type);
+ is_center = (x == cx && y == cy);
+ switch (type) {
+ case SEL_HIT:
+ *(strptr++) = is_center ? 'X' : 'x';
+ break;
+ case SEL_MISS:
+ *(strptr++) = is_center ? 'O' : 'o';
+ break;
+ case SEL_DONT_CARE:
+ *(strptr++) = is_center ? 'C' : ' ';
+ break;
+ }
+ }
+ *(strptr++) = '\n';
+ }
+
+ return str;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Building custom hit-miss sels from a simple file format *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief selaCreateFromFile()
+ *
+ * \param[in] filename
+ * \return sela, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The file contains a sequence of Sel descriptions.
+ * (2) Each Sel is formatted as follows:
+ * ~ Any number of comment lines starting with '#' are ignored
+ * ~ The next line contains the selname
+ * ~ The next lines contain the Sel data. They must be
+ * formatted similarly to the string format in
+ * selCreateFromString(), with each line beginning and
+ * ending with a double-quote, and showing the 2D layout.
+ * ~ Each Sel ends when a blank line, a comment line, or
+ * the end of file is reached.
+ * (3) See selCreateFromString() for a description of the string
+ * format for the Sel data. As an example, here are the lines
+ * of is a valid file for a single Sel. In the file, all lines
+ * are left-justified:
+ * # diagonal sel
+ * sel_5diag
+ * "x "
+ * " x "
+ * " X "
+ * " x "
+ * " x"
+ * </pre>
+ */
+SELA *
+selaCreateFromFile(const char *filename)
+{
+char *filestr, *line;
+l_int32 i, n, first, last, nsel, insel;
+size_t nbytes;
+NUMA *nafirst, *nalast;
+SARRAY *sa;
+SEL *sel;
+SELA *sela;
+
+ PROCNAME("selaCreateFromFile");
+
+ if (!filename)
+ return (SELA *)ERROR_PTR("filename not defined", procName, NULL);
+
+ filestr = (char *)l_binaryRead(filename, &nbytes);
+ sa = sarrayCreateLinesFromString(filestr, 1);
+ LEPT_FREE(filestr);
+ n = sarrayGetCount(sa);
+ sela = selaCreate(0);
+
+ /* Find the start and end lines for each Sel.
+ * We allow the "blank" lines to be null strings or
+ * to have standard whitespace (' ','\t',\'n') or be '#'. */
+ nafirst = numaCreate(0);
+ nalast = numaCreate(0);
+ insel = FALSE;
+ for (i = 0; i < n; i++) {
+ line = sarrayGetString(sa, i, L_NOCOPY);
+ if (!insel &&
+ (line[0] != '\0' && line[0] != ' ' &&
+ line[0] != '\t' && line[0] != '\n' && line[0] != '#')) {
+ numaAddNumber(nafirst, i);
+ insel = TRUE;
+ continue;
+ }
+ if (insel &&
+ (line[0] == '\0' || line[0] == ' ' ||
+ line[0] == '\t' || line[0] == '\n' || line[0] == '#')) {
+ numaAddNumber(nalast, i - 1);
+ insel = FALSE;
+ continue;
+ }
+ }
+ if (insel) /* fell off the end of the file */
+ numaAddNumber(nalast, n - 1);
+
+ /* Extract sels */
+ nsel = numaGetCount(nafirst);
+ for (i = 0; i < nsel; i++) {
+ numaGetIValue(nafirst, i, &first);
+ numaGetIValue(nalast, i, &last);
+ if ((sel = selCreateFromSArray(sa, first, last)) == NULL) {
+ lept_stderr("Error reading sel from %d to %d\n", first, last);
+ selaDestroy(&sela);
+ sarrayDestroy(&sa);
+ numaDestroy(&nafirst);
+ numaDestroy(&nalast);
+ return (SELA *)ERROR_PTR("bad sela file", procName, NULL);
+ }
+ selaAddSel(sela, sel, NULL, 0);
+ }
+
+ numaDestroy(&nafirst);
+ numaDestroy(&nalast);
+ sarrayDestroy(&sa);
+ return sela;
+}
+
+
+/*!
+ * \brief selCreateFromSArray()
+ *
+ * \param[in] sa
+ * \param[in] first line of sarray where Sel begins
+ * \param[in] last line of sarray where Sel ends
+ * \return sela, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The Sel contains the following lines:
+ * ~ The first line is the selname
+ * ~ The remaining lines contain the Sel data. They must
+ * be formatted similarly to the string format in
+ * selCreateFromString(), with each line beginning and
+ * ending with a double-quote, and showing the 2D layout.
+ * ~ 'last' gives the last line in the Sel data.
+ * (2) See selCreateFromString() for a description of the string
+ * format for the Sel data. As an example, here are the lines
+ * of is a valid file for a single Sel. In the file, all lines
+ * are left-justified:
+ * # diagonal sel
+ * sel_5diag
+ * "x "
+ * " x "
+ * " X "
+ * " x "
+ * " x"
+ * </pre>
+ */
+static SEL *
+selCreateFromSArray(SARRAY *sa,
+ l_int32 first,
+ l_int32 last)
+{
+char ch;
+char *name, *line;
+l_int32 n, len, i, w, h, y, x;
+SEL *sel;
+
+ PROCNAME("selCreateFromSArray");
+
+ if (!sa)
+ return (SEL *)ERROR_PTR("sa not defined", procName, NULL);
+ n = sarrayGetCount(sa);
+ if (first < 0 || first >= n || last <= first || last >= n)
+ return (SEL *)ERROR_PTR("invalid range", procName, NULL);
+
+ name = sarrayGetString(sa, first, L_NOCOPY);
+ h = last - first;
+ line = sarrayGetString(sa, first + 1, L_NOCOPY);
+ len = strlen(line);
+ if (line[0] != '"' || line[len - 1] != '"')
+ return (SEL *)ERROR_PTR("invalid format", procName, NULL);
+ w = len - 2;
+ if ((sel = selCreate(h, w, name)) == NULL)
+ return (SEL *)ERROR_PTR("sel not made", procName, NULL);
+ for (i = first + 1; i <= last; i++) {
+ line = sarrayGetString(sa, i, L_NOCOPY);
+ y = i - first - 1;
+ for (x = 0; x < w; ++x) {
+ ch = line[x + 1]; /* skip the leading double-quote */
+ switch (ch)
+ {
+ case 'X':
+ selSetOrigin(sel, y, x); /* set origin and hit */
+ /* fall through */
+ case 'x':
+ selSetElement(sel, y, x, SEL_HIT);
+ break;
+
+ case 'O':
+ selSetOrigin(sel, y, x); /* set origin and miss */
+ /* fall through */
+ case 'o':
+ selSetElement(sel, y, x, SEL_MISS);
+ break;
+
+ case 'C':
+ selSetOrigin(sel, y, x); /* set origin and don't-care */
+ /* fall through */
+ case ' ':
+ selSetElement(sel, y, x, SEL_DONT_CARE);
+ break;
+
+ default:
+ selDestroy(&sel);
+ return (SEL *)ERROR_PTR("unknown char", procName, NULL);
+ }
+ }
+ }
+
+ return sel;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Making hit-only SELs from Pta and Pix *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief selCreateFromPta()
+ *
+ * \param[in] pta
+ * \param[in] cy, cx origin of sel
+ * \param[in] name [optional] sel name; can be null
+ * \return sel of minimum required size, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The origin and all points in the pta must be positive.
+ * </pre>
+ */
+SEL *
+selCreateFromPta(PTA *pta,
+ l_int32 cy,
+ l_int32 cx,
+ const char *name)
+{
+l_int32 i, n, x, y, w, h;
+BOX *box;
+SEL *sel;
+
+ PROCNAME("selCreateFromPta");
+
+ if (!pta)
+ return (SEL *)ERROR_PTR("pta not defined", procName, NULL);
+ if (cy < 0 || cx < 0)
+ return (SEL *)ERROR_PTR("(cy, cx) not both >= 0", procName, NULL);
+ n = ptaGetCount(pta);
+ if (n == 0)
+ return (SEL *)ERROR_PTR("no pts in pta", procName, NULL);
+
+ box = ptaGetBoundingRegion(pta);
+ boxGetGeometry(box, &x, &y, &w, &h);
+ boxDestroy(&box);
+ if (x < 0 || y < 0)
+ return (SEL *)ERROR_PTR("not all x and y >= 0", procName, NULL);
+
+ sel = selCreate(y + h, x + w, name);
+ selSetOrigin(sel, cy, cx);
+ for (i = 0; i < n; i++) {
+ ptaGetIPt(pta, i, &x, &y);
+ selSetElement(sel, y, x, SEL_HIT);
+ }
+
+ return sel;
+}
+
+
+/*!
+ * \brief selCreateFromPix()
+ *
+ * \param[in] pix
+ * \param[in] cy, cx origin of sel
+ * \param[in] name [optional] sel name; can be null
+ * \return sel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The origin must be positive.
+ * (2) The pix must not exceed MaxPixTemplateSize in either dimension.
+ * and the total number of hits must not exceed MaxPixTemplateHits.
+ * </pre>
+ */
+SEL *
+selCreateFromPix(PIX *pix,
+ l_int32 cy,
+ l_int32 cx,
+ const char *name)
+{
+SEL *sel;
+l_int32 i, j, w, h, d, nhits;
+l_uint32 val;
+
+ PROCNAME("selCreateFromPix");
+
+ if (!pix)
+ return (SEL *)ERROR_PTR("pix not defined", procName, NULL);
+ if (cy < 0 || cx < 0)
+ return (SEL *)ERROR_PTR("(cy, cx) not both >= 0", procName, NULL);
+ pixGetDimensions(pix, &w, &h, &d);
+ if (d != 1)
+ return (SEL *)ERROR_PTR("pix not 1 bpp", procName, NULL);
+ if (w > MaxPixTemplateSize || h > MaxPixTemplateSize) {
+ L_ERROR("pix template too large (w = %d, h = %d)\n", procName, w, h);
+ return NULL;
+ }
+ pixCountPixels(pix, &nhits, NULL);
+ if (nhits > MaxPixTemplateHits) {
+ L_ERROR("too many hits (%d) in pix template\n", procName, nhits);
+ return NULL;
+ }
+
+ sel = selCreate(h, w, name);
+ selSetOrigin(sel, cy, cx);
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ pixGetPixel(pix, j, i, &val);
+ if (val)
+ selSetElement(sel, i, j, SEL_HIT);
+ }
+ }
+
+ return sel;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Making hit-miss sels from color Pix and image files *
+ *----------------------------------------------------------------------*/
+/*!
+ *
+ * selReadFromColorImage()
+ *
+ * \param[in] pathname
+ * \return sel if OK; NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Loads an image from a file and creates a (hit-miss) sel.
+ * (2) The sel name is taken from the pathname without the directory
+ * and extension.
+ * </pre>
+ */
+SEL *
+selReadFromColorImage(const char *pathname)
+{
+PIX *pix;
+SEL *sel;
+char *basename, *selname;
+
+ PROCNAME("selReadFromColorImage");
+
+ splitPathAtExtension (pathname, &basename, NULL);
+ splitPathAtDirectory (basename, NULL, &selname);
+ LEPT_FREE(basename);
+
+ if ((pix = pixRead(pathname)) == NULL) {
+ LEPT_FREE(selname);
+ return (SEL *)ERROR_PTR("pix not returned", procName, NULL);
+ }
+ if ((sel = selCreateFromColorPix(pix, selname)) == NULL)
+ L_ERROR("sel not made\n", procName);
+
+ LEPT_FREE(selname);
+ pixDestroy(&pix);
+ return sel;
+}
+
+
+/*!
+ *
+ * selCreateFromColorPix()
+ *
+ * \param[in] pixs cmapped or rgb
+ * \param[in] selname [optional] sel name; can be null
+ * \return sel if OK, NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The sel size is given by the size of pixs.
+ * (2) In pixs, hits are represented by green pixels, misses by red
+ * pixels, and don't-cares by white pixels.
+ * (3) In pixs, there may be no misses, but there must be at least 1 hit.
+ * (4) At most there can be only one origin pixel, which is optionally
+ * specified by using a lower-intensity pixel:
+ * if a hit: dark green
+ * if a miss: dark red
+ * if a don't care: gray
+ * If there is no such pixel, the origin defaults to the approximate
+ * center of the sel.
+ * </pre>
+ */
+SEL *
+selCreateFromColorPix(PIX *pixs,
+ const char *selname)
+{
+PIXCMAP *cmap;
+SEL *sel;
+l_int32 hascolor, num_origins, nohits;
+l_int32 w, h, d, i, j, red, green, blue;
+l_uint32 pixval;
+
+ PROCNAME("selCreateFromColorPix");
+
+ if (!pixs)
+ return (SEL *)ERROR_PTR("pixs not defined", procName, NULL);
+
+ hascolor = FALSE;
+ cmap = pixGetColormap(pixs);
+ if (cmap)
+ pixcmapHasColor(cmap, &hascolor);
+ pixGetDimensions(pixs, &w, &h, &d);
+ if (hascolor == FALSE && d != 32)
+ return (SEL *)ERROR_PTR("pixs has no color", procName, NULL);
+
+ if ((sel = selCreate (h, w, NULL)) == NULL)
+ return (SEL *)ERROR_PTR ("sel not made", procName, NULL);
+ selSetOrigin (sel, h / 2, w / 2); /* default */
+ selSetName(sel, selname);
+
+ num_origins = 0;
+ nohits = TRUE;
+ for (i = 0; i < h; i++) {
+ for (j = 0; j < w; j++) {
+ pixGetPixel (pixs, j, i, &pixval);
+
+ if (cmap) {
+ pixcmapGetColor (cmap, pixval, &red, &green, &blue);
+ } else {
+ red = GET_DATA_BYTE (&pixval, COLOR_RED);
+ green = GET_DATA_BYTE (&pixval, COLOR_GREEN);
+ blue = GET_DATA_BYTE (&pixval, COLOR_BLUE);
+ }
+
+ if (red < 255 && green < 255 && blue < 255) {
+ num_origins++;
+ if (num_origins == 1) /* first one found */
+ selSetOrigin (sel, i, j);
+ if (num_origins == 2)
+ L_WARNING("multiple origins in sel image\n", procName);
+ }
+ if (!red && green && !blue) {
+ nohits = FALSE;
+ selSetElement (sel, i, j, SEL_HIT);
+ } else if (red && !green && !blue) {
+ selSetElement (sel, i, j, SEL_MISS);
+ } else if (red && green && blue) {
+ selSetElement (sel, i, j, SEL_DONT_CARE);
+ } else {
+ selDestroy(&sel);
+ return (SEL *)ERROR_PTR("invalid color", procName, NULL);
+ }
+ }
+ }
+
+ if (nohits) {
+ selDestroy(&sel);
+ return (SEL *)ERROR_PTR("no hits in sel", procName, NULL);
+ }
+ return sel;
+}
+
+
+/*!
+ *
+ * selaCreateFromColorPixa()
+ *
+ * \param[in] pixa color pixa representing the sels
+ * \param[in] sa sarray of sel names
+ * \return sel if OK, NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes in selCreateFromColorPix()
+ * (2) sa is required because all sels that are put in a sela
+ * must have a name.
+ * </pre>
+ */
+SELA *
+selaCreateFromColorPixa(PIXA *pixa,
+ SARRAY *sa)
+{
+char *str;
+l_int32 i, n;
+PIX *pix;
+SEL *sel;
+SELA *sela;
+
+ PROCNAME("selaCreateFromColorPixa");
+
+ if (!pixa)
+ return (SELA *)ERROR_PTR("pixa not defined", procName, NULL);
+ if (!sa)
+ return (SELA *)ERROR_PTR("sa of sel names not defined", procName, NULL);
+
+ n = pixaGetCount(pixa);
+ if ((sela = selaCreate(n)) == NULL)
+ return (SELA *)ERROR_PTR("sela not allocated", procName, NULL);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixa, i, L_CLONE);
+ str = sarrayGetString(sa, i, L_NOCOPY);
+ sel = selCreateFromColorPix(pix, str);
+ selaAddSel(sela, sel, NULL, L_INSERT);
+ pixDestroy(&pix);
+ }
+ return sela;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Printable display of sel *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief selDisplayInPix()
+ *
+ * \param[in] sel
+ * \param[in] size of grid interiors; odd; minimum size of 13 is enforced
+ * \param[in] gthick grid thickness; minimum size of 2 is enforced
+ * \return pix display of sel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This gives a visual representation of a general (hit-miss) sel.
+ * (2) The empty sel is represented by a grid of intersecting lines.
+ * (3) Three different patterns are generated for the sel elements:
+ * ~ hit (solid black circle)
+ * ~ miss (black ring; inner radius is radius2)
+ * ~ origin (cross, XORed with whatever is there)
+ * </pre>
+ */
+PIX *
+selDisplayInPix(SEL *sel,
+ l_int32 size,
+ l_int32 gthick)
+{
+l_int32 i, j, w, h, sx, sy, cx, cy, type, width;
+l_int32 radius1, radius2, shift1, shift2, x0, y0;
+PIX *pixd, *pix2, *pixh, *pixm, *pixorig;
+PTA *pta1, *pta2, *pta1t, *pta2t;
+
+ PROCNAME("selDisplayInPix");
+
+ if (!sel)
+ return (PIX *)ERROR_PTR("sel not defined", procName, NULL);
+ if (size < 13) {
+ L_WARNING("size < 13; setting to 13\n", procName);
+ size = 13;
+ }
+ if (size % 2 == 0)
+ size++;
+ if (gthick < 2) {
+ L_WARNING("grid thickness < 2; setting to 2\n", procName);
+ gthick = 2;
+ }
+ selGetParameters(sel, &sy, &sx, &cy, &cx);
+ w = size * sx + gthick * (sx + 1);
+ h = size * sy + gthick * (sy + 1);
+ pixd = pixCreate(w, h, 1);
+
+ /* Generate grid lines */
+ for (i = 0; i <= sy; i++)
+ pixRenderLine(pixd, 0, gthick / 2 + i * (size + gthick),
+ w - 1, gthick / 2 + i * (size + gthick),
+ gthick, L_SET_PIXELS);
+ for (j = 0; j <= sx; j++)
+ pixRenderLine(pixd, gthick / 2 + j * (size + gthick), 0,
+ gthick / 2 + j * (size + gthick), h - 1,
+ gthick, L_SET_PIXELS);
+
+ /* Generate hit and miss patterns */
+ radius1 = (l_int32)(0.85 * ((size - 1) / 2.0) + 0.5); /* of hit */
+ radius2 = (l_int32)(0.65 * ((size - 1) / 2.0) + 0.5); /* of inner miss */
+ pta1 = generatePtaFilledCircle(radius1);
+ pta2 = generatePtaFilledCircle(radius2);
+ shift1 = (size - 1) / 2 - radius1; /* center circle in square */
+ shift2 = (size - 1) / 2 - radius2;
+ pta1t = ptaTransform(pta1, shift1, shift1, 1.0, 1.0);
+ pta2t = ptaTransform(pta2, shift2, shift2, 1.0, 1.0);
+ pixh = pixGenerateFromPta(pta1t, size, size); /* hits */
+ pix2 = pixGenerateFromPta(pta2t, size, size);
+ pixm = pixSubtract(NULL, pixh, pix2);
+
+ /* Generate crossed lines for origin pattern */
+ pixorig = pixCreate(size, size, 1);
+ width = size / 8;
+ pixRenderLine(pixorig, size / 2, (l_int32)(0.12 * size),
+ size / 2, (l_int32)(0.88 * size),
+ width, L_SET_PIXELS);
+ pixRenderLine(pixorig, (l_int32)(0.15 * size), size / 2,
+ (l_int32)(0.85 * size), size / 2,
+ width, L_FLIP_PIXELS);
+ pixRasterop(pixorig, size / 2 - width, size / 2 - width,
+ 2 * width, 2 * width, PIX_NOT(PIX_DST), NULL, 0, 0);
+
+ /* Specialize origin pattern for this sel */
+ selGetTypeAtOrigin(sel, &type);
+ if (type == SEL_HIT)
+ pixXor(pixorig, pixorig, pixh);
+ else if (type == SEL_MISS)
+ pixXor(pixorig, pixorig, pixm);
+
+ /* Paste the patterns in */
+ y0 = gthick;
+ for (i = 0; i < sy; i++) {
+ x0 = gthick;
+ for (j = 0; j < sx; j++) {
+ selGetElement(sel, i, j, &type);
+ if (i == cy && j == cx) /* origin */
+ pixRasterop(pixd, x0, y0, size, size, PIX_SRC, pixorig, 0, 0);
+ else if (type == SEL_HIT)
+ pixRasterop(pixd, x0, y0, size, size, PIX_SRC, pixh, 0, 0);
+ else if (type == SEL_MISS)
+ pixRasterop(pixd, x0, y0, size, size, PIX_SRC, pixm, 0, 0);
+ x0 += size + gthick;
+ }
+ y0 += size + gthick;
+ }
+
+ pixDestroy(&pix2);
+ pixDestroy(&pixh);
+ pixDestroy(&pixm);
+ pixDestroy(&pixorig);
+ ptaDestroy(&pta1);
+ ptaDestroy(&pta1t);
+ ptaDestroy(&pta2);
+ ptaDestroy(&pta2t);
+ return pixd;
+}
+
+
+/*!
+ * \brief selaDisplayInPix()
+ *
+ * \param[in] sela
+ * \param[in] size of grid interiors; odd; minimum size of 13 is enforced
+ * \param[in] gthick grid thickness; minimum size of 2 is enforced
+ * \param[in] spacing between sels, both horizontally and vertically
+ * \param[in] ncols number of sels per "line"
+ * \return pix display of all sels in sela, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This gives a visual representation of all the sels in a sela.
+ * (2) See notes in selDisplayInPix() for display params of each sel.
+ * (3) This gives the nicest results when all sels in the sela
+ * are the same size.
+ * </pre>
+ */
+PIX *
+selaDisplayInPix(SELA *sela,
+ l_int32 size,
+ l_int32 gthick,
+ l_int32 spacing,
+ l_int32 ncols)
+{
+l_int32 nsels, i, w, width;
+PIX *pixt, *pixd;
+PIXA *pixa;
+SEL *sel;
+
+ PROCNAME("selaDisplayInPix");
+
+ if (!sela)
+ return (PIX *)ERROR_PTR("sela not defined", procName, NULL);
+ if (size < 13) {
+ L_WARNING("size < 13; setting to 13\n", procName);
+ size = 13;
+ }
+ if (size % 2 == 0)
+ size++;
+ if (gthick < 2) {
+ L_WARNING("grid thickness < 2; setting to 2\n", procName);
+ gthick = 2;
+ }
+ if (spacing < 5) {
+ L_WARNING("spacing < 5; setting to 5\n", procName);
+ spacing = 5;
+ }
+
+ /* Accumulate the pix of each sel */
+ nsels = selaGetCount(sela);
+ pixa = pixaCreate(nsels);
+ for (i = 0; i < nsels; i++) {
+ sel = selaGetSel(sela, i);
+ pixt = selDisplayInPix(sel, size, gthick);
+ pixaAddPix(pixa, pixt, L_INSERT);
+ }
+
+ /* Find the tiled output width, using just the first
+ * ncols pix in the pixa. If all pix have the same width,
+ * they will align properly in columns. */
+ width = 0;
+ ncols = L_MIN(nsels, ncols);
+ for (i = 0; i < ncols; i++) {
+ pixt = pixaGetPix(pixa, i, L_CLONE);
+ pixGetDimensions(pixt, &w, NULL, NULL);
+ width += w;
+ pixDestroy(&pixt);
+ }
+ width += (ncols + 1) * spacing; /* add spacing all around as well */
+
+ pixd = pixaDisplayTiledInRows(pixa, 1, width, 1.0, 0, spacing, 0);
+ pixaDestroy(&pixa);
+ return pixd;
+}
diff --git a/leptonica/src/sel2.c b/leptonica/src/sel2.c
new file mode 100644
index 00000000..75320afd
--- /dev/null
+++ b/leptonica/src/sel2.c
@@ -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.
+ *====================================================================*/
+
+/*!
+ * \file sel2.c
+ * <pre>
+ *
+ * Contains definitions of simple structuring elements
+ *
+ * Basic brick structuring elements
+ * SELA *selaAddBasic()
+ * Linear horizontal and vertical
+ * Square
+ * Diagonals
+ *
+ * Simple hit-miss structuring elements
+ * SELA *selaAddHitMiss()
+ * Isolated foreground pixel
+ * Horizontal and vertical edges
+ * Slanted edge
+ * Corners
+ *
+ * Structuring elements for comparing with DWA operations
+ * SELA *selaAddDwaLinear()
+ * SELA *selaAddDwaCombs()
+ *
+ * Structuring elements for the intersection of lines
+ * SELA *selaAddCrossJunctions()
+ * SELA *selaAddTJunctions()
+ *
+ * Structuring elements for connectivity-preserving thinning operations
+ * SELA *sela4ccThin()
+ * SELA *sela8ccThin()
+ * SELA *sela4and8ccThin()
+ *
+ * Other structuring elements
+ * SEL *selMakePlusSign()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+#define 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};
+
+
+/* ------------------------------------------------------------------- *
+ * Basic brick structuring elements *
+ * ------------------------------------------------------------------- */
+/*!
+ * \brief selaAddBasic()
+ *
+ * \param[in] sela [optional]
+ * \return sela with additional sels, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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");
+ selSetOrigin(sel, 2, 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");
+ selSetOrigin(sel, 2, 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;
+}
+
+
+/* ------------------------------------------------------------------- *
+ * Simple hit-miss structuring elements *
+ * ------------------------------------------------------------------- */
+/*!
+ * \brief selaAddHitMiss()
+ *
+ * \param[in] 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;
+}
+
+
+/* ------------------------------------------------------------------- *
+ * Structuring elements for comparing with DWA operations *
+ * ------------------------------------------------------------------- */
+/*!
+ * \brief selaAddDwaLinear()
+ *
+ * \param[in] sela [optional]
+ * \return sela with additional sels, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief selaAddDwaCombs()
+ *
+ * \param[in] sela [optional]
+ * \return sela with additional sels, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (selh) {
+ snprintf(name, L_BUF_SIZE, "sel_comb_%dh", size);
+ selaAddSel(sela, selh, name, 0);
+ } else {
+ L_ERROR("selh not made for i = %d\n", procName, i);
+ }
+ selectComposableSels(i, L_VERT, NULL, &selv);
+ if (selv) {
+ snprintf(name, L_BUF_SIZE, "sel_comb_%dv", size);
+ selaAddSel(sela, selv, name, 0);
+ } else {
+ L_ERROR("selv not made for i = %d\n", procName, i);
+ }
+ prevsize = size;
+ }
+
+ return sela;
+}
+
+
+/* ------------------------------------------------------------------- *
+ * Structuring elements for the intersection of lines *
+ * ------------------------------------------------------------------- */
+/*!
+ * \brief selaAddCrossJunctions()
+ *
+ * \param[in] sela [optional]
+ * \param[in] hlsize length of each line of hits from origin
+ * \param[in] mdist distance of misses from the origin
+ * \param[in] norient number of orientations; max of 8
+ * \param[in] debugflag 1 for debug output
+ * \return sela with additional sels, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ snprintf(name, sizeof(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;
+ lept_mkdir("lept/sel");
+ pixaGetPixDimensions(pixa, 0, &w, NULL, NULL);
+ pixt = pixaDisplayTiledAndScaled(pixa, 32, w, 1, 0, 10, 2);
+ pixWriteDebug("/tmp/lept/sel/xsel1.png", pixt, IFF_PNG);
+ pixDisplay(pixt, 0, 100);
+ pixDestroy(&pixt);
+ pixt = selaDisplayInPix(sela, 15, 2, 20, 1);
+ pixWriteDebug("/tmp/lept/sel/xsel2.png", pixt, IFF_PNG);
+ pixDisplay(pixt, 500, 100);
+ pixDestroy(&pixt);
+ selaWriteStream(stderr, sela);
+ }
+ pixaDestroy(&pixa);
+
+ return sela;
+}
+
+
+/*!
+ * \brief selaAddTJunctions()
+ *
+ * \param[in] sela [optional]
+ * \param[in] hlsize length of each line of hits from origin
+ * \param[in] mdist distance of misses from the origin
+ * \param[in] norient number of orientations; max of 8
+ * \param[in] debugflag 1 for debug output
+ * \return sela with additional sels, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ snprintf(name, sizeof(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;
+ lept_mkdir("lept/sel");
+ pixaGetPixDimensions(pixa, 0, &w, NULL, NULL);
+ pixt = pixaDisplayTiledAndScaled(pixa, 32, w, 4, 0, 10, 2);
+ pixWriteDebug("/tmp/lept/sel/tsel1.png", pixt, IFF_PNG);
+ pixDisplay(pixt, 0, 100);
+ pixDestroy(&pixt);
+ pixt = selaDisplayInPix(sela, 15, 2, 20, 4);
+ pixWriteDebug("/tmp/lept/sel/tsel2.png", pixt, IFF_PNG);
+ pixDisplay(pixt, 500, 100);
+ pixDestroy(&pixt);
+ selaWriteStream(stderr, sela);
+ }
+ pixaDestroy(&pixa);
+
+ return sela;
+}
+
+
+/* -------------------------------------------------------------------------- *
+ * Structuring elements for connectivity-preserving thinning operations *
+ * -------------------------------------------------------------------------- */
+
+ /* ------------------------------------------------------------
+ * 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";
+
+
+/*!
+ * \brief sela4ccThin()
+ *
+ * \param[in] sela [optional]
+ * \return sela with additional sels, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Adds the 9 basic sels for 4-cc thinning.
+ * </pre>
+ */
+SELA *
+sela4ccThin(SELA *sela)
+{
+SEL *sel;
+
+ if (!sela) sela = selaCreate(9);
+
+ 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);
+ sel = selCreateFromString(sel_4_4, 3, 3, "sel_4_4");
+ 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);
+ sel = selCreateFromString(sel_4_7, 3, 3, "sel_4_7");
+ selaAddSel(sela, sel, NULL, 0);
+ sel = selCreateFromString(sel_4_8, 3, 3, "sel_4_8");
+ selaAddSel(sela, sel, NULL, 0);
+ sel = selCreateFromString(sel_4_9, 3, 3, "sel_4_9");
+ selaAddSel(sela, sel, NULL, 0);
+
+ return sela;
+}
+
+
+/*!
+ * \brief sela8ccThin()
+ *
+ * \param[in] sela [optional]
+ * \return sela with additional sels, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Adds the 9 basic sels for 8-cc thinning.
+ * </pre>
+ */
+SELA *
+sela8ccThin(SELA *sela)
+{
+SEL *sel;
+
+ if (!sela) sela = selaCreate(9);
+
+ sel = selCreateFromString(sel_8_1, 3, 3, "sel_8_1");
+ selaAddSel(sela, sel, NULL, 0);
+ 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_4, 3, 3, "sel_8_4");
+ 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);
+ sel = selCreateFromString(sel_8_7, 3, 3, "sel_8_7");
+ 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);
+
+ return sela;
+}
+
+
+/*!
+ * \brief sela4and8ccThin()
+ *
+ * \param[in] sela [optional]
+ * \return sela with additional sels, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Adds the 2 basic sels for either 4-cc or 8-cc thinning.
+ * </pre>
+ */
+SELA *
+sela4and8ccThin(SELA *sela)
+{
+SEL *sel;
+
+ if (!sela) sela = selaCreate(2);
+
+ sel = selCreateFromString(sel_48_1, 3, 3, "sel_48_1");
+ selaAddSel(sela, sel, NULL, 0);
+ sel = selCreateFromString(sel_48_2, 3, 3, "sel_48_2");
+ selaAddSel(sela, sel, NULL, 0);
+
+ return sela;
+}
+
+
+/* -------------------------------------------------------------------------- *
+ * Other structuring elements *
+ * -------------------------------------------------------------------------- */
+/*!
+ * \brief selMakePlusSign()
+ *
+ * \param[in] size side of containing square
+ * \param[in] linewidth of lines
+ * \return sel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Useful for debugging to show location of selected pixels.
+ * (2) See displaySelectedPixels() for an example of use.
+ * </pre>
+ */
+SEL *
+selMakePlusSign(l_int32 size,
+ l_int32 linewidth)
+{
+PIX *pix;
+SEL *sel;
+
+ PROCNAME("selMakePlusSign");
+
+ if (size < 3 || linewidth > size)
+ return (SEL *)ERROR_PTR("invalid input", procName, NULL);
+
+ pix = pixCreate(size, size, 1);
+ pixRenderLine(pix, size / 2, 0, size / 2, size - 1,
+ linewidth, L_SET_PIXELS);
+ pixRenderLine(pix, 0, size / 2, size, size / 2,
+ linewidth, L_SET_PIXELS);
+ sel = selCreateFromPix(pix, size / 2, size / 2, "plus_sign");
+ pixDestroy(&pix);
+ return sel;
+}
diff --git a/leptonica/src/selgen.c b/leptonica/src/selgen.c
new file mode 100644
index 00000000..13ee214f
--- /dev/null
+++ b/leptonica/src/selgen.c
@@ -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.
+ *====================================================================*/
+
+/*!
+ * \file selgen.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Default minimum distance of a hit-miss pixel element to
+ * a boundary pixel of its color. */
+static const l_int32 DefaultDistanceToBoundary = 1;
+static const l_int32 MaxDistanceToBoundary = 4;
+
+ /* Default min runlength to accept a hit or miss element located
+ * at its center */
+static const l_int32 DefaultMinRunlength = 3;
+
+ /* Default scalefactor for displaying image and hit-miss sel
+ * that is derived from it */
+static const l_int32 DefaultSelScalefactor = 7;
+static const l_int32 MaxSelScalefactor = 31; /* should be big enough */
+
+#ifndef NO_CONSOLE_IO
+#define DEBUG_DISPLAY_HM_SEL 0
+#endif /* ~NO_CONSOLE_IO */
+
+
+/*-----------------------------------------------------------------*
+ * Generate a subsampled structuring element *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixGenerateSelWithRuns()
+ *
+ * \param[in] pixs 1 bpp, typically small, to be used as a pattern
+ * \param[in] nhlines number of hor lines along which elements are found
+ * \param[in] nvlines number of vert lines along which elements are found
+ * \param[in] distance min distance from boundary pixel; use 0 for default
+ * \param[in] minlength min runlength to set hit or miss; use 0 for default
+ * \param[in] toppix number of extra pixels of bg added above
+ * \param[in] botpix number of extra pixels of bg added below
+ * \param[in] leftpix number of extra pixels of bg added to left
+ * \param[in] rightpix number of extra pixels of bg added to right
+ * \param[out] ppixe [optional] input pix expanded by extra pixels
+ * \return sel hit-miss for input pattern, or NULL on error
+ *
+ * <pre>
+ * 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
+ * constraint 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.
+ * </pre>
+ */
+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 = DefaultDistanceToBoundary;
+ if (minlength <= 0)
+ minlength = DefaultMinRunlength;
+ if (distance > MaxDistanceToBoundary) {
+ L_WARNING("distance too large; setting to max value\n", procName);
+ distance = MaxDistanceToBoundary;
+ }
+
+ /* 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;
+}
+
+
+/*!
+ * \brief pixGenerateSelRandom()
+ *
+ * \param[in] pixs 1 bpp, typically small, to be used as a pattern
+ * \param[in] hitfract fraction of allowable fg pixels that are hits
+ * \param[in] missfract fraction of allowable bg pixels that are misses
+ * \param[in] distance min distance from boundary pixel; use 0 for default
+ * \param[in] toppix number of extra pixels of bg added above
+ * \param[in] botpix number of extra pixels of bg added below
+ * \param[in] leftpix number of extra pixels of bg added to left
+ * \param[in] rightpix number of extra pixels of bg added to right
+ * \param[out] ppixe [optional] input pix expanded by extra pixels
+ * \return sel hit-miss for input pattern, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 = DefaultDistanceToBoundary;
+ if (distance > MaxDistanceToBoundary) {
+ L_WARNING("distance too large; setting to max value\n", procName);
+ distance = MaxDistanceToBoundary;
+ }
+
+ /* 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;
+}
+
+
+/*!
+ * \brief pixGenerateSelBoundary()
+ *
+ * \param[in] pixs 1 bpp, typically small, to be used as a pattern
+ * \param[in] hitdist min distance from fg boundary pixel
+ * \param[in] missdist min distance from bg boundary pixel
+ * \param[in] hitskip number of boundary pixels skipped between hits
+ * \param[in] missskip number of boundary pixels skipped between misses
+ * \param[in] topflag flag for extra pixels of bg added above
+ * \param[in] botflag flag for extra pixels of bg added below
+ * \param[in] leftflag flag for extra pixels of bg added to left
+ * \param[in] rightflag flag for extra pixels of bg added to right
+ * \param[out] ppixe [optional] input pix expanded by extra pixels
+ * \return sel hit-miss for input pattern, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixGetRunCentersOnLine()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] x, y set one of these to -1; see notes
+ * \param[in] minlength minimum length of acceptable run
+ * \return numa of fg runs, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGetRunsOnLine()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] x1, y1, x2, y2
+ * \return numa, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ ptaDestroy(&pta);
+ return (NUMA *)ERROR_PTR("pta has no pts", procName, NULL);
+ }
+ if ((numa = numaCreate(0)) == NULL) {
+ ptaDestroy(&pta);
+ 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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixSubsampleBoundaryPixels()
+ *
+ * \param[in] pixs 1 bpp, with only boundary pixels in fg
+ * \param[in] skip number to skip between samples as you traverse boundary
+ * \return pta, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief adjacentOnPixelInRaster()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] x, y current pixel
+ * \param[out] pxa, pya adjacent ON pixel, found by simple CCW search
+ * \return 1 if a pixel is found; 0 otherwise or on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Search is in 4-connected directions first; then on diagonals.
+ * This allows traversal along a 4-connected boundary.
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixDisplayHitMissSel()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] sel hit-miss in general
+ * \param[in] scalefactor an integer >= 1; use 0 for default
+ * \param[in] hitcolor RGB0 color for center of hit pixels
+ * \param[in] misscolor RGB0 color for center of miss pixels
+ * \return pixd RGB showing both pixs and sel, or NULL on error
+ * <pre>
+ * Notes:
+ * (1) We don't allow scalefactor to be larger than MaxSelScalefactor
+ * (2) The colors are conveniently given as 4 bytes in hex format,
+ * such as 0xff008800. The least significant byte is ignored.
+ * </pre>
+ */
+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 = DefaultSelScalefactor;
+ if (scalefactor > MaxSelScalefactor) {
+ L_WARNING("scalefactor too large; using max value\n", procName);
+ scalefactor = MaxSelScalefactor;
+ }
+
+ /* 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/leptonica/src/shear.c b/leptonica/src/shear.c
new file mode 100644
index 00000000..c4f5e92d
--- /dev/null
+++ b/leptonica/src/shear.c
@@ -0,0 +1,854 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file shear.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 MinDiffFromHalfPi = 0.04f;
+
+static l_float32 normalizeAngleForShear(l_float32 radang, l_float32 mindif);
+
+
+#ifndef NO_CONSOLE_IO
+#define DEBUG 0
+#endif /* ~NO_CONSOLE_IO */
+
+
+/*-------------------------------------------------------------*
+ * About arbitrary lines *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixHShear()
+ *
+ * \param[in] pixd [optional] this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs any depth; cmap ok
+ * \param[in] yloc location of horizontal line, measured from origin
+ * \param[in] radang angle in radians
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return pixd, always
+ *
+ * <pre>
+ * 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) In-place shears do not work on cmapped pix, because the
+ * in-place operation cannot initialize to the requested %incolor,
+ * so we shear from a copy.
+ * (8) The angle is brought into the range [-pi, -pi]. It is
+ * not permitted to be within MinDiffFromHalfPi radians
+ * from either -pi/2 or pi/2.
+ * </pre>
+ */
+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)) {
+ pixHShearIP(pixd, yloc, radang, incolor);
+ } else { /* can't do in-place with a colormap */
+ PIX *pix1 = pixCopy(NULL, pixs);
+ pixHShear(pixd, pix1, yloc, radang, incolor);
+ pixDestroy(&pix1);
+ }
+ 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, MinDiffFromHalfPi);
+ 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
+ lept_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
+ lept_stderr("y = %d, hshift = %d, yincr = %d\n",
+ y - yincr, hshift, yincr);
+#endif /* DEBUG */
+ y -= yincr;
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixVShear()
+ *
+ * \param[in] pixd [optional], this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs any depth; cmap ok
+ * \param[in] xloc location of vertical line, measured from origin
+ * \param[in] radang angle in radians; not too close to +-(pi / 2)
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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) In-place shears do not work on cmapped pix, because the
+ * in-place operation cannot initialize to the requested %incolor,
+ * so we shear from a copy.
+ * (8) The angle is brought into the range [-pi, -pi]. It is
+ * not permitted to be within MinDiffFromHalfPi radians
+ * from either -pi/2 or pi/2.
+ * </pre>
+ */
+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)) {
+ pixVShearIP(pixd, xloc, radang, incolor);
+ } else { /* can't do in-place with a colormap */
+ PIX *pix1 = pixCopy(NULL, pixs);
+ pixVShear(pixd, pix1, xloc, radang, incolor);
+ pixDestroy(&pix1);
+ }
+ 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, MinDiffFromHalfPi);
+ 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
+ lept_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
+ lept_stderr("x = %d, vshift = %d, xincr = %d\n",
+ x - xincr, vshift, xincr);
+#endif /* DEBUG */
+ x -= xincr;
+ }
+
+ return pixd;
+}
+
+
+
+/*-------------------------------------------------------------*
+ * Shears about UL corner and center *
+ *-------------------------------------------------------------*/
+/*!
+ * \brief pixHShearCorner()
+ *
+ * \param[in] pixd [optional], if not null, must be equal to pixs
+ * \param[in] pixs any depth
+ * \param[in] radang angle in radians
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return pixd, or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixVShearCorner()
+ *
+ * \param[in] pixd [optional], if not null, must be equal to pixs
+ * \param[in] pixs any depth
+ * \param[in] radang angle in radians
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return pixd, or NULL on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixHShearCenter()
+ *
+ * \param[in] pixd [optional] if not null, must be equal to pixs
+ * \param[in] pixs any depth
+ * \param[in] radang angle in radians
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return pixd, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixHShear() for usage.
+ * (2) This does a horizontal shear about the center, with (+) shear
+ * pushing increasingly leftward (-x) with increasing y.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixVShearCenter()
+ *
+ * \param[in] pixd [optional] if not null, must be equal to pixs
+ * \param[in] pixs any depth
+ * \param[in] radang angle in radians
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return pixd, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixVShear() for usage.
+ * (2) This does a vertical shear about the center, with (+) shear
+ * pushing increasingly downward (+y) with increasing x.
+ * </pre>
+ */
+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 *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief pixHShearIP()
+ *
+ * \param[in] pixs any depth; no cmap
+ * \param[in] yloc location of horizontal line, measured from origin
+ * \param[in] radang angle in radians
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, MinDiffFromHalfPi);
+ 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;
+}
+
+
+/*!
+ * \brief pixVShearIP()
+ *
+ * \param[in] pixs any depth; no cmap
+ * \param[in] xloc location of vertical line, measured from origin
+ * \param[in] radang angle in radians
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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, MinDiffFromHalfPi);
+ 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 *
+ *-------------------------------------------------------------------------*/
+/*!
+ * \brief pixHShearLI()
+ *
+ * \param[in] pixs 8 bpp or 32 bpp, or colormapped
+ * \param[in] yloc location of horizontal line, measured from origin
+ * \param[in] radang angle in radians, in range (-pi/2 ... pi/2)
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return pixd sheared, or NULL on error
+ *
+ * <pre>
+ * 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 == MinDiffFromHalfPi.
+ * </pre>
+ */
+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, MinDiffFromHalfPi);
+ 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;
+}
+
+
+/*!
+ * \brief pixVShearLI()
+ *
+ * \param[in] pixs 8 bpp or 32 bpp, or colormapped
+ * \param[in] xloc location of vertical line, measured from origin
+ * \param[in] radang angle in radians, in range (-pi/2 ... pi/2)
+ * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
+ * \return pixd sheared, or NULL on error
+ *
+ * <pre>
+ * 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 == MinDiffFromHalfPi.
+ * </pre>
+ */
+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, MinDiffFromHalfPi);
+ 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.14159265f / 2.0f;
+ 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/leptonica/src/skew.c b/leptonica/src/skew.c
new file mode 100644
index 00000000..087f8ba9
--- /dev/null
+++ b/leptonica/src/skew.c
@@ -0,0 +1,1247 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file skew.c
+ * <pre>
+ *
+ * Top-level deskew interfaces
+ * PIX *pixDeskewBoth()
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <math.h>
+#include "allheaders.h"
+
+ /* Default sweep angle parameters for pixFindSkew() */
+static const l_float32 DefaultSweepRange = 7.0; /* degrees */
+static const l_float32 DefaultSweepDelta = 1.0; /* 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 DefaultMinbsDelta = 0.01f; /* degrees */
+
+ /* Default scale factors for pixFindSkew() */
+static const l_int32 DefaultSweepReduction = 4; /* sweep part; 4 is good */
+static const l_int32 DefaultBsReduction = 2; /* binary search part */
+
+ /* Minimum angle for deskewing in pixDeskew() */
+static const l_float32 MinDeskewAngle = 0.1f; /* degree */
+
+ /* Minimum allowed confidence (ratio) for deskewing in pixDeskew() */
+static const l_float32 MinAllowedConfidence = 3.0;
+
+ /* Minimum allowed maxscore to give nonzero confidence */
+static const l_int32 MinValidMaxscore = 10000;
+
+ /* Constant setting threshold for minimum allowed minscore
+ * to give nonzero confidence; multiply this constant by
+ * (height * width^2) */
+static const l_float32 MinscoreThreshFactor = 0.000002f;
+
+ /* Default binarization threshold value */
+static const l_int32 DefaultBinaryThreshold = 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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixDeskewBoth()
+ *
+ * \param[in] pixs any depth
+ * \param[in] redsearch for binary search: reduction factor = 1, 2 or 4;
+ * use 0 for default
+ * \return pixd deskewed pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This binarizes if necessary and does both horizontal
+ * and vertical deskewing, using the default parameters in
+ * the underlying pixDeskew(). See usage there.
+ * (2) This may return a clone.
+ * </pre>
+ */
+PIX *
+pixDeskewBoth(PIX *pixs,
+ l_int32 redsearch)
+{
+PIX *pix1, *pix2, *pix3, *pix4;
+
+ PROCNAME("pixDeskewBoth");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+ if (redsearch == 0)
+ redsearch = DefaultBsReduction;
+ else if (redsearch != 1 && redsearch != 2 && redsearch != 4)
+ return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL);
+
+ pix1 = pixDeskew(pixs, redsearch);
+ pix2 = pixRotate90(pix1, 1);
+ pix3 = pixDeskew(pix2, redsearch);
+ pix4 = pixRotate90(pix3, -1);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ return pix4;
+}
+
+
+/*!
+ * \brief pixDeskew()
+ *
+ * \param[in] pixs any depth
+ * \param[in] redsearch for binary search: reduction factor = 1, 2 or 4;
+ * use 0 for default
+ * \return pixd deskewed pix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * (2) Typical values at 300 ppi for %redsearch are 2 and 4.
+ * At 75 ppi, one should use %redsearch = 1.
+ * </pre>
+ */
+PIX *
+pixDeskew(PIX *pixs,
+ l_int32 redsearch)
+{
+ PROCNAME("pixDeskew");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+ if (redsearch == 0)
+ redsearch = DefaultBsReduction;
+ 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);
+}
+
+
+/*!
+ * \brief pixFindSkewAndDeskew()
+ *
+ * \param[in] pixs any depth
+ * \param[in] redsearch for binary search: reduction factor = 1, 2 or 4;
+ * use 0 for default
+ * \param[out] pangle [optional] angle required to deskew,
+ * in degrees; use NULL to skip
+ * \param[out] pconf [optional] conf value is ratio
+ * of max/min scores; use NULL to skip
+ * \return pixd deskewed pix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 = DefaultBsReduction;
+ 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);
+}
+
+
+/*!
+ * \brief pixDeskewGeneral()
+ *
+ * \param[in] pixs any depth
+ * \param[in] redsweep for linear search: reduction factor = 1, 2 or 4;
+ * use 0 for default
+ * \param[in] sweeprange in degrees in each direction from 0;
+ * use 0.0 for default
+ * \param[in] sweepdelta in degrees; use 0.0 for default
+ * \param[in] redsearch for binary search: reduction factor = 1, 2 or 4;
+ * use 0 for default;
+ * \param[in] thresh for binarizing the image; use 0 for default
+ * \param[out] pangle [optional] angle required to deskew,
+ * in degrees; use NULL to skip
+ * \param[out] pconf [optional] conf value is ratio
+ * of max/min scores; use NULL to skip
+ * \return pixd deskewed pix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 = DefaultSweepReduction;
+ 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 = DefaultSweepRange;
+ if (sweepdelta == 0.0)
+ sweepdelta = DefaultSweepDelta;
+ if (redsearch == 0)
+ redsearch = DefaultBsReduction;
+ else if (redsearch != 1 && redsearch != 2 && redsearch != 4)
+ return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL);
+ if (thresh == 0)
+ thresh = DefaultBinaryThreshold;
+
+ deg2rad = 3.1415926535f / 180.f;
+
+ /* 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,
+ DefaultMinbsDelta);
+ pixDestroy(&pixb);
+ if (pangle) *pangle = angle;
+ if (pconf) *pconf = conf;
+ if (ret)
+ return pixClone(pixs);
+
+ if (L_ABS(angle) < MinDeskewAngle || conf < MinAllowedConfidence)
+ 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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixFindSkew()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[out] pangle angle required to deskew, in degrees
+ * \param[out] pconf confidence value is ratio max/min scores
+ * \return 0 if OK, 1 on error or if angle measurement not valid
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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,
+ DefaultSweepReduction,
+ DefaultBsReduction,
+ DefaultSweepRange,
+ DefaultSweepDelta,
+ DefaultMinbsDelta);
+}
+
+
+/*-----------------------------------------------------------------------*
+ * Basic angle-finding functions *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixFindSkewSweep()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[out] pangle angle required to deskew, in degrees
+ * \param[in] reduction factor = 1, 2, 4 or 8
+ * \param[in] sweeprange half the full range; assumed about 0; in degrees
+ * \param[in] sweepdelta angle increment of sweep; in degrees
+ * \return 0 if OK, 1 on error or if angle measurement not valid
+ *
+ * <pre>
+ * Notes:
+ * (1) This examines the 'score' for skew angles with equal intervals.
+ * (2) Caller must check the return value for validity of the result.
+ * </pre>
+ */
+l_ok
+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.1415926535f / 180.f;
+ 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;
+}
+
+
+/*!
+ * \brief pixFindSkewSweepAndSearch()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[out] pangle angle required to deskew; in degrees
+ * \param[out] pconf confidence given by ratio of max/min score
+ * \param[in] redsweep sweep reduction factor = 1, 2, 4 or 8
+ * \param[in] redsearch binary search reduction factor = 1, 2, 4 or 8;
+ * and must not exceed redsweep
+ * \param[in] sweeprange half the full range, assumed about 0; in degrees
+ * \param[in] sweepdelta angle increment of sweep; in degrees
+ * \param[in] minbsdelta min binary search increment angle; in degrees
+ * \return 0 if OK, 1 on error or if angle measurement not valid
+ *
+ * <pre>
+ * 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()
+ * </pre>
+ */
+l_ok
+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);
+}
+
+
+/*!
+ * \brief pixFindSkewSweepAndSearchScore()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[out] pangle angle required to deskew; in degrees
+ * \param[out] pconf confidence given by ratio of max/min score
+ * \param[out] pendscore [optional] max score; use NULL to ignore
+ * \param[in] redsweep sweep reduction factor = 1, 2, 4 or 8
+ * \param[in] redsearch binary search reduction factor = 1, 2, 4 or 8;
+ * and must not exceed redsweep
+ * \param[in] sweepcenter angle about which sweep is performed; in degrees
+ * \param[in] sweeprange half the full range, taken about sweepcenter;
+ * in degrees
+ * \param[in] sweepdelta angle increment of sweep; in degrees
+ * \param[in] minbsdelta min binary search increment angle; in degrees
+ * \return 0 if OK, 1 on error or if angle measurement not valid
+ *
+ * <pre>
+ * 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:
+ * ~ MinValidMaxscore (minimum allowed maxscore)
+ * ~ MinscoreThreshFactor (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.
+ * </pre>
+ */
+l_ok
+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);
+}
+
+
+/*!
+ * \brief pixFindSkewSweepAndSearchScorePivot()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[out] pangle angle required to deskew; in degrees
+ * \param[out] pconf confidence given by ratio of max/min score
+ * \param[out] pendscore [optional] max score; use NULL to ignore
+ * \param[in] redsweep sweep reduction factor = 1, 2, 4 or 8
+ * \param[in] redsearch binary search reduction factor = 1, 2, 4 or 8;
+ * and must not exceed redsweep
+ * \param[in] sweepcenter angle about which sweep is performed; in degrees
+ * \param[in] sweeprange half the full range, taken about sweepcenter;
+ * in degrees
+ * \param[in] sweepdelta angle increment of sweep; in degrees
+ * \param[in] minbsdelta min binary search increment angle; in degrees
+ * \param[in] pivot L_SHEAR_ABOUT_CORNER, L_SHEAR_ABOUT_CENTER
+ * \return 0 if OK, 1 on error or if angle measurement not valid
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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.1415926535f / 180.f;
+ 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 = MinscoreThreshFactor * 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 < MinValidMaxscore))
+ *pconf = 0.0;
+
+#if DEBUG_PRINT_BINARY
+ lept_stderr("Binary search: angle = %7.3f, score ratio = %6.2f\n",
+ *pangle, *pconf);
+ lept_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 *
+ *---------------------------------------------------------------------*/
+/*
+ * \brief pixFindSkewOrthogonalRange()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[out] pangle angle required to deskew; in degrees cw
+ * \param[out] pconf confidence given by ratio of max/min score
+ * \param[in] redsweep sweep reduction factor = 1, 2, 4 or 8
+ * \param[in] redsearch binary search reduction factor = 1, 2, 4 or 8;
+ * and must not exceed redsweep
+ * \param[in] sweeprange half the full range in each orthogonal
+ * direction, taken about 0, in degrees
+ * \param[in] sweepdelta angle increment of sweep; in degrees
+ * \param[in] minbsdelta min binary search increment angle; in degrees
+ * \param[in] 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 measurement not valid
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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
+ lept_stderr(" About 0: angle1 = %7.3f, conf1 = %7.3f, score1 = %f\n",
+ angle1, conf1, score1);
+ lept_stderr(" About 90: angle2 = %7.3f, conf2 = %7.3f, score2 = %f\n",
+ angle2, conf2, score2);
+ lept_stderr(" Final: angle = %7.3f, conf = %7.3f\n", *pangle, *pconf);
+#endif /* DEBUG_PRINT_ORTH */
+
+ return 0;
+}
+
+
+
+/*----------------------------------------------------------------*
+ * Differential square sum function *
+ *----------------------------------------------------------------*/
+/*!
+ * \brief pixFindDifferentialSquareSum()
+ *
+ * \param[in] pixs
+ * \param[out] psum result
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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 *
+ *----------------------------------------------------------------*/
+/*!
+ * \brief pixFindNormalizedSquareSum()
+ *
+ * \param[in] pixs
+ * \param[out] phratio [optional] ratio of normalized horiz square sum
+ * to result if the pixel distribution were uniform
+ * \param[out] pvratio [optional] ratio of normalized vert square sum
+ * to result if the pixel distribution were uniform
+ * \param[out] pfract [optional] ratio of fg pixels to total pixels
+ * \return 0 if OK, 1 on error or if there are no fg pixels
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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/leptonica/src/spixio.c b/leptonica/src/spixio.c
new file mode 100644
index 00000000..229f4f46
--- /dev/null
+++ b/leptonica/src/spixio.c
@@ -0,0 +1,518 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file spixio.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* Image dimension limits */
+static const l_int32 MaxAllowedWidth = 1000000;
+static const l_int32 MaxAllowedHeight = 1000000;
+static const l_int64 MaxAllowedArea = 400000000LL;
+
+#ifndef NO_CONSOLE_IO
+#define DEBUG_SERIALIZE 0
+#endif /* ~NO_CONSOLE_IO */
+
+
+/*-----------------------------------------------------------------------*
+ * Reading spix from file *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixReadStreamSpix()
+ *
+ * \param[in] fp file stream
+ * \return pix, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) If called from pixReadStream(), the stream is positioned
+ * at the beginning of the file.
+ * </pre>
+ */
+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);
+ pix = pixReadMemSpix(data, nbytes);
+ LEPT_FREE(data);
+ if (!pix)
+ return (PIX *)ERROR_PTR("pix not made", procName, NULL);
+ return pix;
+}
+
+
+/*!
+ * \brief readHeaderSpix()
+ *
+ * \param[in] filename
+ * \param[out] pwidth width
+ * \param[out] pheight height
+ * \param[out] pbps bits/sample
+ * \param[out] pspp samples/pixel
+ * \param[out] piscmap [optional] input NULL to ignore
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If there is a colormap, iscmap is returned as 1; else 0.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief freadHeaderSpix()
+ *
+ * \param[in] fp file stream
+ * \param[out] pwidth width
+ * \param[out] pheight height
+ * \param[out] pbps bits/sample
+ * \param[out] pspp samples/pixel
+ * \param[out] piscmap [optional] input NULL to ignore
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If there is a colormap, iscmap is returned as 1; else 0.
+ * </pre>
+ */
+l_ok
+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[6];
+
+ 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 (fread(data, 4, 6, fp) != 6)
+ return ERROR_INT("error reading data", procName, 1);
+ ret = sreadHeaderSpix(data, nbytes, pwidth, pheight, pbps, pspp, piscmap);
+ return ret;
+}
+
+
+/*!
+ * \brief sreadHeaderSpix()
+ *
+ * \param[in] data
+ * \param[in] size of data
+ * \param[out] pwidth width
+ * \param[out] pheight height
+ * \param[out] pbps bits/sample
+ * \param[out] pspp samples/pixel
+ * \param[out] piscmap [optional] input NULL to ignore
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If there is a colormap, iscmap is returned as 1; else 0.
+ * </pre>
+ */
+l_ok
+sreadHeaderSpix(const l_uint32 *data,
+ size_t size,
+ 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;
+ if (size < 28)
+ return ERROR_INT("size too small", procName, 1);
+
+ /* 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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixWriteStreamSpix()
+ *
+ * \param[in] fp file stream
+ * \param[in] pix
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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) *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief pixReadMemSpix()
+ *
+ * \param[in] data const; uncompressed
+ * \param[in] size bytes of data
+ * \return pix, or NULL on error
+ */
+PIX *
+pixReadMemSpix(const l_uint8 *data,
+ size_t size)
+{
+ return pixDeserializeFromMemory((l_uint32 *)data, size);
+}
+
+
+/*!
+ * \brief pixWriteMemSpix()
+ *
+ * \param[out] pdata data of serialized, uncompressed pix
+ * \param[out] psize size of returned data
+ * \param[in] pix all depths; colormap OK
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+pixWriteMemSpix(l_uint8 **pdata,
+ size_t *psize,
+ PIX *pix)
+{
+ return pixSerializeToMemory(pix, (l_uint32 **)pdata, psize);
+}
+
+
+/*!
+ * \brief pixSerializeToMemory()
+ *
+ * \param[in] pixs all depths, colormap OK
+ * \param[out] pdata serialized data in memory
+ * \param[out] pnbytes number of bytes in data string
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+l_ok
+pixSerializeToMemory(PIX *pixs,
+ l_uint32 **pdata,
+ size_t *pnbytes)
+{
+char *id;
+l_int32 w, h, d, wpl, rdatasize, ncolors, nbytes, index, valid;
+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) {
+ pixcmapIsValid(cmap, pixs, &valid);
+ if (!valid)
+ return ERROR_INT("colormap not valid", procName, 1);
+ pixcmapSerializeToMemory(cmap, 4, &ncolors, &cdata);
+ }
+
+ nbytes = 24 + 4 * ncolors + 4 + rdatasize;
+ if ((data = (l_uint32 *)LEPT_CALLOC(nbytes / 4, sizeof(l_uint32)))
+ == NULL) {
+ LEPT_FREE(cdata);
+ 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(data + 6, cdata, 4 * ncolors);
+ index = 6 + ncolors;
+ data[index] = rdatasize;
+ memcpy(data + index + 1, rdata, rdatasize);
+
+#if DEBUG_SERIALIZE
+ lept_stderr("Serialize: "
+ "raster size = %d, ncolors in cmap = %d, total bytes = %d\n",
+ rdatasize, ncolors, nbytes);
+#endif /* DEBUG_SERIALIZE */
+
+ LEPT_FREE(cdata);
+ return 0;
+}
+
+
+/*!
+ * \brief pixDeserializeFromMemory()
+ *
+ * \param[in] data serialized data in memory
+ * \param[in] nbytes number of bytes in data string
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixSerializeToMemory() for the binary format.
+ * (2) Note the image size limits.
+ * </pre>
+ */
+PIX *
+pixDeserializeFromMemory(const l_uint32 *data,
+ size_t nbytes)
+{
+char *id;
+l_int32 w, h, d, pixdata_size, memdata_size, imdata_size, ncolors, valid;
+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 || nbytes > ((1LL << 31) - 1)) {
+ L_ERROR("invalid nbytes = %zu\n", procName, nbytes);
+ return 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];
+
+ /* Sanity checks on the amount of image data */
+ if (w < 1 || w > MaxAllowedWidth)
+ return (PIX *)ERROR_PTR("invalid width", procName, NULL);
+ if (h < 1 || h > MaxAllowedHeight)
+ return (PIX *)ERROR_PTR("invalid height", procName, NULL);
+ if (1LL * w * h > MaxAllowedArea)
+ return (PIX *)ERROR_PTR("area too large", procName, NULL);
+ if (ncolors < 0 || ncolors > 256 || ncolors + 7 >= nbytes/sizeof(l_int32))
+ return (PIX *)ERROR_PTR("invalid ncolors", procName, NULL);
+ if ((pix1 = pixCreateHeader(w, h, d)) == NULL) /* just make the header */
+ 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) {
+ pixDestroy(&pixd);
+ return (PIX *)ERROR_PTR("cmap not made", procName, NULL);
+ }
+ if (pixSetColormap(pixd, cmap)) {
+ pixDestroy(&pixd);
+ return (PIX *)ERROR_PTR("cmap is not valid", procName, NULL);
+ }
+ }
+
+ /* Read the raster data */
+ imdata = pixGetData(pixd);
+ memcpy(imdata, data + 7 + ncolors, imdata_size);
+
+ /* Verify that the colormap is valid with the pix */
+ if (ncolors > 0) {
+ pixcmapIsValid(cmap, pixd, &valid);
+ if (!valid) {
+ pixDestroy(&pixd);
+ return (PIX *)ERROR_PTR("cmap is invalid with pix", procName, NULL);
+ }
+ }
+
+#if DEBUG_SERIALIZE
+ lept_stderr("Deserialize: "
+ "raster size = %d, ncolors in cmap = %d, total bytes = %zu\n",
+ imdata_size, ncolors, nbytes);
+#endif /* DEBUG_SERIALIZE */
+
+ return pixd;
+}
diff --git a/leptonica/src/stack.c b/leptonica/src/stack.c
new file mode 100644
index 00000000..a0811fbb
--- /dev/null
+++ b/leptonica/src/stack.c
@@ -0,0 +1,293 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file stack.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+ /* Bounds on initial array size */
+static const l_uint32 MaxPtrArraySize = 100000;
+static const l_int32 InitialPtrArraySize = 20; /*!< n'importe quoi */
+
+ /* Static function */
+static l_int32 lstackExtendArray(L_STACK *lstack);
+
+/*---------------------------------------------------------------------*
+ * Create/Destroy *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief lstackCreate()
+ *
+ * \param[in] n initial ptr array size; use 0 for default
+ * \return lstack, or NULL on error
+ */
+L_STACK *
+lstackCreate(l_int32 n)
+{
+L_STACK *lstack;
+
+ PROCNAME("lstackCreate");
+
+ if (n <= 0 || n > MaxPtrArraySize)
+ n = InitialPtrArraySize;
+
+ lstack = (L_STACK *)LEPT_CALLOC(1, sizeof(L_STACK));
+ lstack->array = (void **)LEPT_CALLOC(n, sizeof(void *));
+ if (!lstack->array) {
+ lstackDestroy(&lstack, FALSE);
+ return (L_STACK *)ERROR_PTR("lstack array not made", procName, NULL);
+ }
+
+ lstack->nalloc = n;
+ lstack->n = 0;
+ return lstack;
+}
+
+
+/*!
+ * \brief lstackDestroy()
+ *
+ * \param[in,out] plstack will be set to null before returning
+ * \param[in] freeflag TRUE to free each remaining struct in the array
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief lstackAdd()
+ *
+ * \param[in] lstack
+ * \param[in] item to be added to the lstack
+ * \return 0 if OK; 1 on error.
+ */
+l_ok
+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) {
+ if (lstackExtendArray(lstack))
+ return ERROR_INT("extension failed", procName, 1);
+ }
+
+ /* Store the new pointer */
+ lstack->array[lstack->n] = (void *)item;
+ lstack->n++;
+
+ return 0;
+}
+
+
+/*!
+ * \brief lstackRemove()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief lstackExtendArray()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief lstackGetCount()
+ *
+ * \param[in] 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief lstackPrint()
+ *
+ * \param[in] fp file stream
+ * \param[in] lstack
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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/leptonica/src/stack.h b/leptonica/src/stack.h
new file mode 100644
index 00000000..4fa61141
--- /dev/null
+++ b/leptonica/src/stack.h
@@ -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.
+ *====================================================================*/
+
+#ifndef LEPTONICA_STACK_H
+#define LEPTONICA_STACK_H
+
+/*!
+ * \file stack.h
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+
+
+ /*! Expandable pointer stack for arbitrary void* data.
+ * 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/leptonica/src/stringcode.c b/leptonica/src/stringcode.c
new file mode 100644
index 00000000..76c9fdc7
--- /dev/null
+++ b/leptonica/src/stringcode.c
@@ -0,0 +1,806 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file stringcode.c
+ * <pre>
+ *
+ * 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()
+ * l_int32 strcodeFinalize()
+ * l_int32 l_getStructStrFromFile() (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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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" */
+ char memreader[20]; /* e.g., "pixaReadMem" */
+};
+
+ /*! Number of serializable data types */
+static const l_int32 l_ntypes = 19;
+ /*! Serializable data types */
+static const struct L_GenAssoc l_assoc[] = {
+ {0, "INVALID", "invalid", "invalid", "invalid" },
+ {1, "BOXA", "Boxa", "boxaRead", "boxaReadMem" },
+ {2, "BOXAA", "Boxaa", "boxaaRead", "boxaaReadMem" },
+ {3, "L_DEWARP", "Dewarp", "dewarpRead", "dewarpReadMem" },
+ {4, "L_DEWARPA", "Dewarpa", "dewarpaRead", "dewarpaReadMem" },
+ {5, "L_DNA", "L_Dna", "l_dnaRead", "l_dnaReadMem" },
+ {6, "L_DNAA", "L_Dnaa", "l_dnaaRead", "l_dnaaReadMem" },
+ {7, "DPIX", "DPix", "dpixRead", "dpixReadMem" },
+ {8, "FPIX", "FPix", "fpixRead", "fpixReadMem" },
+ {9, "NUMA", "Numa", "numaRead", "numaReadMem" },
+ {10, "NUMAA", "Numaa", "numaaRead", "numaaReadMem" },
+ {11, "PIX", "Pix", "pixRead", "pixReadMem" },
+ {12, "PIXA", "Pixa", "pixaRead", "pixaReadMem" },
+ {13, "PIXAA", "Pixaa", "pixaaRead", "pixaaReadMem" },
+ {14, "PIXACOMP", "Pixacomp", "pixacompRead", "pixacompReadMem" },
+ {15, "PIXCMAP", "Pixcmap", "pixcmapRead", "pixcmapReadMem" },
+ {16, "PTA", "Pta", "ptaRead", "ptaReadMem" },
+ {17, "PTAA", "Ptaa", "ptaaRead", "ptaaReadMem" },
+ {18, "RECOG", "Recog", "recogRead", "recogReadMem" },
+ {19, "SARRAY", "Sarray", "sarrayRead", "sarrayReadMem" }
+};
+
+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 */
+/*---------------------------------------------------------------------*/
+/*!
+ * \brief strcodeCreate()
+ *
+ * \param[in] fileno integer that labels the two output files
+ * \return initialized L_StrCode, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief strcodeDestroy()
+ *
+ * \param[out] pstrcode will be 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;
+}
+
+
+/*!
+ * \brief strcodeCreateFromFile()
+ *
+ * \param[in] filein containing filenames of serialized data
+ * \param[in] fileno integer that labels the two output files
+ * \param[in] outdir [optional] if null, files are made in /tmp/lept/auto
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+l_ok
+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);
+ sarrayDestroy(&sa);
+ return 0;
+}
+
+
+/*!
+ * \brief strcodeGenerate()
+ *
+ * \param[in] strcode for accumulating data
+ * \param[in] filein input file with serialized data
+ * \param[in] type of data; use the typedef string
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief strcodeFinalize()
+ *
+ * \param[in,out] pstrcode destroys and sets to null after .c and .h files
+ * have been generated
+ * \param[in] outdir [optional] if NULL, make files in /tmp/lept/auto
+ * \return 0 if OK; 1 on error
+ */
+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 */
+ filestr = (char *)l_binaryRead(TEMPLATE1, &size);
+ sa1 = sarrayCreateLinesFromString(filestr, 1);
+ LEPT_FREE(filestr);
+ sa3 = sarrayCreate(0);
+
+ /* 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), " * \\brief 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 */
+ filestr = sarrayToString(sa3, 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 */
+ filestr = (char *)l_binaryRead(TEMPLATE2, &size);
+ sa2 = sarrayCreateLinesFromString(filestr, 1);
+ LEPT_FREE(filestr);
+ sa3 = sarrayCreate(0);
+
+ /* 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 */
+ filestr = sarrayToString(sa3, 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;
+}
+
+
+/*!
+ * \brief l_getStructStrFromFile()
+ *
+ * \param[in] filename
+ * \param[in] field (L_STR_TYPE, L_STR_NAME, L_STR_READER, L_STR_MEMREADER)
+ * \param[out] pstr struct string for this file
+ * \return 0 if found, 1 on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) For example, if %field == L_STR_NAME, and the file is a serialized
+ * pixa, this will return "Pixa", the name of the struct.
+ * (2) Caller must free the returned string.
+ * </pre>
+ */
+l_int32
+l_getStructStrFromFile(const char *filename,
+ l_int32 field,
+ char **pstr)
+{
+l_int32 index;
+
+ PROCNAME("l_getStructStrFromFile");
+
+ if (!pstr)
+ return ERROR_INT("&str not defined", procName, 1);
+ *pstr = NULL;
+ if (!filename)
+ return ERROR_INT("filename not defined", procName, 1);
+ if (field != L_STR_TYPE && field != L_STR_NAME &&
+ field != L_STR_READER && field != L_STR_MEMREADER)
+ return ERROR_INT("invalid field", procName, 1);
+
+ if (l_getIndexFromFile(filename, &index))
+ return ERROR_INT("index not retrieved", procName, 1);
+ if (field == L_STR_TYPE)
+ *pstr = stringNew(l_assoc[index].type);
+ else if (field == L_STR_NAME)
+ *pstr = stringNew(l_assoc[index].structname);
+ else if (field == L_STR_READER)
+ *pstr = stringNew(l_assoc[index].reader);
+ else /* field == L_STR_MEMREADER */
+ *pstr = stringNew(l_assoc[index].memreader);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Static helpers */
+/*---------------------------------------------------------------------*/
+/*!
+ * \brief l_getIndexFromType()
+ *
+ * \param[in] type e.g., "PIXA"
+ * \param[out] pindex found index
+ * \return 0 if found, 1 if not.
+ *
+ * <pre>
+ * Notes:
+ * (1) For valid type, %found == true and %index > 0.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_getIndexFromStructname()
+ *
+ * \param[in] sn structname e.g., "Pixa"
+ * \param[out] pindex found index
+ * \return 0 if found, 1 if not.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_getIndexFromFile()
+ *
+ * \param[in] filename
+ * \param[out] pindex found index
+ * \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;
+}
+
+
+/*!
+ * \brief l_genDataString()
+ *
+ * \param[in] filein input file of serialized data
+ * \param[in] 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, ";\n", L_COPY);
+ cdata3 = sarrayToString(sa, 0);
+ sarrayDestroy(&sa);
+ return cdata3;
+}
+
+
+/*!
+ * \brief l_genCaseString()
+ *
+ * \param[in] ifunc index into set of functions in generated file
+ * \param[in] itype index into type of function to be used
+ * \return case string for this decoding function
+ *
+ * <pre>
+ * Notes:
+ * (1) %ifunc and %itype have been validated, so no error can occur
+ * </pre>
+ */
+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");
+ snprintf(buf, sizeof(buf),
+ " result = (void *)%s(data2, size2);\n",
+ l_assoc[itype].memreader);
+ stringJoinIP(&code, buf);
+ stringJoinIP(&code, " lept_free(data1);\n");
+ stringJoinIP(&code, " lept_free(data2);\n");
+ stringJoinIP(&code, " break;\n");
+ return code;
+}
+
+
+/*!
+ * \brief l_genDescrString()
+ *
+ * \param[in] filein input file of serialized data
+ * \param[in] ifunc index into set of functions in generated file
+ * \param[in] 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/leptonica/src/stringcode.h b/leptonica/src/stringcode.h
new file mode 100644
index 00000000..4510bdb0
--- /dev/null
+++ b/leptonica/src/stringcode.h
@@ -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.
+ *====================================================================*/
+
+#ifndef LEPTONICA_STRINGCODE_H
+#define LEPTONICA_STRINGCODE_H
+
+/*!
+ * \file stringcode.h
+ *
+ * Data structure to hold accumulating generated code for storing
+ * and extracting serializable leptonica objects (e.g., pixa, recog).
+ *
+ * Also a flag for selecting a string from the L_GenAssoc struct
+ * in stringcode.
+ */
+
+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;
+
+
+ /*! Select string in stringcode for a specific serializable data type */
+/*! Stringcode Select */
+enum {
+ L_STR_TYPE = 0, /*!< typedef for the data type */
+ L_STR_NAME = 1, /*!< name of the data type */
+ L_STR_READER = 2, /*!< reader to get the data type from file */
+ L_STR_MEMREADER = 3 /*!< reader to get the compressed string in memory */
+};
+
+#endif /* LEPTONICA_STRINGCODE_H */
diff --git a/leptonica/src/stringtemplate1.txt b/leptonica/src/stringtemplate1.txt
new file mode 100644
index 00000000..aea771f9
--- /dev/null
+++ b/leptonica/src/stringtemplate1.txt
@@ -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 */
+/*---------------------------------------------------------------------*/
+/*!
+--- * \brief l_autodecode_*()
+ *
+ * \param[in] 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 the selected string, uncompress it, 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/leptonica/src/stringtemplate2.txt b/leptonica/src/stringtemplate2.txt
new file mode 100644
index 00000000..20c853ad
--- /dev/null
+++ b/leptonica/src/stringtemplate2.txt
@@ -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/leptonica/src/strokes.c b/leptonica/src/strokes.c
new file mode 100644
index 00000000..e611e06c
--- /dev/null
+++ b/leptonica/src/strokes.c
@@ -0,0 +1,439 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file strokes.c
+ * <pre>
+ *
+ * Operations on 1 bpp images to:
+ * (1) measure stroke parameters, such as length and average width
+ * (2) change the average stroke width to a given value by eroding
+ * or dilating the image.
+ *
+ * These operations are intended to operate on a single text
+ * character, to regularize the stroke width. It is expected
+ * that character matching by correlation, as used in the recog
+ * application, can often be improved by pre-processing both
+ * template and character images to a fixed stroke width.
+ *
+ * Stroke parameter measurement
+ * l_int32 pixFindStrokeLength()
+ * l_int32 pixFindStrokeWidth()
+ * NUMA *pixaFindStrokeWidth()
+ *
+ * Stroke width regulation
+ * PIXA *pixaModifyStrokeWidth()
+ * PIX *pixModifyStrokeWidth()
+ * PIXA *pixaSetStrokeWidth()
+ * PIX *pixSetStrokeWidth()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*-----------------------------------------------------------------*
+ * Stroke parameter measurement *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixFindStrokeLength()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] tab8 [optional] table for counting fg pixels; can be NULL
+ * \param[out] plength estimated length of the strokes
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Returns half the number of fg boundary pixels.
+ * </pre>
+ */
+l_ok
+pixFindStrokeLength(PIX *pixs,
+ l_int32 *tab8,
+ l_int32 *plength)
+{
+l_int32 n;
+l_int32 *tab;
+PIX *pix1;
+
+ PROCNAME("pixFindStrokeLength");
+
+ if (!plength)
+ return ERROR_INT("&length not defined", procName, 1);
+ *plength = 0;
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+
+ pix1 = pixExtractBoundary(pixs, 1);
+ tab = (tab8) ? tab8 : makePixelSumTab8();
+ pixCountPixels(pix1, &n, tab);
+ *plength = n / 2;
+ if (!tab8) LEPT_FREE(tab);
+ pixDestroy(&pix1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixFindStrokeWidth()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] thresh fractional count threshold relative to distance 1
+ * \param[in] tab8 [optional] table for counting fg pixels; can be NULL
+ * \param[out] pwidth estimated width of the strokes
+ * \param[out] pnahisto [optional] histo of pixel distances from bg
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This uses two methods to estimate the stroke width:
+ * (a) half the fg boundary length
+ * (b) a value derived from the histogram of the fg distance transform
+ * (2) Distance is measured in 8-connected
+ * (3) %thresh is the minimum fraction N(dist=d)/N(dist=1) of pixels
+ * required to determine if the pixels at distance d are above
+ * the noise. It is typically about 0.15.
+ * </pre>
+ */
+l_ok
+pixFindStrokeWidth(PIX *pixs,
+ l_float32 thresh,
+ l_int32 *tab8,
+ l_float32 *pwidth,
+ NUMA **pnahisto)
+{
+l_int32 i, n, count, length, first, last;
+l_int32 *tab;
+l_float32 width1, width2, ratio, extra;
+l_float32 *fa;
+NUMA *na1, *na2;
+PIX *pix1;
+
+ PROCNAME("pixFindStrokeWidth");
+
+ if (!pwidth)
+ return ERROR_INT("&width not defined", procName, 1);
+ *pwidth = 0;
+ if (!pixs)
+ return ERROR_INT("pixs not defined", procName, 1);
+
+ tab = (tab8) ? tab8 : makePixelSumTab8();
+
+ /* ------- Method 1: via boundary length ------- */
+ /* The computed stroke length is a bit larger than that actual
+ * length, because of the addition of the 'caps' at the
+ * stroke ends. Therefore the computed width is a bit
+ * smaller than the average width. */
+ pixFindStrokeLength(pixs, tab8, &length);
+ pixCountPixels(pixs, &count, tab8);
+ width1 = (l_float32)count / (l_float32)length;
+
+ /* ------- Method 2: via distance transform ------- */
+ /* First get the histogram of distances */
+ pix1 = pixDistanceFunction(pixs, 8, 8, L_BOUNDARY_BG);
+ na1 = pixGetGrayHistogram(pix1, 1);
+ pixDestroy(&pix1);
+ numaGetNonzeroRange(na1, 0.1f, &first, &last);
+ na2 = numaClipToInterval(na1, 0, last);
+ numaWriteStderr(na2);
+
+ /* Find the bucket with the largest distance whose contents
+ * exceed the threshold. */
+ fa = numaGetFArray(na2, L_NOCOPY);
+ n = numaGetCount(na2);
+ for (i = n - 1; i > 0; i--) {
+ ratio = fa[i] / fa[1];
+ if (ratio > thresh) break;
+ }
+ /* Let the last skipped bucket contribute to the stop bucket.
+ * This is the 'extra' term below. The result may be a slight
+ * over-correction, so the computed width may be a bit larger
+ * than the average width. */
+ extra = (i < n - 1) ? fa[i + 1] / fa[1] : 0;
+ width2 = 2.0 * (i - 1.0 + ratio + extra);
+ lept_stderr("width1 = %5.2f, width2 = %5.2f\n", width1, width2);
+
+ /* Average the two results */
+ *pwidth = (width1 + width2) / 2.0;
+
+ if (!tab8) LEPT_FREE(tab);
+ numaDestroy(&na1);
+ if (pnahisto)
+ *pnahisto = na2;
+ else
+ numaDestroy(&na2);
+ return 0;
+}
+
+
+/*!
+ * \brief pixaFindStrokeWidth()
+ *
+ * \param[in] pixa of 1 bpp images
+ * \param[in] thresh fractional count threshold relative to distance 1
+ * \param[in] tab8 [optional] table for counting fg pixels; can be NULL
+ * \param[in] debug 1 for debug output; 0 to skip
+ * \return na array of stroke widths for each pix in %pixa; NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixFindStrokeWidth() for details.
+ * </pre>
+ */
+NUMA *
+pixaFindStrokeWidth(PIXA *pixa,
+ l_float32 thresh,
+ l_int32 *tab8,
+ l_int32 debug)
+{
+l_int32 i, n, same, maxd;
+l_int32 *tab;
+l_float32 width;
+NUMA *na;
+PIX *pix;
+
+ PROCNAME("pixaFindStrokeWidth");
+
+ if (!pixa)
+ return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL);
+ pixaVerifyDepth(pixa, &same, &maxd);
+ if (maxd > 1)
+ return (NUMA *)ERROR_PTR("pix not all 1 bpp", procName, NULL);
+
+ tab = (tab8) ? tab8 : makePixelSumTab8();
+
+ n = pixaGetCount(pixa);
+ na = numaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix = pixaGetPix(pixa, i, L_CLONE);
+ pixFindStrokeWidth(pix, thresh, tab8, &width, NULL);
+ numaAddNumber(na, width);
+ pixDestroy(&pix);
+ }
+
+ if (!tab8) LEPT_FREE(tab);
+ return na;
+}
+
+
+/*-----------------------------------------------------------------*
+ * Change stroke width *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixaModifyStrokeWidth()
+ *
+ * \param[in] pixas of 1 bpp pix
+ * \param[out] targetw desired width for strokes in each pix
+ * \return pixa with modified stroke widths, or NULL on error
+ */
+PIXA *
+pixaModifyStrokeWidth(PIXA *pixas,
+ l_float32 targetw)
+{
+l_int32 i, n, same, maxd;
+l_float32 width;
+NUMA *na;
+PIX *pix1, *pix2;
+PIXA *pixad;
+
+ PROCNAME("pixaModifyStrokeWidth");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (targetw < 1)
+ return (PIXA *)ERROR_PTR("target width < 1", procName, NULL);
+ pixaVerifyDepth(pixas, &same, &maxd);
+ if (maxd > 1)
+ return (PIXA *)ERROR_PTR("pix not all 1 bpp", procName, NULL);
+
+ na = pixaFindStrokeWidth(pixas, 0.1f, NULL, 0);
+ n = pixaGetCount(pixas);
+ pixad = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ numaGetFValue(na, i, &width);
+ pix2 = pixModifyStrokeWidth(pix1, width, targetw);
+ pixaAddPix(pixad, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ numaDestroy(&na);
+ return pixad;
+}
+
+
+/*!
+ * \brief pixModifyStrokeWidth()
+ *
+ * \param[in] pixs of 1 bpp pix
+ * \param[in] width measured average stroke width
+ * \param[in] targetw desired stroke width
+ * \return pix with modified stroke width, or NULL on error
+ */
+PIX *
+pixModifyStrokeWidth(PIX *pixs,
+ l_float32 width,
+ l_float32 targetw)
+{
+char buf[32];
+l_int32 diff, size;
+
+ PROCNAME("pixModifyStrokeWidth");
+
+ if (!pixs || (pixGetDepth(pixs) != 1))
+ return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+ if (targetw < 1)
+ return (PIX *)ERROR_PTR("target width < 1", procName, NULL);
+
+ diff = lept_roundftoi(targetw - width);
+ if (diff == 0) return pixCopy(NULL, pixs);
+
+ size = L_ABS(diff) + 1;
+ if (diff < 0) /* erode */
+ snprintf(buf, sizeof(buf), "e%d.%d", size, size);
+ else /* diff > 0; dilate */
+ snprintf(buf, sizeof(buf), "d%d.%d", size, size);
+ return pixMorphSequence(pixs, buf, 0);
+}
+
+
+/*!
+ * \brief pixaSetStrokeWidth()
+ *
+ * \param[in] pixas of 1 bpp pix
+ * \param[in] width set stroke width to this value, in [1 ... 100].
+ * \param[in] thinfirst 1 to thin all pix to a skeleton first; 0 to skip
+ * \param[in] connectivity 4 or 8, to be used if %thinfirst == 1
+ * \return pixa with all stroke widths being %width, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %thinfirst == 1, thin to a skeleton using the specified
+ * %connectivity. Use %thinfirst == 0 if all pix in pixas
+ * have already been thinned as far as possible.
+ * (2) The image is dilated to the required %width. This dilation
+ * is not connectivity preserving, so this is typically
+ * used in a situation where merging of c.c. in the individual
+ * pix is not a problem; e.g., where each pix is a single c.c.
+ * </pre>
+ */
+PIXA *
+pixaSetStrokeWidth(PIXA *pixas,
+ l_int32 width,
+ l_int32 thinfirst,
+ l_int32 connectivity)
+{
+l_int32 i, n, maxd, same;
+PIX *pix1, *pix2;
+PIXA *pixad;
+
+ PROCNAME("pixaSetStrokeWidth");
+
+ if (!pixas)
+ return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+ if (width < 1 || width > 100)
+ return (PIXA *)ERROR_PTR("width not in [1 ... 100]", procName, NULL);
+ if (connectivity != 4 && connectivity != 8)
+ return (PIXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+ pixaVerifyDepth(pixas, &same, &maxd);
+ if (maxd > 1)
+ return (PIXA *)ERROR_PTR("pix are not all 1 bpp", procName, NULL);
+
+ n = pixaGetCount(pixas);
+ pixad = pixaCreate(n);
+ for (i = 0; i < n; i++) {
+ pix1 = pixaGetPix(pixas, i, L_CLONE);
+ pix2 = pixSetStrokeWidth(pix1, width, thinfirst, connectivity);
+ pixaAddPix(pixad, pix2, L_INSERT);
+ pixDestroy(&pix1);
+ }
+
+ return pixad;
+}
+
+
+/*!
+ * \brief pixSetStrokeWidth()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] width set stroke width to this value, in [1 ... 100].
+ * \param[in] thinfirst 1 to thin all pix to a skeleton first; 0 to skip
+ * \param[in] connectivity 4 or 8, to be used if %thinfirst == 1
+ * \return pixd with stroke width set to %width, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes in pixaSetStrokeWidth().
+ * (2) A white border of sufficient width to avoid boundary
+ * artifacts in the thickening step is added before thinning.
+ * (3) %connectivity == 8 usually gives a slightly smoother result.
+ * </pre>
+ */
+PIX *
+pixSetStrokeWidth(PIX *pixs,
+ l_int32 width,
+ l_int32 thinfirst,
+ l_int32 connectivity)
+{
+char buf[16];
+l_int32 border;
+PIX *pix1, *pix2, *pixd;
+
+ PROCNAME("pixSetStrokeWidth");
+
+ if (!pixs || (pixGetDepth(pixs) != 1))
+ return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+ if (width < 1 || width > 100)
+ return (PIX *)ERROR_PTR("width not in [1 ... 100]", procName, NULL);
+ if (connectivity != 4 && connectivity != 8)
+ return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+ if (!thinfirst && width == 1) /* nothing to do */
+ return pixCopy(NULL, pixs);
+
+ /* Add a white border */
+ border = width / 2;
+ pix1 = pixAddBorder(pixs, border, 0);
+
+ /* Thin to a skeleton */
+ if (thinfirst)
+ pix2 = pixThinConnected(pix1, L_THIN_FG, connectivity, 0);
+ else
+ pix2 = pixClone(pix1);
+ pixDestroy(&pix1);
+
+ /* Dilate */
+ snprintf(buf, sizeof(buf), "D%d.%d", width, width);
+ pixd = pixMorphSequence(pix2, buf, 0);
+ pixCopyText(pixd, pixs);
+ pixDestroy(&pix2);
+ return pixd;
+}
diff --git a/leptonica/src/sudoku.c b/leptonica/src/sudoku.c
new file mode 100644
index 00000000..9f73eb49
--- /dev/null
+++ b/leptonica/src/sudoku.c
@@ -0,0 +1,881 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file sudoku.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief sudokuReadFile()
+ *
+ * \param[in] filename formatted sudoku file
+ * \return array of 81 numbers, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sudokuReadString()
+ *
+ * \param[in] str formatted input data
+ * \return array of 81 numbers, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The string is formatted as 81 single digits, each separated
+ * by 81 spaces.
+ * </pre>
+ */
+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) {
+ LEPT_FREE(array);
+ return (l_int32 *)ERROR_PTR("invalid format", procName, NULL);
+ }
+ }
+
+ return array;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Create/destroy sudoku *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief sudokuCreate()
+ *
+ * \param[in] array 81 numbers, 9 rows of 9 numbers each
+ * \return l_sudoku, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+ sud = (L_SUDOKU *)LEPT_CALLOC(1, sizeof(L_SUDOKU));
+ sud->locs = (l_int32 *)LEPT_CALLOC(81, sizeof(l_int32));
+ sud->init = (l_int32 *)LEPT_CALLOC(81, sizeof(l_int32));
+ sud->state = (l_int32 *)LEPT_CALLOC(81, sizeof(l_int32));
+ 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;
+}
+
+
+/*!
+ * \brief sudokuDestroy()
+ *
+ * \param[in,out] psud will be set to null before returning
+ * \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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Solve the puzzle *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief sudokuSolve()
+ *
+ * \param[in] sud 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) {
+ lept_stderr("Failure after %d guesses\n", sud->nguess);
+ return 0;
+ }
+
+ lept_stderr("Solved after %d guesses\n", sud->nguess);
+ return 1;
+}
+
+
+/*!
+ * \brief sudokuValidState()
+ *
+ * \param[in] state array of size 81
+ * \return 1 if valid, 0 if invalid
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sudokuNewGuess()
+ *
+ * \param[in] sud l_sudoku
+ * \return 0 if OK; 1 if no solution is possible
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sudokuTestState()
+ *
+ * \param[in] state current state: array of 81 values
+ * \param[in] 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief sudokuTestUniqueness()
+ *
+ * \param[in] array of 81 numbers, 9 lines of 9 numbers each
+ * \param[out] punique 1 if unique, 0 if not
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief sudokuCompareState()
+ *
+ * \param[in] sud1, sud2 two l_Sudoku states (solutions)
+ * \param[in] quads rotation of sud2 input with respect to sud1,
+ * in units of 90 degrees cw
+ * \param[out] psame 1 if all 4 results are identical; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief sudokuRotateArray()
+ *
+ * \param[in] array 81 numbers; 9 lines of 9 numbers each
+ * \param[in] 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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief sudokuGenerate()
+ *
+ * \param[in] array 81 numbers, 9 rows of 9 numbers each
+ * \param[in] seed random number
+ * \param[in] minelems min non-zero elements allowed; <= 80
+ * \param[in] maxtries max tries to remove a number and get a valid sudoku
+ * \return l_sudoku, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) {
+ genRandomIntOnInterval(0, 8, 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) {
+ lept_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. */
+ genRandomIntOnInterval(0, 8, 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;
+ lept_stderr("Have %d zeros\n", nzeros);
+ nzeros++;
+ }
+ }
+ lept_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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief sudokuOutput()
+ *
+ * \param[in] sud l_sudoku at any stage
+ * \param[in] arraytype L_SUDOKU_INIT, L_SUDOKU_STATE
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Prints either the initial array or the current state
+ * of the solution.
+ * </pre>
+ */
+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++)
+ lept_stderr("%d ", array[9 * i + j]);
+ lept_stderr("\n");
+ }
+ return 0;
+}
diff --git a/leptonica/src/sudoku.h b/leptonica/src/sudoku.h
new file mode 100644
index 00000000..5abb7cbc
--- /dev/null
+++ b/leptonica/src/sudoku.h
@@ -0,0 +1,77 @@
+/*====================================================================*
+ - 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
+
+/*!
+ * \file sudoku.h
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+
+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 */
+/*! Sudoku Output */
+enum {
+ L_SUDOKU_INIT = 0,
+ L_SUDOKU_STATE = 1
+};
+
+#endif /* SUDOKU_H_INCLUDED */
+
+
diff --git a/leptonica/src/textops.c b/leptonica/src/textops.c
new file mode 100644
index 00000000..3e23c22b
--- /dev/null
+++ b/leptonica/src/textops.c
@@ -0,0 +1,1129 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file textops.c
+ * <pre>
+ *
+ * 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
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixAddSingleTextblock()
+ *
+ * \param[in] pixs input pix; colormap ok
+ * \param[in] bmf bitmap font data
+ * \param[in] textstr [optional] text string to be added
+ * \param[in] val color to set the text
+ * \param[in] location L_ADD_ABOVE, L_ADD_AT_TOP,
+ * L_ADD_AT_BOT, L_ADD_BELOW
+ * \param[out] poverflow [optional] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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_WARNING("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);
+ pixCopyResolution(pixd, pixs);
+ pixCopyText(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;
+}
+
+
+/*!
+ * \brief pixAddTextlines()
+ *
+ * \param[in] pixs input pix; colormap ok
+ * \param[in] bmf bitmap font data
+ * \param[in] textstr [optional] text string to be added
+ * \param[in] val color to set the text
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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_WARNING("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);
+ pixCopyResolution(pixd, pixs);
+ pixCopyText(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);
+ pixCopyResolution(pixd, pixs);
+ pixCopyText(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;
+}
+
+
+/*!
+ * \brief pixSetTextblock()
+ *
+ * \param[in] pixs input image
+ * \param[in] bmf bitmap font data
+ * \param[in] textstr block text string to be set
+ * \param[in] val color to set the text
+ * \param[in] x0 left edge for each line of text
+ * \param[in] y0 baseline location for the first text line
+ * \param[in] wtext max width of each line of generated text
+ * \param[in] firstindent indentation of first line, in x-widths
+ * \param[out] poverflow [optional] 0 if text is contained in input pix;
+ * 1 if it is clipped
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixSetTextline()
+ *
+ * \param[in] pixs input image
+ * \param[in] bmf bitmap font data
+ * \param[in] textstr text string to be set on the line
+ * \param[in] val color to set the text
+ * \param[in] x0 left edge for first char
+ * \param[in] y0 baseline location for all text on line
+ * \param[out] pwidth [optional] width of generated text
+ * \param[out] poverflow [optional] 0 if text is contained in input pix;
+ * 1 if it is clipped
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pixaAddTextNumber()
+ *
+ * \param[in] pixas input pixa; colormap ok
+ * \param[in] bmf bitmap font data
+ * \param[in] na [optional] number array; use 1 ... n if null
+ * \param[in] val color to set the text
+ * \param[in] location L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT
+ * \return pixad new pixa with rendered numbers, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaAddTextlines()
+ *
+ * \param[in] pixas input pixa; colormap ok
+ * \param[in] bmf bitmap font data
+ * \param[in] sa [optional] sarray; use text embedded in
+ * each pix if null
+ * \param[in] val color to set the text
+ * \param[in] location L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT
+ * \return pixad new pixa with rendered text, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixaAddPixWithText()
+ *
+ * \param[in] pixa
+ * \param[in] pixs any depth, colormap ok
+ * \param[in] reduction integer subsampling factor
+ * \param[in] bmf [optional] bitmap font data
+ * \param[in] textstr [optional] text string to be added
+ * \param[in] val color to set the text
+ * \param[in] location L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT
+ * \return 0 if OK, 1 on error.
+ *
+ * <pre>
+ * 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 labeled images
+ * is used to generate a pdf. Suggest using 1.0 for scalefactor.
+ * </pre>
+ */
+l_ok
+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 (location != L_ADD_ABOVE && location != L_ADD_BELOW &&
+ location != L_ADD_LEFT && location != L_ADD_RIGHT)
+ return ERROR_INT("invalid location", procName, 1);
+
+ if (!textstr) {
+ textstr = pixGetText(pixs);
+ if (!textstr) {
+ L_WARNING("no textstring defined; inserting copy", procName);
+ pixaAddPix(pixa, pixs, L_COPY);
+ return 0;
+ }
+ }
+
+ /* 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 (!bmf) bmfDestroy(&bmf8);
+ if (!pix3)
+ return ERROR_INT("pix3 not made", procName, 1);
+
+ pixaAddPix(pixa, pix3, L_INSERT);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Text size estimation and partitioning *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief bmfGetLineStrings()
+ *
+ * \param[in] bmf
+ * \param[in] textstr
+ * \param[in] maxw max width of a text line in pixels
+ * \param[in] firstindent indentation of first line, in x-widths
+ * \param[out] ph height required to hold text bitmap
+ * \return sarray of text strings for each line, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Divides the input text string into an array of text strings,
+ * each of which will fit within maxw bits of width.
+ * </pre>
+ */
+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) {
+ sarrayDestroy(&sawords);
+ return (SARRAY *)ERROR_PTR("na not made", procName, NULL);
+ }
+ nwords = numaGetCount(na);
+ if (nwords == 0) {
+ sarrayDestroy(&sawords);
+ numaDestroy(&na);
+ return (SARRAY *)ERROR_PTR("no words in textstr", procName, NULL);
+ }
+ bmfGetWidth(bmf, 'x', &xwidth);
+
+ sa = sarrayCreate(0);
+ 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;
+}
+
+
+/*!
+ * \brief bmfGetWordWidths()
+ *
+ * \param[in] bmf
+ * \param[in] textstr
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief bmfGetStringWidth()
+ *
+ * \param[in] bmf
+ * \param[in] textstr
+ * \param[out] pw width of text string, in pixels for the
+ * font represented by the bmf
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief splitStringToParagraphs()
+ *
+ * \param[in] textstr text string
+ * \param[in] splitflag 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);
+ sarrayDestroy(&salines);
+ return saout;
+}
+
+
+/*!
+ * \brief stringAllWhitespace()
+ *
+ * \param[in] textstr text string
+ * \param[out] pval 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;
+}
+
+
+/*!
+ * \brief stringLeadingWhitespace()
+ *
+ * \param[in] textstr text string
+ * \param[out] pval 1 if leading char is [space] or [tab]; 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/leptonica/src/tiffio.c b/leptonica/src/tiffio.c
new file mode 100644
index 00000000..c3028160
--- /dev/null
+++ b/leptonica/src/tiffio.c
@@ -0,0 +1,2895 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file tiffio.c
+ * <pre>
+ *
+ * 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()
+ * l_int32 pixWriteStreamTiffWA()
+ * static l_int32 pixWriteToTiffStream()
+ * static l_int32 writeCustomTiffTags()
+ *
+ * Reading and writing multipage tiff
+ * PIX *pixReadFromMultipageTiff()
+ * PIXA *pixaReadMultipageTiff() [ special top level ]
+ * l_int32 pixaWriteMultipageTiff() [ special top level ]
+ * 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]
+ * PIX *pixReadMemTiff();
+ * PIX *pixReadMemFromMultipageTiff();
+ * PIXA *pixaReadMemMultipageTiff() [ special top level ]
+ * l_int32 pixaWriteMemMultipageTiff() [ special top level ]
+ * l_int32 pixWriteMemTiff();
+ * l_int32 pixWriteMemTiffCustom();
+ *
+ * Note 1: To include all necessary functions, use libtiff version 3.7.4
+ * (from 2005) or later.
+ * Note 2: What compression methods in tiff are supported?
+ * * We support most methods that are fully implemented in the
+ * tiff library, such as G3, G4, RLE and LZW.
+ * * The exception is the old-style jpeg tiff format (OJPEG), which
+ * is not supported.
+ * * We support only one format, ZIP, that uses an external library.
+ * * At present we do not support WEBP in tiff, which uses
+ * libwebp and was added in tifflib 4.1.0 in 2019.
+ * Note 3: On Windows with 2 bpp or 4 bpp images, the bytes in the
+ * tiff-compressed file depend on the pad bits, but not on the
+ * decoded raster image when read. Because it is sometimes
+ * convenient to use a golden file with a byte-by-byte check
+ * to verify invariance, we set the pad bits to 0 before writing,
+ * in pixWriteToTiffStream().
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h> /* for isnan */
+#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 DefaultResolution = 300; /* ppi */
+static const l_int32 ManyPagesInTiffFile = 3000; /* warn if big */
+
+ /* Verified that tiflib makes valid g4 files of this size */
+static const l_int32 MaxTiffWidth = 1 << 20; /* 1M pixels */
+static const l_int32 MaxTiffHeight = 1 << 20; /* 1M pixels */
+
+ /* Check g4 data size */
+static const size_t MaxNumTiffBytes = (1 << 28) - 1; /* 256 MB */
+
+ /* 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. On unix it is common to look *
+ * for a macro _LARGEFILE64_SOURCE being defined, which makes available *
+ * the off64_t type, and to 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(_LARGEFILE64_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(_LARGEFILE64_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 *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief pixReadTiff()
+ *
+ * \param[in] filename
+ * \param[in] n page number 0 based
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief pixReadStreamTiff()
+ *
+ * \param[in] fp file stream
+ * \param[in] n page number: 0 based
+ * \return pix, or NULL on error or if there are no more images in the file
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixReadStreamTiff(FILE *fp,
+ l_int32 n)
+{
+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);
+
+ if (TIFFSetDirectory(tif, n) == 0) {
+ TIFFCleanup(tif);
+ return NULL;
+ }
+ if ((pix = pixReadFromTiffStream(tif)) == NULL) {
+ TIFFCleanup(tif);
+ return NULL;
+ }
+ TIFFCleanup(tif);
+ return pix;
+}
+
+
+/*!
+ * \brief pixReadFromTiffStream()
+ *
+ * \param[in] tif TIFF handle
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) We can read the following images (up to 32 bits/pixel):
+ * 1 spp (grayscale): 1, 2, 4, 8, 16 bps
+ * 1 spp (colormapped): 1, 2, 4, 8 bps
+ * 2 spp (gray+alpha): 8 bps
+ * 3 spp (rgb) and 4 spp (rgba): 8 or 16 bps
+ * (2) We do not handle 16 bps for spp == 2.
+ * (3) 2 bpp gray+alpha are rasterized as 32 bit/pixel rgba, with
+ * the gray value replicated in r, g and b.
+ * (4) For colormapped images, we support 8 bits/color in the palette.
+ * Tiff colormaps have 16 bits/color, and we reduce them to 8.
+ * (5) Quoting the libtiff documentation 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."
+ * </pre>
+ */
+static PIX *
+pixReadFromTiffStream(TIFF *tif)
+{
+char *text;
+l_uint8 *linebuf, *data, *rowptr;
+l_uint16 spp, bps, photometry, tiffcomp, orientation, sample_fmt;
+l_uint16 *redmap, *greenmap, *bluemap;
+l_int32 d, wpl, bpl, comptype, i, j, k, ncolors, rval, gval, bval, aval;
+l_int32 xres, yres, tiffbpl, packedbpl, halfsize;
+l_uint32 w, h, tiffword, read_oriented;
+l_uint32 *line, *ppixel, *tiffdata, *pixdata;
+PIX *pix, *pix1;
+PIXCMAP *cmap;
+
+ PROCNAME("pixReadFromTiffStream");
+
+ if (!tif)
+ return (PIX *)ERROR_PTR("tif not defined", procName, NULL);
+
+ read_oriented = 0;
+
+ /* Only accept uint image data:
+ * SAMPLEFORMAT_UINT = 1;
+ * SAMPLEFORMAT_INT = 2;
+ * SAMPLEFORMAT_IEEEFP = 3;
+ * SAMPLEFORMAT_VOID = 4; */
+ TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_fmt);
+ if (sample_fmt != SAMPLEFORMAT_UINT) {
+ L_ERROR("sample format = %d is not uint\n", procName, sample_fmt);
+ return NULL;
+ }
+
+ /* Can't read tiff in tiled format. For what is involved, see, e.g:
+ * https://www.cs.rochester.edu/~nelson/courses/vision/\
+ * resources/tiff/libtiff.html#Tiles
+ * A tiled tiff can be converted to a normal (strip) tif:
+ * tiffcp -s <input-tiled-tif> <output-strip-tif> */
+ if (TIFFIsTiled(tif)) {
+ L_ERROR("tiled format is not supported\n", procName);
+ return NULL;
+ }
+
+ /* Old style jpeg is not supported. We tried supporting 8 bpp.
+ * TIFFReadScanline() fails on this format, so we used RGBA
+ * reading, which generates a 4 spp image, and pulled out the
+ * red component. However, there were problems with double-frees
+ * in cleanup. For RGB, tiffbpl is exactly half the size that
+ * you would expect for the raster data in a scanline, which
+ * is 3 * w. */
+ TIFFGetFieldDefaulted(tif, TIFFTAG_COMPRESSION, &tiffcomp);
+ if (tiffcomp == COMPRESSION_OJPEG) {
+ L_ERROR("old style jpeg format is not supported\n", procName);
+ return NULL;
+ }
+
+ /* webp in tiff is in 4.1.0 and not yet supported in Adobe registry */
+#if defined(COMPRESSION_WEBP)
+ if (tiffcomp == COMPRESSION_WEBP) {
+ L_ERROR("webp in tiff not generally supported yet\n", procName);
+ return NULL;
+ }
+#endif /* COMPRESSION_WEBP */
+
+ /* Use default fields for bps and spp */
+ TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bps);
+ TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &spp);
+ if (bps != 1 && bps != 2 && bps != 4 && bps != 8 && bps != 16) {
+ L_ERROR("invalid bps = %d\n", procName, bps);
+ return NULL;
+ }
+ if (spp == 2 && bps != 8) {
+ L_WARNING("for 2 spp, only handle 8 bps\n", procName);
+ return NULL;
+ }
+ if (spp == 1)
+ d = bps;
+ else if (spp == 2) /* gray plus alpha */
+ d = 32; /* will convert to RGBA */
+ else if (spp == 3 || spp == 4)
+ d = 32;
+ else
+ return (PIX *)ERROR_PTR("spp not in set {1,2,3,4}", procName, NULL);
+
+ TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w);
+ TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h);
+ if (w > MaxTiffWidth) {
+ L_ERROR("width = %d pixels; too large\n", procName, w);
+ return NULL;
+ }
+ if (h > MaxTiffHeight) {
+ L_ERROR("height = %d pixels; too large\n", procName, h);
+ return NULL;
+ }
+
+ /* The relation between the size of a byte buffer required to hold
+ a raster of image pixels (packedbpl) and the size of the tiff
+ buffer (tiffbuf) is either 1:1 or approximately 2:1, depending
+ on how the data is stored and subsampled. Allow some slop
+ when validating the relation between buffer size and the image
+ parameters w, spp and bps. */
+ tiffbpl = TIFFScanlineSize(tif);
+ packedbpl = (bps * spp * w + 7) / 8;
+ halfsize = L_ABS(2 * tiffbpl - packedbpl) <= 8;
+#if 0
+ if (halfsize)
+ L_INFO("packedbpl = %d is approx. twice tiffbpl = %d\n", procName,
+ packedbpl, tiffbpl);
+#endif
+ if (tiffbpl != packedbpl && !halfsize) {
+ L_ERROR("invalid tiffbpl: tiffbpl = %d, packedbpl = %d, "
+ "bps = %d, spp = %d, w = %d\n",
+ procName, tiffbpl, packedbpl, bps, spp, w);
+ return NULL;
+ }
+
+ 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;
+
+ if (spp == 1) {
+ linebuf = (l_uint8 *)LEPT_CALLOC(tiffbpl + 1, sizeof(l_uint8));
+ 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(data, linebuf, tiffbpl);
+ data += bpl;
+ }
+ if (bps <= 8)
+ pixEndianByteSwap(pix);
+ else /* bps == 16 */
+ pixEndianTwoByteSwap(pix);
+ LEPT_FREE(linebuf);
+ } else if (spp == 2 && bps == 8) { /* gray plus alpha */
+ L_INFO("gray+alpha is not supported; converting to RGBA\n", procName);
+ pixSetSpp(pix, 4);
+ linebuf = (l_uint8 *)LEPT_CALLOC(2 * tiffbpl + 1, sizeof(l_uint8));
+ pixdata = pixGetData(pix);
+ 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);
+ }
+ rowptr = linebuf;
+ ppixel = pixdata + i * wpl;
+ 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++;
+ }
+ }
+ LEPT_FREE(linebuf);
+ } else { /* rgb and rgba */
+ if ((tiffdata = (l_uint32 *)LEPT_CALLOC((size_t)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, tiffdata,
+ ORIENTATION_TOPLEFT, 0)) {
+ LEPT_FREE(tiffdata);
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("failed to read tiffdata", procName, NULL);
+ } else {
+ read_oriented = 1;
+ }
+
+ if (spp == 4) pixSetSpp(pix, 4);
+ 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);
+ if (spp == 3) {
+ composeRGBPixel(rval, gval, bval, ppixel);
+ } else { /* spp == 4 */
+ aval = TIFFGetA(tiffword);
+ composeRGBAPixel(rval, gval, bval, aval, ppixel);
+ }
+ ppixel++;
+ }
+ }
+ LEPT_FREE(tiffdata);
+ }
+
+ if (getTiffStreamResolution(tif, &xres, &yres) == 0) {
+ pixSetXRes(pix, xres);
+ pixSetYRes(pix, yres);
+ }
+
+ /* Find and save the compression type */
+ 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("colormap size > 256", procName, NULL);
+ }
+ if ((cmap = pixcmapCreate(bps)) == NULL) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("colormap not made", procName, NULL);
+ }
+ ncolors = 1 << bps;
+ for (i = 0; i < ncolors; i++)
+ pixcmapAddColor(cmap, redmap[i] >> 8, greenmap[i] >> 8,
+ bluemap[i] >> 8);
+ if (pixSetColormap(pix, cmap)) {
+ pixDestroy(&pix);
+ return (PIX *)ERROR_PTR("invalid colormap", procName, NULL);
+ }
+
+ /* Remove the colormap for 1 bpp. */
+ if (bps == 1) {
+ pix1 = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+ pixDestroy(&pix);
+ pix = pix1;
+ }
+ } 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);
+ }
+ }
+ }
+
+ text = NULL;
+ TIFFGetField(tif, TIFFTAG_IMAGEDESCRIPTION, &text);
+ if (text) pixSetText(pix, text);
+ return pix;
+}
+
+
+/*--------------------------------------------------------------*
+ * Writing to file *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief pixWriteTiff()
+ *
+ * \param[in] filename to write to
+ * \param[in] pix any depth, colormap will be removed
+ * \param[in] comptype IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS,
+ * IFF_TIFF_G3, IFF_TIFF_G4,
+ * IFF_TIFF_LZW, IFF_TIFF_ZIP, IFF_TIFF_JPEG
+ * \param[in] modestr "a" or "w"
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) For multipage tiff, write the first pix with mode "w" and
+ * all subsequent pix with mode "a".
+ * (2) For multipage tiff, there is considerable overhead in the
+ * machinery to append an image and add the directory entry,
+ * and the time required for each image increases linearly
+ * with the number of images in the file.
+ * </pre>
+ */
+l_ok
+pixWriteTiff(const char *filename,
+ PIX *pix,
+ l_int32 comptype,
+ const char *modestr)
+{
+ return pixWriteTiffCustom(filename, pix, comptype, modestr,
+ NULL, NULL, NULL, NULL);
+}
+
+
+/*!
+ * \brief pixWriteTiffCustom()
+ *
+ * \param[in] filename to write to
+ * \param[in] pix
+ * \param[in] comptype IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS,
+ * IFF_TIFF_G3, IFF_TIFF_G4,
+ * IFF_TIFF_LZW, IFF_TIFF_ZIP, IFF_TIFF_JPEG
+ * \param[in] modestr "a" or "w"
+ * \param[in] natags [optional] NUMA of custom tiff tags
+ * \param[in] savals [optional] SARRAY of values
+ * \param[in] satypes [optional] SARRAY of types
+ * \param[in] 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 multipage 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_ok
+pixWriteTiffCustom(const char *filename,
+ PIX *pix,
+ l_int32 comptype,
+ const char *modestr,
+ 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, modestr)) == 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 *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief pixWriteStreamTiff()
+ *
+ * \param[in] fp file stream
+ * \param[in] pix
+ * \param[in] comptype IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS,
+ * IFF_TIFF_G3, IFF_TIFF_G4,
+ * IFF_TIFF_LZW, IFF_TIFF_ZIP, IFF_TIFF_JPEG
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This writes a single image to a file stream opened for writing.
+ * (2) If the pix has a colormap, it is preserved in the output file.
+ * (3) For images with bpp > 1, this resets the comptype, if
+ * necessary, to write uncompressed data.
+ * (4) G3 and G4 are only defined for 1 bpp.
+ * (5) We only allow PACKBITS for bpp = 1, because for bpp > 1
+ * it typically expands images that are not synthetically generated.
+ * (6) 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.
+ * </pre>
+ */
+l_ok
+pixWriteStreamTiff(FILE *fp,
+ PIX *pix,
+ l_int32 comptype)
+{
+ return pixWriteStreamTiffWA(fp, pix, comptype, "w");
+}
+
+
+/*!
+ * \brief pixWriteStreamTiffWA()
+ *
+ * \param[in] fp file stream opened for append or write
+ * \param[in] pix
+ * \param[in] comptype IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS,
+ * IFF_TIFF_G3, IFF_TIFF_G4,
+ * IFF_TIFF_LZW, IFF_TIFF_ZIP, IFF_TIFF_JPEG
+ * \param[in] modestr "w" or "a"
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixWriteStreamTiff()
+ * </pre>
+ */
+l_ok
+pixWriteStreamTiffWA(FILE *fp,
+ PIX *pix,
+ l_int32 comptype,
+ const char *modestr)
+{
+TIFF *tif;
+
+ PROCNAME("pixWriteStreamTiffWA");
+
+ if (!fp)
+ return ERROR_INT("stream not defined", procName, 1 );
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1 );
+ if (strcmp(modestr, "w") && strcmp(modestr, "a"))
+ return ERROR_INT("modestr not 'w' or 'a'", procName, 1 );
+
+ if (pixGetDepth(pix) != 1 && comptype != IFF_TIFF &&
+ comptype != IFF_TIFF_LZW && comptype != IFF_TIFF_ZIP &&
+ comptype != IFF_TIFF_JPEG) {
+ L_WARNING("invalid compression type for bpp > 1\n", procName);
+ comptype = IFF_TIFF_ZIP;
+ }
+
+ if ((tif = fopenTiff(fp, modestr)) == 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;
+}
+
+
+/*!
+ * \brief pixWriteToTiffStream()
+ *
+ * \param[in] tif data structure, opened to a file
+ * \param[in] pix
+ * \param[in] 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: lossless for any image
+ * IFF_TIFF_JPEG: lossy 8 bpp gray or rgb
+ * \param[in] natags [optional] NUMA of custom tiff tags
+ * \param[in] savals [optional] SARRAY of values
+ * \param[in] satypes [optional] SARRAY of types
+ * \param[in] nasizes [optional] NUMA of sizes
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, spp, 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 );
+
+ pixSetPadBits(pix, 0);
+ pixGetDimensions(pix, &w, &h, &d);
+ spp = pixGetSpp(pix);
+ xres = pixGetXRes(pix);
+ yres = pixGetYRes(pix);
+ if (xres == 0) xres = DefaultResolution;
+ if (yres == 0) yres = DefaultResolution;
+
+ /* ------------------ 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 && !pixGetColormap(pix)) {
+ /* If d == 1, preserve the colormap. Note that when
+ * d == 1 pix with colormaps are read, the colormaps
+ * are removed. The only pix in leptonica that have
+ * colormaps are made programmatically. */
+ TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
+ } else if ((d == 32 && spp == 3) || d == 24) {
+ TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
+ TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, (l_uint16)3);
+ TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE,
+ (l_uint16)8, (l_uint16)8, (l_uint16)8);
+ } else if (d == 32 && spp == 4) {
+ l_uint16 val[1];
+ val[0] = EXTRASAMPLE_ASSOCALPHA;
+ TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, (l_uint16)1, &val);
+ TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
+ TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, (l_uint16)4);
+ TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE,
+ (l_uint16)8, (l_uint16)8, (l_uint16)8, (l_uint16)8);
+ } else if (d == 16) { /* we only support spp = 1, bps = 16 */
+ TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
+ } else if ((cmap = pixGetColormap(pix)) == NULL) {
+ TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
+ } else { /* Save colormap in the tiff; not more than 256 colors */
+ if (d > 8) {
+ L_ERROR("d = %d > 8 with colormap!; reducing to 8\n", procName, d);
+ d = 8;
+ }
+ 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 <= 16) {
+ 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 if (comptype == IFF_TIFF_JPEG) {
+ TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_JPEG);
+ } 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)
+ lept_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(linebuf, 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 { /* 32 bpp rgb or rgba */
+ 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);
+ if (spp == 4)
+ linebuf[k++] = GET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL);
+ ppixel++;
+ }
+ if (TIFFWriteScanline(tif, linebuf, i, 0) < 0)
+ break;
+ }
+ }
+
+/* TIFFWriteDirectory(tif); */
+ LEPT_FREE(linebuf);
+
+ return 0;
+}
+
+
+/*!
+ * \brief writeCustomTiffTags()
+ *
+ * \param[in] tif
+ * \param[in] natags [optional] NUMA of custom tiff tags
+ * \param[in] savals [optional] SARRAY of values
+ * \param[in] satypes [optional] SARRAY of types
+ * \param[in] nasizes [optional] NUMA of sizes
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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*") || !strcmp(type, "const char*")) {
+ TIFFSetField(tif, tagval, sval);
+ } else if (!strcmp(type, "l_uint16")) {
+ if (sscanf(sval, "%u", &uval) == 1) {
+ TIFFSetField(tif, tagval, (l_uint16)uval);
+ } else {
+ lept_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 {
+ lept_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 {
+ lept_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 {
+ lept_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 {
+ lept_stderr("val %s not of type %s\n", sval, type);
+ return ERROR_INT("custom tag(s) not written", procName, 1);
+ }
+ } else {
+ lept_stderr("unknown type %s\n",type);
+ return ERROR_INT("unknown type; tag(s) not written", procName, 1);
+ }
+ }
+ return 0;
+}
+
+
+/*--------------------------------------------------------------*
+ * Reading and writing multipage tiff *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief pixReadFromMultipageTiff()
+ *
+ * \param[in] fname filename
+ * \param[in,out] poffset set offset to 0 for first image
+ * \return pix, or NULL on error or if previous call returned the last image
+ *
+ * <pre>
+ * Notes:
+ * (1) This allows overhead for traversal of a multipage tiff file
+ * to be linear in the number of images. This will also work
+ * with a singlepage tiff file.
+ * (2) No TIFF internal data structures are exposed to the caller
+ * (thanks to Jeff Breidenbach).
+ * (3) offset is the byte offset of a particular image in a multipage
+ * tiff file. To get the first image in the file, input the
+ * special offset value of 0.
+ * (4) The offset is updated to point to the next image, for a
+ * subsequent call.
+ * (5) On the last image, the offset returned is 0. Exit the loop
+ * when the returned offset is 0.
+ * (6) For reading a multipage tiff from a memory buffer, see
+ * pixReadMemFromMultipageTiff()
+ * (7) Example usage for reading all the images in the tif file:
+ * size_t offset = 0;
+ * do {
+ * Pix *pix = pixReadFromMultipageTiff(filename, &offset);
+ * // do something with pix
+ * } while (offset != 0);
+ * </pre>
+ */
+PIX *
+pixReadFromMultipageTiff(const char *fname,
+ size_t *poffset)
+{
+l_int32 retval;
+size_t offset;
+PIX *pix;
+TIFF *tif;
+
+ PROCNAME("pixReadFromMultipageTiff");
+
+ if (!fname)
+ return (PIX *)ERROR_PTR("fname not defined", procName, NULL);
+ if (!poffset)
+ return (PIX *)ERROR_PTR("&offset not defined", procName, NULL);
+
+ if ((tif = openTiff(fname, "r")) == NULL) {
+ L_ERROR("tif open failed for %s\n", procName, fname);
+ return NULL;
+ }
+
+ /* Set ptrs in the TIFF to the beginning of the image */
+ offset = *poffset;
+ retval = (offset == 0) ? TIFFSetDirectory(tif, 0)
+ : TIFFSetSubDirectory(tif, offset);
+ if (retval == 0) {
+ TIFFCleanup(tif);
+ return NULL;
+ }
+
+ if ((pix = pixReadFromTiffStream(tif)) == NULL) {
+ TIFFCleanup(tif);
+ return NULL;
+ }
+
+ /* Advance to the next image and return the new offset */
+ TIFFReadDirectory(tif);
+ *poffset = TIFFCurrentDirOffset(tif);
+ TIFFClose(tif);
+ return pix;
+}
+
+
+/*!
+ * \brief pixaReadMultipageTiff()
+ *
+ * \param[in] 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;
+TIFF *tif;
+
+ 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);
+ }
+
+ if ((tif = fopenTiff(fp, "r")) == NULL)
+ return (PIXA *)ERROR_PTR("tif not opened", procName, NULL);
+
+ pixa = pixaCreate(npages);
+ pix = NULL;
+ for (i = 0; i < npages; i++) {
+ if ((pix = pixReadFromTiffStream(tif)) != NULL) {
+ pixaAddPix(pixa, pix, L_INSERT);
+ } else {
+ L_WARNING("pix not read for page %d\n", procName, i);
+ }
+
+ /* Advance to the next directory (i.e., the next image) */
+ if (TIFFReadDirectory(tif) == 0)
+ break;
+ }
+
+ fclose(fp);
+ TIFFCleanup(tif);
+ return pixa;
+}
+
+
+/*!
+ * \brief pixaWriteMultipageTiff()
+ *
+ * \param[in] fname input tiff file
+ * \param[in] pixa any depth; colormap will be removed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The tiff directory overhead is O(n^2). I have not been
+ * able to reduce it to O(n). The overhead for n = 2000 is
+ * about 1 second.
+ * </pre>
+ */
+l_ok
+pixaWriteMultipageTiff(const char *fname,
+ PIXA *pixa)
+{
+const char *modestr;
+l_int32 i, n;
+PIX *pix1;
+
+ PROCNAME("pixaWriteMultipageTiff");
+
+ if (!fname)
+ return ERROR_INT("fname not defined", procName, 1);
+ if (!pixa)
+ return ERROR_INT("pixa not defined", procName, 1);
+
+ n = pixaGetCount(pixa);
+ for (i = 0; i < n; i++) {
+ modestr = (i == 0) ? "w" : "a";
+ pix1 = pixaGetPix(pixa, i, L_CLONE);
+ if (pixGetDepth(pix1) == 1)
+ pixWriteTiff(fname, pix1, IFF_TIFF_G4, modestr);
+ else
+ pixWriteTiff(fname, pix1, IFF_TIFF_ZIP, modestr);
+ pixDestroy(&pix1);
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief writeMultipageTiff()
+ *
+ * \param[in] dirin input directory
+ * \param[in] substr [optional] substring filter on filenames; can be NULL
+ * \param[in] fileout output multipage tiff file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * (4) The tiff directory overhead is quadratic in the number of
+ * images. To avoid this for very large numbers of images to be
+ * written, apply the method used in pixaWriteMultipageTiff().
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief writeMultipageTiffSA()
+ *
+ * \param[in] sa string array of full path names
+ * \param[in] fileout output ps file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See writeMultipageTiff()
+ * </pre>
+ */
+l_ok
+writeMultipageTiffSA(SARRAY *sa,
+ const char *fileout)
+{
+char *fname;
+const char *op;
+l_int32 i, nfiles, firstfile, format;
+PIX *pix;
+
+ 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
+ pixWriteTiff(fileout, pix, IFF_TIFF_ZIP, op);
+ firstfile = FALSE;
+ pixDestroy(&pix);
+ }
+
+ return 0;
+}
+
+
+/*--------------------------------------------------------------*
+ * Print info to stream *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief fprintTiffInfo()
+ *
+ * \param[in] fpout stream for output of tag data
+ * \param[in] tiffile input
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief tiffGetCount()
+ *
+ * \param[in] fp file stream opened for read
+ * \param[out] pn number of images
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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++) {
+ if (TIFFReadDirectory(tif) == 0)
+ break;
+ if (i == ManyPagesInTiffFile + 1) {
+ L_WARNING("big file: more than %d pages\n", procName,
+ ManyPagesInTiffFile);
+ }
+ }
+ *pn = i;
+ TIFFCleanup(tif);
+ return 0;
+}
+
+
+/*--------------------------------------------------------------*
+ * Get resolution from tif *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief getTiffResolution()
+ *
+ * \param[in] fp file stream opened for read
+ * \param[out] pxres, pyres resolution in ppi
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If neither resolution field is set, this is not an error;
+ * the returned resolution values are 0 (designating 'unknown').
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief getTiffStreamResolution()
+ *
+ * \param[in] tif TIFF handle opened for read
+ * \param[out] pxres, pyres resolution in ppi
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If neither resolution field is set, this is not an error;
+ * the returned resolution values are 0 (designating 'unknown').
+ * </pre>
+ */
+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 (isnan(fxres) || isnan(fyres)) return 1;
+ if (!foundxres && foundyres)
+ fxres = fyres;
+ else if (foundxres && !foundyres)
+ fyres = fxres;
+
+ /* Avoid overflow into int32; set max fxres and fyres to 5 x 10^8 */
+ if (fxres < 0 || fxres > (1L << 29) || fyres < 0 || fyres > (1L << 29))
+ return ERROR_INT("fxres and/or fyres values are invalid", procName, 1);
+
+ 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 *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief readHeaderTiff()
+ *
+ * \param[in] filename
+ * \param[in] n page image number: 0-based
+ * \param[out] pw [optional] width
+ * \param[out] ph [optional] height
+ * \param[out] pbps [optional] bits per sample -- 1, 2, 4 or 8
+ * \param[out] pspp [optional] samples per pixel -- 1 or 3
+ * \param[out] pres [optional] resolution in x dir; NULL to ignore
+ * \param[out] pcmap [optional] colormap exists; input NULL to ignore
+ * \param[out] pformat [optional] tiff format; input NULL to ignore
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+readHeaderTiff(const char *filename,
+ l_int32 n,
+ l_int32 *pw,
+ l_int32 *ph,
+ l_int32 *pbps,
+ l_int32 *pspp,
+ l_int32 *pres,
+ l_int32 *pcmap,
+ l_int32 *pformat)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("readHeaderTiff");
+
+ if (pw) *pw = 0;
+ if (ph) *ph = 0;
+ if (pbps) *pbps = 0;
+ if (pspp) *pspp = 0;
+ if (pres) *pres = 0;
+ if (pcmap) *pcmap = 0;
+ if (pformat) *pformat = 0;
+ if (!filename)
+ return ERROR_INT("filename not defined", procName, 1);
+ if (!pw && !ph && !pbps && !pspp && !pres && !pcmap && !pformat)
+ return ERROR_INT("no results requested", procName, 1);
+
+ if ((fp = fopenReadStream(filename)) == NULL)
+ return ERROR_INT("image file not found", procName, 1);
+ ret = freadHeaderTiff(fp, n, pw, ph, pbps, pspp, pres, pcmap, pformat);
+ fclose(fp);
+ return ret;
+}
+
+
+/*!
+ * \brief freadHeaderTiff()
+ *
+ * \param[in] fp file stream
+ * \param[in] n page image number: 0-based
+ * \param[out] pw [optional] width
+ * \param[out] ph [optional] height
+ * \param[out] pbps [optional] bits per sample -- 1, 2, 4 or 8
+ * \param[out] pspp [optional] samples per pixel -- 1 or 3
+ * \param[out] pres [optional] resolution in x dir; NULL to ignore
+ * \param[out] pcmap [optional] colormap exists; input NULL to ignore
+ * \param[out] pformat [optional] tiff format; input NULL to ignore
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+freadHeaderTiff(FILE *fp,
+ l_int32 n,
+ l_int32 *pw,
+ l_int32 *ph,
+ 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 (pw) *pw = 0;
+ if (ph) *ph = 0;
+ if (pbps) *pbps = 0;
+ if (pspp) *pspp = 0;
+ if (pres) *pres = 0;
+ if (pcmap) *pcmap = 0;
+ if (pformat) *pformat = 0;
+ if (!fp)
+ return ERROR_INT("stream not defined", procName, 1);
+ if (n < 0)
+ return ERROR_INT("image index must be >= 0", procName, 1);
+ if (!pw && !ph && !pbps && !pspp && !pres && !pcmap && !pformat)
+ return ERROR_INT("no results requested", procName, 1);
+
+ findFileFormatStream(fp, &format);
+ if (!L_FORMAT_IS_TIFF(format))
+ 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, pw, ph, pbps, pspp, pres, pcmap, pformat);
+ TIFFCleanup(tif);
+ return ret;
+}
+
+
+/*!
+ * \brief readHeaderMemTiff()
+ *
+ * \param[in] cdata const; tiff-encoded
+ * \param[in] size size of data
+ * \param[in] n page image number: 0-based
+ * \param[out] pw [optional] width
+ * \param[out] ph [optional] height
+ * \param[out] pbps [optional] bits per sample -- 1, 2, 4 or 8
+ * \param[out] pspp [optional] samples per pixel -- 1 or 3
+ * \param[out] pres [optional] resolution in x dir; NULL to ignore
+ * \param[out] pcmap [optional] colormap exists; input NULL to ignore
+ * \param[out] pformat [optional] tiff format; input NULL to ignore
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Use TIFFClose(); TIFFCleanup() doesn't free internal memstream.
+ * </pre>
+ */
+l_ok
+readHeaderMemTiff(const l_uint8 *cdata,
+ size_t size,
+ l_int32 n,
+ l_int32 *pw,
+ l_int32 *ph,
+ 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 (pw) *pw = 0;
+ if (ph) *ph = 0;
+ if (pbps) *pbps = 0;
+ if (pspp) *pspp = 0;
+ if (pres) *pres = 0;
+ if (pcmap) *pcmap = 0;
+ if (pformat) *pformat = 0;
+ if (!pw && !ph && !pbps && !pspp && !pres && !pcmap && !pformat)
+ return ERROR_INT("no results requested", procName, 1);
+ if (!cdata)
+ return ERROR_INT("cdata not defined", procName, 1);
+
+ /* 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, pw, ph, pbps, pspp, pres, pcmap, pformat);
+ TIFFClose(tif);
+ return ret;
+}
+
+
+/*!
+ * \brief tiffReadHeaderTiff()
+ *
+ * \param[in] tif
+ * \param[out] pw [optional] width
+ * \param[out] ph [optional] height
+ * \param[out] pbps [optional] bits per sample -- 1, 2, 4 or 8
+ * \param[out] pspp [optional] samples per pixel -- 1 or 3
+ * \param[out] pres [optional] resolution in x dir; NULL to ignore
+ * \param[out] pcmap [optional] cmap exists; input NULL to ignore
+ * \param[out] pformat [optional] tiff format; input NULL to ignore
+ * \return 0 if OK, 1 on error
+ */
+static l_int32
+tiffReadHeaderTiff(TIFF *tif,
+ l_int32 *pw,
+ l_int32 *ph,
+ 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);
+ TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h);
+ TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bps);
+ TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &spp);
+ if (w < 1 || h < 1)
+ return ERROR_INT("tif w and h not both > 0", procName, 1);
+ if (bps != 1 && bps != 2 && bps != 4 && bps != 8 && bps != 16)
+ return ERROR_INT("bps not in set {1,2,4,8,16}", procName, 1);
+ if (spp != 1 && spp != 2 && spp != 3 && spp != 4)
+ return ERROR_INT("spp not in set {1,2,3,4}", procName, 1);
+ if (pw) *pw = w;
+ if (ph) *ph = h;
+ if (pbps) *pbps = bps;
+ if (pspp) *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;
+}
+
+
+/*!
+ * \brief findTiffCompression()
+ *
+ * \param[in] fp file stream; must be rewound to BOF
+ * \param[out] pcomptype compression type
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief getTiffCompressedFormat()
+ *
+ * \param[in] tiffcomp defined in tiff.h
+ * \return compression format defined in imageio.h
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+ case COMPRESSION_JPEG:
+ comptype = IFF_TIFF_JPEG;
+ break;
+ default:
+ comptype = IFF_TIFF;
+ break;
+ }
+ return comptype;
+}
+
+
+/*--------------------------------------------------------------*
+ * Extraction of tiff g4 data *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief extractG4DataFromFile()
+ *
+ * \param[in] filein
+ * \param[out] pdata binary data of ccitt g4 encoded stream
+ * \param[out] pnbytes size of binary data
+ * \param[out] pw [optional] image width
+ * \param[out] ph [optional] image height
+ * \param[out] pminisblack [optional] boolean
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ LEPT_FREE(inarray);
+ 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];
+ }
+/* lept_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;
+ if (nbytes > MaxNumTiffBytes) {
+ LEPT_FREE(inarray);
+ L_ERROR("requesting %zu bytes > %zu\n", procName,
+ nbytes, MaxNumTiffBytes);
+ return 1;
+ }
+ *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 *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief fopenTiff()
+ *
+ * \param[in] fp file stream
+ * \param[in] modestring "r", "w", ...
+ * \return tiff data structure, opened for a file descriptor
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+
+ TIFFSetWarningHandler(NULL); /* disable warnings */
+ TIFFSetErrorHandler(NULL); /* disable error messages */
+
+ 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 *
+ *--------------------------------------------------------------*/
+/*!
+ * \brief openTiff()
+ *
+ * \param[in] filename
+ * \param[in] modestring "r", "w", ...
+ * \return tiff data structure
+ *
+ * <pre>
+ * Notes:
+ * (1) This handles multi-platform file naming.
+ * </pre>
+ */
+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);
+
+ TIFFSetWarningHandler(NULL); /* disable warnings */
+ TIFFSetErrorHandler(NULL); /* disable error messages */
+
+ 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. */
+
+/*!
+ * \brief Memory stream buffer used with TIFFClientOpen()
+ *
+ * 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 < amount || /* overflow */
+ mstream->offset + amount > mstream->hw) {
+ lept_stderr("Bad file: amount too big: %zu\n", 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:
+/* lept_stderr("seek_set: offset = %d\n", offset); */
+ if((size_t)offset != offset) { /* size_t overflow on uint32 */
+ return (toff_t)ERROR_INT("too large offset value", procName, 1);
+ }
+ mstream->offset = offset;
+ break;
+ case SEEK_CUR:
+/* lept_stderr("seek_cur: offset = %d\n", offset); */
+ mstream->offset += offset;
+ break;
+ case SEEK_END:
+/* lept_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;
+}
+
+
+/*!
+ * \brief fopenTiffMemstream()
+ *
+ * \param[in] filename for error output; can be ""
+ * \param[in] operation "w" for write, "r" for read
+ * \param[out] pdata written data
+ * \param[out] pdatasize size of written data
+ * \return tiff data structure, opened for write to memory
+ *
+ * <pre>
+ * 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.
+ * (3) This does not work in append mode, and in write mode it
+ * does not append.
+ * </pre>
+ */
+static TIFF *
+fopenTiffMemstream(const char *filename,
+ const char *operation,
+ l_uint8 **pdata,
+ size_t *pdatasize)
+{
+L_MEMSTREAM *mstream;
+TIFF *tif;
+
+ 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("op not 'r' or 'w'", procName, NULL);
+
+ if (!strcmp(operation, "r"))
+ mstream = memstreamCreateForRead(*pdata, *pdatasize);
+ else
+ mstream = memstreamCreateForWrite(pdata, pdatasize);
+
+ TIFFSetWarningHandler(NULL); /* disable warnings */
+ TIFFSetErrorHandler(NULL); /* disable error messages */
+
+ tif = TIFFClientOpen(filename, operation, (thandle_t)mstream,
+ tiffReadCallback, tiffWriteCallback,
+ tiffSeekCallback, tiffCloseCallback,
+ tiffSizeCallback, tiffMapCallback,
+ tiffUnmapCallback);
+ if (!tif)
+ LEPT_FREE(mstream);
+ return tif;
+}
+
+
+/*!
+ * \brief pixReadMemTiff()
+ *
+ * \param[in] cdata const; tiff-encoded
+ * \param[in] size size of cdata
+ * \param[in] n page image number: 0-based
+ * \return pix, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * (4) Tiff directory overhead is linear in the input page number.
+ * If reading many images, use pixReadMemFromMultipageTiff().
+ * </pre>
+ */
+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++) {
+ if (i == n) {
+ if ((pix = pixReadFromTiffStream(tif)) == NULL) {
+ TIFFClose(tif);
+ return NULL;
+ }
+ pixSetInputFormat(pix, IFF_TIFF);
+ break;
+ }
+ if (TIFFReadDirectory(tif) == 0)
+ break;
+ if (i == ManyPagesInTiffFile + 1) {
+ L_WARNING("big file: more than %d pages\n", procName,
+ ManyPagesInTiffFile);
+ }
+ }
+
+ TIFFClose(tif);
+ return pix;
+}
+
+
+/*!
+ * \brief pixReadMemFromMultipageTiff()
+ *
+ * \param[in] cdata const; tiff-encoded
+ * \param[in] size size of cdata
+ * \param[in,out] poffset set offset to 0 for first image
+ * \return pix, or NULL on error or if previous call returned the last image
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a read-from-memory version of pixReadFromMultipageTiff().
+ * See that function for usage.
+ * (2) If reading sequentially from the tiff data, this is more
+ * efficient than pixReadMemTiff(), which has an overhead
+ * proportional to the image index n.
+ * (3) Example usage for reading all the images:
+ * size_t offset = 0;
+ * do {
+ * Pix *pix = pixReadMemFromMultipageTiff(data, size, &offset);
+ * // do something with pix
+ * } while (offset != 0);
+ * </pre>
+ */
+PIX *
+pixReadMemFromMultipageTiff(const l_uint8 *cdata,
+ size_t size,
+ size_t *poffset)
+{
+l_uint8 *data;
+l_int32 retval;
+size_t offset;
+PIX *pix;
+TIFF *tif;
+
+ PROCNAME("pixReadMemFromMultipageTiff");
+
+ if (!cdata)
+ return (PIX *)ERROR_PTR("cdata not defined", procName, NULL);
+ if (!poffset)
+ return (PIX *)ERROR_PTR("&offset 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);
+
+ /* Set ptrs in the TIFF to the beginning of the image */
+ offset = *poffset;
+ retval = (offset == 0) ? TIFFSetDirectory(tif, 0)
+ : TIFFSetSubDirectory(tif, offset);
+ if (retval == 0) {
+ TIFFClose(tif);
+ return NULL;
+ }
+
+ if ((pix = pixReadFromTiffStream(tif)) == NULL) {
+ TIFFClose(tif);
+ return NULL;
+ }
+
+ /* Advance to the next image and return the new offset */
+ TIFFReadDirectory(tif);
+ *poffset = TIFFCurrentDirOffset(tif);
+ TIFFClose(tif);
+ return pix;
+}
+
+
+/*!
+ * \brief pixaReadMemMultipageTiff()
+ *
+ * \param[in] data const; multiple pages; tiff-encoded
+ * \param[in] size size of cdata
+ * \return pixa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is an O(n) read-from-memory version of pixaReadMultipageTiff().
+ * </pre>
+ */
+PIXA *
+pixaReadMemMultipageTiff(const l_uint8 *data,
+ size_t size)
+{
+size_t offset;
+PIX *pix;
+PIXA *pixa;
+
+ PROCNAME("pixaReadMemMultipageTiff");
+
+ if (!data)
+ return (PIXA *)ERROR_PTR("data not defined", procName, NULL);
+
+ offset = 0;
+ pixa = pixaCreate(0);
+ do {
+ pix = pixReadMemFromMultipageTiff(data, size, &offset);
+ pixaAddPix(pixa, pix, L_INSERT);
+ } while (offset != 0);
+ return pixa;
+}
+
+
+/*!
+ * \brief pixaWriteMemMultipageTiff()
+ *
+ * \param[out] pdata const; tiff-encoded
+ * \param[out] psize size of data
+ * \param[in] pixa any depth; colormap will be removed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) fopenTiffMemstream() does not work in append mode, so we
+ * must work-around with a temporary file.
+ * (2) Getting a file stream from
+ * open_memstream((char **)pdata, psize)
+ * does not work with the tiff directory.
+ * </pre>
+ */
+l_ok
+pixaWriteMemMultipageTiff(l_uint8 **pdata,
+ size_t *psize,
+ PIXA *pixa)
+{
+const char *modestr;
+l_int32 i, n;
+FILE *fp;
+PIX *pix1;
+
+ PROCNAME("pixaWriteMemMultipageTiff");
+
+ if (pdata) *pdata = NULL;
+ if (!pdata)
+ return ERROR_INT("pdata not defined", procName, 1);
+ if (!pixa)
+ return ERROR_INT("pixa not defined", procName, 1);
+
+#ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+#else
+ if ((fp = tmpfile()) == NULL)
+ return ERROR_INT("tmpfile stream not opened", procName, 1);
+#endif /* _WIN32 */
+
+ n = pixaGetCount(pixa);
+ for (i = 0; i < n; i++) {
+ modestr = (i == 0) ? "w" : "a";
+ pix1 = pixaGetPix(pixa, i, L_CLONE);
+ if (pixGetDepth(pix1) == 1)
+ pixWriteStreamTiffWA(fp, pix1, IFF_TIFF_G4, modestr);
+ else
+ pixWriteStreamTiffWA(fp, pix1, IFF_TIFF_ZIP, modestr);
+ pixDestroy(&pix1);
+ }
+
+ rewind(fp);
+ *pdata = l_binaryReadStream(fp, psize);
+ fclose(fp);
+ return 0;
+}
+
+
+/*!
+ * \brief pixWriteMemTiff()
+ *
+ * \param[out] pdata data of tiff compressed image
+ * \param[out] psize size of returned data
+ * \param[in] pix
+ * \param[in] comptype IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS,
+ * IFF_TIFF_G3, IFF_TIFF_G4,
+ * IFF_TIFF_LZW, IFF_TIFF_ZIP, IFF_TIFF_JPEG
+ * \return 0 if OK, 1 on error
+ *
+ * Usage:
+ * 1) See pixWriteTiff(. This version writes to
+ * memory instead of to a file.
+ */
+l_ok
+pixWriteMemTiff(l_uint8 **pdata,
+ size_t *psize,
+ PIX *pix,
+ l_int32 comptype)
+{
+ return pixWriteMemTiffCustom(pdata, psize, pix, comptype,
+ NULL, NULL, NULL, NULL);
+}
+
+
+/*!
+ * \brief pixWriteMemTiffCustom()
+ *
+ * \param[out] pdata data of tiff compressed image
+ * \param[out] psize size of returned data
+ * \param[in] pix
+ * \param[in] comptype IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS,
+ * IFF_TIFF_G3, IFF_TIFF_G4,
+ * IFF_TIFF_LZW, IFF_TIFF_ZIP, IFF_TIFF_JPEG
+ * \param[in] natags [optional] NUMA of custom tiff tags
+ * \param[in] savals [optional] SARRAY of values
+ * \param[in] satypes [optional] SARRAY of types
+ * \param[in] 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_ok
+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 &&
+ comptype != IFF_TIFF_JPEG) {
+ 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/leptonica/src/tiffiostub.c b/leptonica/src/tiffiostub.c
new file mode 100644
index 00000000..181eff4b
--- /dev/null
+++ b/leptonica/src/tiffiostub.c
@@ -0,0 +1,242 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file tiffiostub.c
+ * <pre>
+ *
+ * Stubs for tiffio.c functions
+ * </pre>
+ */
+
+#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_ok pixWriteTiff(const char *filename, PIX *pix, l_int32 comptype,
+ const char *modestring)
+{
+ return ERROR_INT("function not present", "pixWriteTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok pixWriteStreamTiff(FILE *fp, PIX *pix, l_int32 comptype)
+{
+ return ERROR_INT("function not present", "pixWriteStreamTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWriteStreamTiffWA(FILE *fp, PIX *pix, l_int32 comptype,
+ const char *modestr)
+{
+ return ERROR_INT("function not present", "pixWriteStreamTiffWA", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadFromMultipageTiff(const char *filename, size_t *poffset)
+{
+ return (PIX *)ERROR_PTR("function not present",
+ "pixReadFromMultipageTiff", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIXA * pixaReadMultipageTiff(const char *filename)
+{
+ return (PIXA *)ERROR_PTR("function not present",
+ "pixaReadMultipageTiff", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixaWriteMultipageTiff(const char *filename, PIXA *pixa)
+{
+ return ERROR_INT("function not present", "pixaWriteMultipageTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok writeMultipageTiff(const char *dirin, const char *substr,
+ const char *fileout)
+{
+ return ERROR_INT("function not present", "writeMultipageTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok writeMultipageTiffSA(SARRAY *sa, const char *fileout)
+{
+ return ERROR_INT("function not present", "writeMultipageTiffSA", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok fprintTiffInfo(FILE *fpout, const char *tiffile)
+{
+ return ERROR_INT("function not present", "fprintTiffInfo", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok tiffGetCount(FILE *fp, l_int32 *pn)
+{
+ return ERROR_INT("function not present", "tiffGetCount", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok getTiffResolution(FILE *fp, l_int32 *pxres, l_int32 *pyres)
+{
+ return ERROR_INT("function not present", "getTiffResolution", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok 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_ok 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_ok findTiffCompression(FILE *fp, l_int32 *pcomptype)
+{
+ return ERROR_INT("function not present", "findTiffCompression", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadMemFromMultipageTiff(const l_uint8 *cdata, size_t size,
+ size_t *poffset)
+{
+ return (PIX *)ERROR_PTR("function not present",
+ "pixReadMemFromMultipageTiff", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIXA * pixaReadMemMultipageTiff(const l_uint8 *data, size_t size)
+{
+ return (PIXA *)ERROR_PTR("function not present",
+ "pixaReadMemMultipageTiff", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixaWriteMemMultipageTiff(l_uint8 **pdata, size_t *psize, PIXA *pixa)
+{
+ return ERROR_INT("function not present", "pixaWriteMemMultipageTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWriteMemTiff(l_uint8 **pdata, size_t *psize, PIX *pix,
+ l_int32 comptype)
+{
+ return ERROR_INT("function not present", "pixWriteMemTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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/leptonica/src/utils1.c b/leptonica/src/utils1.c
new file mode 100644
index 00000000..67c9008e
--- /dev/null
+++ b/leptonica/src/utils1.c
@@ -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.
+ *====================================================================*/
+
+/*!
+ * \file utils1.c
+ * <pre>
+ *
+ * ------------------------------------------
+ * This file has these utilities:
+ * - error, warning and info messages
+ * - redirection of stderr
+ * - low-level endian conversions
+ * - file corruption operations
+ * - random and prime number operations
+ * - 64-bit hash functions
+ * - leptonica version number accessor
+ * - timing and date operations
+ * ------------------------------------------
+ *
+ * Control of error, warning and info messages
+ * l_int32 setMsgSeverity()
+ *
+ * Error return functions, invoked by macros
+ * l_int32 returnErrorInt()
+ * l_float32 returnErrorFloat()
+ * void *returnErrorPtr()
+ *
+ * Runtime redirection of stderr
+ * void leptSetStderrHandler()
+ * void lept_stderr()
+ *
+ * Test files for equivalence
+ * l_int32 filesAreIdentical()
+ *
+ * Byte-swapping data conversion
+ * l_uint16 convertOnBigEnd16()
+ * l_uint32 convertOnBigEnd32()
+ * l_uint16 convertOnLittleEnd16()
+ * l_uint32 convertOnLittleEnd32()
+ *
+ * File corruption and byte replacement operations
+ * l_int32 fileCorruptByDeletion()
+ * l_int32 fileCorruptByMutation()
+ * l_int32 fileReplaceBytes()
+ *
+ * Generate random integer in given interval
+ * l_int32 genRandomIntOnInterval()
+ *
+ * Simple math function
+ * l_int32 lept_roundftoi()
+ *
+ * 64-bit hash functions
+ * l_int32 l_hashStringToUint64()
+ * l_int32 l_hashPtToUint64()
+ * l_int32 l_hashFloat64ToUint64()
+ *
+ * Prime finders
+ * l_int32 findNextLargerPrime()
+ * l_int32 lept_isPrime()
+ *
+ * Gray code conversion
+ * l_uint32 convertIntToGrayCode()
+ * l_uint32 convertGrayCodeToInt()
+ *
+ * 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()
+ *
+ * For all issues with cross-platform development, see utils2.c.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#ifdef _WIN32
+#include <windows.h>
+#endif /* _WIN32 */
+
+#include <time.h>
+#include "allheaders.h"
+#include <math.h>
+
+ /* Global for controlling message output at runtime */
+LEPT_DLL l_int32 LeptMsgSeverity = DEFAULT_SEVERITY;
+
+#define DEBUG_SEV 0
+
+/*----------------------------------------------------------------------*
+ * Control of error, warning and info messages *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief setMsgSeverity()
+ *
+ * \param[in] newsev
+ * \return oldsev
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_int32
+setMsgSeverity(l_int32 newsev)
+{
+l_int32 oldsev;
+char *envsev;
+
+ oldsev = LeptMsgSeverity;
+ if (newsev == L_SEVERITY_EXTERNAL) {
+ envsev = getenv("LEPT_MSG_SEVERITY");
+ if (envsev) {
+ LeptMsgSeverity = atoi(envsev);
+#if DEBUG_SEV
+ L_INFO("message severity set to external\n", "setMsgSeverity");
+#endif /* DEBUG_SEV */
+ } else {
+#if DEBUG_SEV
+ L_WARNING("environment var LEPT_MSG_SEVERITY not defined\n",
+ "setMsgSeverity");
+#endif /* DEBUG_SEV */
+ }
+ } else {
+ LeptMsgSeverity = newsev;
+#if DEBUG_SEV
+ L_INFO("message severity set to %d\n", "setMsgSeverity", newsev);
+#endif /* DEBUG_SEV */
+ }
+
+ 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. *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief returnErrorInt()
+ *
+ * \param[in] msg error message
+ * \param[in] procname
+ * \param[in] ival return error val
+ * \return ival typically 1 for an error return
+ */
+l_int32
+returnErrorInt(const char *msg,
+ const char *procname,
+ l_int32 ival)
+{
+ lept_stderr("Error in %s: %s\n", procname, msg);
+ return ival;
+}
+
+
+/*!
+ * \brief returnErrorFloat()
+ *
+ * \param[in] msg error message
+ * \param[in] procname
+ * \param[in] fval return error val
+ * \return fval
+ */
+l_float32
+returnErrorFloat(const char *msg,
+ const char *procname,
+ l_float32 fval)
+{
+ lept_stderr("Error in %s: %s\n", procname, msg);
+ return fval;
+}
+
+
+/*!
+ * \brief returnErrorPtr()
+ *
+ * \param[in] msg error message
+ * \param[in] procname
+ * \param[in] pval return error val
+ * \return pval typically null for an error return
+ */
+void *
+returnErrorPtr(const char *msg,
+ const char *procname,
+ void *pval)
+{
+ lept_stderr("Error in %s: %s\n", procname, msg);
+ return pval;
+}
+
+
+/*------------------------------------------------------------------------*
+ * Runtime redirection of stderr *
+ *------------------------------------------------------------------------*
+ * *
+ * The user can provide a callback function to redirect messages *
+ * that would otherwise go to stderr. Here are two examples: *
+ * (1) to stop all messages: *
+ * void send_to_devnull(const char *msg) {} *
+ * (2) to write to the system logger: *
+ * void send_to_syslog(const char *msg) { *
+ * syslog(1, msg); *
+ * } *
+ * These would then be registered using *
+ * leptSetStderrHandler(send_to_devnull); *
+ * and *
+ * leptSetStderrHandler(send_to_syslog); *
+ *------------------------------------------------------------------------*/
+ /* By default, all messages go to stderr */
+static void lept_default_stderr_handler(const char *formatted_msg)
+{
+ if (formatted_msg)
+ fputs(formatted_msg, stderr);
+}
+
+ /* The stderr callback handler is private to leptonica.
+ * By default it writes to stderr. */
+void (*stderr_handler)(const char *) = lept_default_stderr_handler;
+
+
+/*!
+ * \brief leptSetStderrHandler()
+ *
+ * \param[in] handler callback function for lept_stderr output
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) This registers a handler for redirection of output to stderr
+ * at runtime.
+ * (2) If called with NULL, the output goes to stderr.
+ * </pre>
+ */
+void leptSetStderrHandler(void (*handler)(const char *))
+{
+ if (handler)
+ stderr_handler = handler;
+ else
+ stderr_handler = lept_default_stderr_handler;
+}
+
+
+#define MAX_DEBUG_MESSAGE 2000
+/*!
+ * \brief lept_stderr()
+ *
+ * \param[in] fmt format string
+ * \param[in] ... varargs
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a replacement for fprintf(), to allow redirection
+ * of output. All calls to fprintf(stderr, ...) are replaced
+ * with calls to lept_stderr(...).
+ * (2) The message size is limited to 2K bytes.
+ (3) This utility was provided by jbarlow83.
+ * </pre>
+ */
+void lept_stderr(const char *fmt, ...)
+{
+va_list args;
+char msg[MAX_DEBUG_MESSAGE];
+l_int32 n;
+
+ va_start(args, fmt);
+ n = vsnprintf(msg, sizeof(msg), fmt, args);
+ va_end(args);
+ if (n < 0)
+ return;
+ (*stderr_handler)(msg);
+}
+
+
+/*--------------------------------------------------------------------*
+ * Test files for equivalence *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief filesAreIdentical()
+ *
+ * \param[in] fname1
+ * \param[in] fname2
+ * \param[out] psame 1 if identical; 0 if different
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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) {
+ LEPT_FREE(array1);
+ 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 */
+
+
+/*---------------------------------------------------------------------*
+ * File corruption and byte replacement operations *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief fileCorruptByDeletion()
+ *
+ * \param[in] filein
+ * \param[in] loc fractional location of start of deletion
+ * \param[in] size fractional size of deletion
+ * \param[in] fileout corrupted file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fileCorruptByMutation()
+ *
+ * \param[in] filein
+ * \param[in] loc fractional location of start of randomization
+ * \param[in] size fractional size of randomization
+ * \param[in] fileout corrupted file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fileReplaceBytes()
+ *
+ * \param[in] filein input file
+ * \param[in] start start location for replacement
+ * \param[in] nbytes number of bytes to be removed
+ * \param[in] newdata replacement bytes
+ * \param[in] newsize size of replacement bytes
+ * \param[in] fileout output file
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) To remove %nbytes without replacement, set %newdata == NULL.
+ * (2) One use is for replacing the date/time in a pdf file by a
+ * string of 12 '0's, effectively removing the date without
+ * invalidating the byte counters in the pdf file:
+ * fileReplaceBytes(filein 86 12 (char *)"000000000000" 12 fileout
+ * </pre>
+ */
+l_ok
+fileReplaceBytes(const char *filein,
+ l_int32 start,
+ l_int32 nbytes,
+ l_uint8 *newdata,
+ size_t newsize,
+ const char *fileout)
+{
+l_int32 i, index;
+size_t inbytes, outbytes;
+l_uint8 *datain, *dataout;
+
+ PROCNAME("fileReplaceBytes");
+
+ if (!filein || !fileout)
+ return ERROR_INT("filein and fileout not both specified", procName, 1);
+
+ datain = l_binaryRead(filein, &inbytes);
+ if (start + nbytes > inbytes)
+ L_WARNING("start + nbytes > length(filein) = %zu\n", procName, inbytes);
+
+ if (!newdata) newsize = 0;
+ outbytes = inbytes - nbytes + newsize;
+ if ((dataout = (l_uint8 *)LEPT_CALLOC(outbytes, 1)) == NULL) {
+ LEPT_FREE(datain);
+ return ERROR_INT("calloc fail for dataout", procName, 1);
+ }
+
+ for (i = 0; i < start; i++)
+ dataout[i] = datain[i];
+ for (i = start; i < start + newsize; i++)
+ dataout[i] = newdata[i - start];
+ index = start + nbytes; /* for datain */
+ start += newsize; /* for dataout */
+ for (i = start; i < outbytes; i++, index++)
+ dataout[i] = datain[index];
+ l_binaryWrite(fileout, "w", dataout, outbytes);
+
+ LEPT_FREE(datain);
+ LEPT_FREE(dataout);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Generate random integer in given interval *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief genRandomIntOnInterval()
+ *
+ * \param[in] start beginning of interval; can be < 0
+ * \param[in] end end of interval; must be >= start
+ * \param[in] seed use 0 to skip; otherwise call srand
+ * \param[out] pval random integer in interval [start ... end]
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+genRandomIntOnInterval(l_int32 start,
+ l_int32 end,
+ l_int32 seed,
+ l_int32 *pval)
+{
+l_float64 range;
+
+ PROCNAME("genRandomIntOnInterval");
+
+ if (!pval)
+ return ERROR_INT("&val not defined", procName, 1);
+ *pval = 0;
+ if (end < start)
+ return ERROR_INT("invalid range", procName, 1);
+
+ if (seed > 0) srand(seed);
+ range = (l_float64)(end - start + 1);
+ *pval = start + (l_int32)((l_float64)range *
+ ((l_float64)rand() / (l_float64)RAND_MAX));
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Simple math function *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief lept_roundftoi()
+ *
+ * \param[in] fval
+ * \return value rounded to int
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_hashStringToUint64()
+ *
+ * \param[in] str
+ * \param[out] phash hash value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_hashPtToUint64()
+ *
+ * \param[in] x, y
+ * \param[out] phash hash value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This simple hash function has no collisions for
+ * any of 400 million points with x and y up to 20000.
+ * (2) Previously used a much more complicated and slower function:
+ * mulp = 26544357894361;
+ * hash = 104395301;
+ * hash += (x * mulp) ^ (hash >> 5);
+ * hash ^= (hash << 7);
+ * hash += (y * mulp) ^ (hash >> 7);
+ * hash = hash ^ (hash << 11);
+ * Such logical gymnastics to get coverage over the 2^64
+ * values are not required.
+ * </pre>
+ */
+l_ok
+l_hashPtToUint64(l_int32 x,
+ l_int32 y,
+ l_uint64 *phash)
+{
+ PROCNAME("l_hashPtToUint64");
+
+ if (!phash)
+ return ERROR_INT("&hash not defined", procName, 1);
+
+ *phash = (l_uint64)(2173249142.3849 * x + 3763193258.6227 * y);
+ return 0;
+}
+
+
+/*!
+ * \brief l_hashFloat64ToUint64()
+ *
+ * \param[in] nbuckets
+ * \param[in] val
+ * \param[out] phash hash value
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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_dnaMakeHistoByHash().
+ * </pre>
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief findNextLargerPrime()
+ *
+ * \param[in] start
+ * \param[out] pprime first prime larger than %start
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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);
+}
+
+
+/*!
+ * \brief lept_isPrime()
+ *
+ * \param[in] n 64-bit unsigned
+ * \param[out] pis_prime 1 if prime, 0 otherwise
+ * \param[out] pfactor [optional] smallest divisor, or 0 on error
+ * or if prime
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 = (l_uint64)sqrt((l_float64)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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief convertIntToGrayCode()
+ *
+ * \param[in] val integer value
+ * \return corresponding gray code value
+ *
+ * <pre>
+ * Notes:
+ * (1) Gray code values corresponding to integers differ by
+ * only one bit transition between successive integers.
+ * </pre>
+ */
+l_uint32
+convertIntToGrayCode(l_uint32 val)
+{
+ return (val >> 1) ^ val;
+}
+
+
+/*!
+ * \brief convertGrayCodeToInt()
+ *
+ * \param[in] val gray code value
+ * \return corresponding integer value
+ */
+l_uint32
+convertGrayCodeToInt(l_uint32 val)
+{
+l_uint32 shift;
+
+ for (shift = 1; shift < 32; shift <<= 1)
+ val ^= val >> shift;
+ return val;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Leptonica version number *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief getLeptonicaVersion()
+ *
+ * Return: string of version number (e.g., 'leptonica-1.74.2')
+ *
+ * Notes:
+ * (1) The caller has responsibility to free the memory.
+ */
+char *
+getLeptonicaVersion(void)
+{
+size_t bufsize = 100;
+
+ char *version = (char *)LEPT_CALLOC(bufsize, 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, bufsize, "leptonica-%d.%d.%d (%s, %s) [MSC v.%d %s %s%s]",
+ LIBLEPT_MAJOR_VERSION, LIBLEPT_MINOR_VERSION, LIBLEPT_PATCH_VERSION,
+ __DATE__, __TIME__, _MSC_VER, dllStr, debugStr, bitStr);
+
+#else
+
+ snprintf(version, bufsize, "leptonica-%d.%d.%d", LIBLEPT_MAJOR_VERSION,
+ LIBLEPT_MINOR_VERSION, LIBLEPT_PATCH_VERSION);
+
+#endif /* _MSC_VER */
+ return version;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Timing procs *
+ *---------------------------------------------------------------------*/
+#if !defined(_WIN32) && !defined(__Fuchsia__)
+
+#include <sys/time.h>
+#include <sys/resource.h>
+
+static struct rusage rusage_before;
+static struct rusage rusage_after;
+
+/*!
+ * \brief startTimer(), stopTimer()
+ *
+ * Notes:
+ * (1) These measure the cpu time elapsed between the two calls:
+ * startTimer();
+ * ....
+ * lept_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);
+}
+
+
+/*!
+ * \brief startTimerNested(), stopTimerNested()
+ *
+ * Example of usage:
+ *
+ * L_TIMER t1 = startTimerNested();
+ * ....
+ * L_TIMER t2 = startTimerNested();
+ * ....
+ * lept_stderr( "Elapsed time 2 = %7.3f sec\n", stopTimerNested(t2));
+ * ....
+ * lept_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);
+}
+
+
+/*!
+ * \brief l_getCurrentTime()
+ *
+ * \param[out] sec [optional] in seconds since birth of Unix
+ * \param[out] usec [optional] 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;
+}
+
+#elif defined(__Fuchsia__) /* resource.h not implemented on Fuchsia. */
+
+ /* Timer functions are used for testing and debugging, and
+ * are stubbed out. If they are needed in the future, they
+ * can be implemented in Fuchsia using the zircon syscall
+ * zx_object_get_info() in ZX_INFOR_THREAD_STATS mode. */
+void
+startTimer(void)
+{
+}
+
+l_float32
+stopTimer(void)
+{
+ return 0.0;
+}
+
+L_TIMER
+startTimerNested(void)
+{
+ return NULL;
+}
+
+l_float32
+stopTimerNested(L_TIMER rusage_start)
+{
+ return 0.0;
+}
+
+void
+l_getCurrentTime(l_int32 *sec,
+ l_int32 *usec)
+{
+}
+
+#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);
+}
+
+#endif
+
+
+/*!
+ * \brief startWallTimer()
+ *
+ * \return walltimer-ptr
+ *
+ * <pre>
+ * Notes:
+ * (1) These measure the wall clock time elapsed between the two calls:
+ * L_WALLTIMER *timer = startWallTimer();
+ * ....
+ * lept_stderr( "Elapsed time = %f sec\n", stopWallTimer(&timer);
+ * (2) Note that the timer object is destroyed by stopWallTimer().
+ * </pre>
+ */
+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;
+}
+
+/*!
+ * \brief stopWallTimer()
+ *
+ * \param[in,out] ptimer walltimer pointer
+ * \return time wall time elapsed in seconds
+ */
+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);
+}
+
+
+/*!
+ * \brief l_getFormattedDate()
+ *
+ * \return formatted date string, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+char *
+l_getFormattedDate(void)
+{
+char buf[128] = "", sep = 'Z';
+l_int32 gmt_offset, relh, relm;
+time_t ut, lt;
+struct tm Tm;
+struct tm *tptr = &Tm;
+
+ 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. */
+#ifdef _WIN32
+ #ifdef _MSC_VER
+ gmtime_s(tptr, &ut);
+ #else /* mingw */
+ tptr = gmtime(&ut);
+ #endif
+#else
+ gmtime_r(&ut, tptr);
+#endif
+ 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;
+
+#ifdef _WIN32
+ #ifdef _MSC_VER
+ localtime_s(tptr, &ut);
+ #else /* mingw */
+ tptr = localtime(&ut);
+ #endif
+#else
+ localtime_r(&ut, tptr);
+#endif
+ strftime(buf, sizeof(buf), "%Y%m%d%H%M%S", tptr);
+ sprintf(buf + 14, "%c%02d'%02d'", sep, relh, relm);
+ return stringNew(buf);
+}
diff --git a/leptonica/src/utils2.c b/leptonica/src/utils2.c
new file mode 100644
index 00000000..687bfdbe
--- /dev/null
+++ b/leptonica/src/utils2.c
@@ -0,0 +1,3485 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file utils2.c
+ * <pre>
+ *
+ * ------------------------------------------
+ * This file has these utilities:
+ * - safe string operations
+ * - find/replace operations on strings
+ * - read/write between file and memory
+ * - multi-platform file and directory operations
+ * - file name operations
+ * ------------------------------------------
+ *
+ * Safe string procs
+ * char *stringNew()
+ * l_int32 stringCopy()
+ * l_int32 stringCopySegment()
+ * 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
+ * l_int32 stringCheckForChars()
+ * char *stringRemoveChars()
+ * char *stringReplaceEachSubstr()
+ * char *stringReplaceSubstr()
+ * L_DNA *stringFindEachSubstr()
+ * l_int32 stringFindSubstr()
+ * l_uint8 *arrayReplaceEachSequence()
+ * 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 and compare in memory
+ * l_uint8 *l_binaryCopy()
+ * l_uint8 *l_binaryCompare()
+ *
+ * File copy operations
+ * l_int32 fileCopy()
+ * l_int32 fileConcatenate()
+ * l_int32 fileAppendString()
+ *
+ * File split operations
+ * l_int32 fileSplitLinesUniform()
+ *
+ * Multi-platform functions for opening file streams
+ * FILE *fopenReadStream()
+ * FILE *fopenWriteStream()
+ * FILE *fopenReadFromMemory()
+ *
+ * Opening a windows tmpfile for writing
+ * FILE *fopenWriteWinTempfile()
+ *
+ * Multi-platform functions that avoid C-runtime boundary crossing
+ * with Windows DLLs
+ * FILE *lept_fopen()
+ * l_int32 lept_fclose()
+ * void *lept_calloc()
+ * void lept_free()
+ *
+ * Multi-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()
+ *
+ * Special debug/test function for calling 'system'
+ * void callSystemDebug()
+ *
+ * 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 *l_makeTempFilename()
+ * l_int32 extractNumberFromFilename()
+ *
+ *
+ * Notes on multi-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().
+ * (7) Why read and write files to temp directories?
+ * The library needs the ability to read and write ephemeral
+ * files to default places, both for generating debugging output
+ * and for supporting regression tests. Applications also need
+ * this ability for debugging.
+ * (8) Why do the pathname rewrite on Windows?
+ * The goal is to have the library, and programs using the library,
+ * run on multiple platforms without changes. The location of
+ * temporary files depends on the platform as well as the user's
+ * configuration. Temp files on Windows are in some directory
+ * not known a priori. To make everything work seamlessly on
+ * Windows, every time you open a file for reading or writing,
+ * use a special function such as fopenReadStream() or
+ * fopenWriteStream(); these call genPathname() to ensure that
+ * if it is a temp file, the correct path is used. To indicate
+ * that this is a temp file, the application is written with the
+ * root directory of the path in a canonical form: "/tmp".
+ * (9) Why is it that multi-platform directory functions like lept_mkdir()
+ * and lept_rmdir(), as well as associated file functions like
+ * lept_rm(), lept_mv() and lept_cp(), only work in the temp dir?
+ * These functions were designed to provide easy manipulation of
+ * temp files. The restriction to temp files is for safety -- to
+ * prevent an accidental deletion of important files. For example,
+ * lept_rmdir() first deletes all files in a specified subdirectory
+ * of temp, and then removes the directory.
+ *
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#ifdef _MSC_VER
+#include <process.h>
+#include <direct.h>
+#define getcwd _getcwd /* fix MSVC warning */
+#else
+#include <unistd.h>
+#endif /* _MSC_VER */
+
+#ifdef _WIN32
+#include <windows.h>
+#include <fcntl.h> /* _O_CREAT, ... */
+#include <io.h> /* _open */
+#include <sys/stat.h> /* _S_IREAD, _S_IWRITE */
+#else
+#include <sys/stat.h> /* for stat, mkdir(2) */
+#include <sys/types.h>
+#endif
+
+#ifdef OS_IOS
+#include <unistd.h>
+#include <errno.h>
+#endif
+
+#include <string.h>
+#include <stddef.h>
+#include "allheaders.h"
+
+
+/*--------------------------------------------------------------------*
+ * Safe string operations *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief stringNew()
+ *
+ * \param[in] src
+ * \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;
+}
+
+
+/*!
+ * \brief stringCopy()
+ *
+ * \param[in] dest existing byte buffer
+ * \param[in] src string [optional] can be null
+ * \param[in] n max number of characters to copy
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief stringCopySegment()
+ *
+ *
+ * \param[in] src string
+ * \param[in] start byte position at start of segment
+ * \param[in] nbytes number of bytes in the segment; use 0 to go to end
+ * \return copy of segment, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a variant of stringNew() that makes a new string
+ * from a segment of the input string. The segment is specified
+ * by the starting position and the number of bytes.
+ * (2) The start location %start must be within the string %src.
+ * (3) The copy is truncated to the end of the source string.
+ * Use %nbytes = 0 to copy to the end of %src.
+ * </pre>
+ */
+char *
+stringCopySegment(const char *src,
+ l_int32 start,
+ l_int32 nbytes)
+{
+char *dest;
+l_int32 len;
+
+ PROCNAME("stringCopySegment");
+
+ if (!src)
+ return (char *)ERROR_PTR("src not defined", procName, NULL);
+ len = strlen(src);
+ if (start < 0 || start > len - 1)
+ return (char *)ERROR_PTR("invalid start", procName, NULL);
+ if (nbytes <= 0) /* copy to the end */
+ nbytes = len - start;
+ if (start + nbytes > len) /* truncate to the end */
+ nbytes = len - start;
+ if ((dest = (char *)LEPT_CALLOC(nbytes + 1, sizeof(char))) == NULL)
+ return (char *)ERROR_PTR("dest not made", procName, NULL);
+ stringCopy(dest, src + start, nbytes);
+ return dest;
+}
+
+
+/*!
+ * \brief stringReplace()
+ *
+ * \param[out] pdest string copy
+ * \param[in] src [optional] string; can be null
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+stringReplace(char **pdest,
+ const char *src)
+{
+ PROCNAME("stringReplace");
+
+ if (!pdest)
+ return ERROR_INT("pdest not defined", procName, 1);
+
+ if (*pdest)
+ LEPT_FREE(*pdest);
+
+ if (src)
+ *pdest = stringNew(src);
+ else
+ *pdest = NULL;
+ return 0;
+}
+
+
+/*!
+ * \brief stringLength()
+ *
+ * \param[in] src string can be null or NULL-terminated string
+ * \param[in] size size of src buffer
+ * \return length of src in bytes.
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+}
+
+
+/*!
+ * \brief stringCat()
+ *
+ * \param[in] dest null-terminated byte buffer
+ * \param[in] size size of dest
+ * \param[in] src string can be null or NULL-terminated string
+ * \return number of bytes added to dest; -1 on error
+ *
+ * <pre>
+ * 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 there is not enough room to append the src, which is 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().
+ * </pre>
+ */
+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 ? 0 : 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;
+}
+
+
+/*!
+ * \brief stringConcatNew()
+ *
+ * \param[in] first first string in list
+ * \param[in] ... NULL-terminated list of strings
+ * \return result new string concatenating the input strings, or
+ * NULL if first == NULL
+ *
+ * <pre>
+ * Notes:
+ * (1) The last arg in the list of strings must be NULL.
+ * (2) Caller must free the returned string.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief stringJoin()
+ *
+ * \param[in] src1 [optional] string; can be null
+ * \param[in] src2 [optional] string; can be null
+ * \return concatenated string, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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)
+ stringCat(dest, destlen, src1);
+ if (src2)
+ stringCat(dest, destlen, src2);
+ return dest;
+}
+
+
+/*!
+ * \brief stringJoinIP()
+ *
+ * \param[in,out] psrc1 address of string src1; cannot be on the stack
+ * \param[in] src2 [optional] string; can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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:
+ * \code
+ * #define stringJoinIP(src1, src2) \
+ * {tmpstr = stringJoin((src1),(src2)); \
+ * LEPT_FREE(src1); \
+ * (src1) = tmpstr;}
+ * \endcode
+ * (5) Another function to consider for joining many strings is
+ * stringConcatNew().
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief stringReverse()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief strtokSafe()
+ *
+ * \param[in] cstr input string to be sequentially parsed;
+ * use NULL after the first call
+ * \param[in] seps a string of character separators
+ * \param[out] psaveptr 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.
+ *
+ * <pre>
+ * 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_r.
+ * </pre>
+ */
+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;
+ *psaveptr = NULL;
+ }
+ 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;
+}
+
+
+/*!
+ * \brief stringSplitOnToken()
+ *
+ * \param[in] cstr input string to be split; not altered
+ * \param[in] seps a string of character separators
+ * \param[out] phead ptr to copy of the input string, up to
+ * the first separator token encountered
+ * \param[out] ptail 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief stringCheckForChars()
+ *
+ * \param[in] src input string; can be of zero length
+ * \param[in] chars string of chars to be searched for in %src
+ * \param[out] pfound 1 if any characters are found; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This can be used to sanitize an operation by checking for
+ * special characters that don't belong in a string.
+ * </pre>
+ */
+l_ok
+stringCheckForChars(const char *src,
+ const char *chars,
+ l_int32 *pfound)
+{
+char ch;
+l_int32 i, n;
+
+ PROCNAME("stringCheckForChars");
+
+ if (!pfound)
+ return ERROR_INT("&found not defined", procName, 1);
+ *pfound = FALSE;
+ if (!src || !chars)
+ return ERROR_INT("src and chars not both defined", procName, 1);
+
+ n = strlen(src);
+ for (i = 0; i < n; i++) {
+ ch = src[i];
+ if (strchr(chars, ch)) {
+ *pfound = TRUE;
+ break;
+ }
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief stringRemoveChars()
+ *
+ * \param[in] src input string; can be of zero length
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief stringReplaceEachSubstr()
+ *
+ * \param[in] src input string; can be of zero length
+ * \param[in] sub1 substring to be replaced
+ * \param[in] sub2 substring to put in; can be ""
+ * \param[out] pcount [optional] 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.
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a wrapper for simple string substitution that uses
+ * the more general function arrayReplaceEachSequence().
+ * (2) This finds every non-overlapping occurrence of %sub1 in
+ * %src, and replaces it with %sub2. By "non-overlapping"
+ * we mean that after it finds each match, it removes the
+ * matching characters, replaces with the substitution string
+ * (if not empty), and continues. For example, if you replace
+ * 'aa' by 'X' in 'baaabbb', you find one match at position 1
+ * and return 'bXabbb'.
+ * (3) To only remove each instance of sub1, use "" for sub2
+ * (4) Returns a copy of %src if sub1 and sub2 are the same.
+ * (5) If the input %src is binary data that can have null characters,
+ * use arrayReplaceEachSequence() directly.
+ * </pre>
+ */
+char *
+stringReplaceEachSubstr(const char *src,
+ const char *sub1,
+ const char *sub2,
+ l_int32 *pcount)
+{
+size_t datalen;
+
+ PROCNAME("stringReplaceEachSubstr");
+
+ if (pcount) *pcount = 0;
+ if (!src || !sub1 || !sub2)
+ return (char *)ERROR_PTR("src, sub1, sub2 not all defined",
+ procName, NULL);
+
+ if (strlen(sub2) > 0) {
+ return (char *)arrayReplaceEachSequence(
+ (const l_uint8 *)src, strlen(src),
+ (const l_uint8 *)sub1, strlen(sub1),
+ (const l_uint8 *)sub2, strlen(sub2),
+ &datalen, pcount);
+ } else { /* empty replacement string; removal only */
+ return (char *)arrayReplaceEachSequence(
+ (const l_uint8 *)src, strlen(src),
+ (const l_uint8 *)sub1, strlen(sub1),
+ NULL, 0, &datalen, pcount);
+ }
+}
+
+
+/*!
+ * \brief stringReplaceSubstr()
+ *
+ * \param[in] src input string; can be of zero length
+ * \param[in] sub1 substring to be replaced
+ * \param[in] sub2 substring to put in; can be ""
+ * \param[in,out] ploc [optional] input start location for search;
+ * returns the loc after replacement
+ * \param[out] pfound [optional] 1 if sub1 is found; 0 otherwise
+ * \return dest string with substring replaced, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) Replaces the first instance.
+ * (2) To remove sub1 without replacement, use "" for sub2.
+ * (3) Returns a copy of %src if either no instance of %sub1 is found,
+ * or if %sub1 and %sub2 are the same.
+ * (4) If %ploc == NULL, the search will start at the beginning of %src.
+ * If %ploc != NULL, *ploc must be initialized to the byte offset
+ * within %src from which the search starts. To search the
+ * string from the beginning, set %loc = 0 and input &loc.
+ * After finding %sub1 and replacing it with %sub2, %loc will be
+ * returned as the next position after %sub2 in the output string.
+ * (5) Note that the output string also includes all the characters
+ * from the input string that occur after the single substitution.
+ * </pre>
+ */
+char *
+stringReplaceSubstr(const char *src,
+ const char *sub1,
+ const char *sub2,
+ l_int32 *ploc,
+ l_int32 *pfound)
+{
+const char *ptr;
+char *dest;
+l_int32 nsrc, nsub1, nsub2, len, npre, loc;
+
+ PROCNAME("stringReplaceSubstr");
+
+ if (pfound) *pfound = 0;
+ if (!src || !sub1 || !sub2)
+ return (char *)ERROR_PTR("src, sub1, sub2 not all defined",
+ procName, NULL);
+
+ if (ploc)
+ loc = *ploc;
+ else
+ loc = 0;
+ if (!strcmp(sub1, sub2))
+ return stringNew(src);
+ if ((ptr = strstr(src + loc, sub1)) == NULL)
+ return stringNew(src);
+ 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;
+}
+
+
+/*!
+ * \brief stringFindEachSubstr()
+ *
+ * \param[in] src input string; can be of zero length
+ * \param[in] sub substring to be searched for
+ * \return dna of offsets where the sequence is found, or NULL if
+ * none are found or on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This finds every non-overlapping occurrence in %src of %sub.
+ * After it finds each match, it moves forward in %src by the length
+ * of %sub before continuing the search. So for example,
+ * if you search for the sequence 'aa' in the data 'baaabbb',
+ * you find one match at position 1.
+
+ * </pre>
+ */
+L_DNA *
+stringFindEachSubstr(const char *src,
+ const char *sub)
+{
+ PROCNAME("stringFindEachSubstr");
+
+ if (!src || !sub)
+ return (L_DNA *)ERROR_PTR("src, sub not both defined", procName, NULL);
+
+ return arrayFindEachSequence((const l_uint8 *)src, strlen(src),
+ (const l_uint8 *)sub, strlen(sub));
+}
+
+
+/*!
+ * \brief stringFindSubstr()
+ *
+ * \param[in] src input string; can be of zero length
+ * \param[in] sub substring to be searched for; must not be empty
+ * \param[out] ploc [optional] location of substring in src
+ * \return 1 if found; 0 if not found or on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a wrapper around strstr(). It finds the first
+ * instance of %sub in %src. If the substring is not found
+ * and the location is returned, it has the value -1.
+ * (2) Both %src and %sub must be defined, and %sub must have
+ * length of at least 1.
+ * </pre>
+ */
+l_int32
+stringFindSubstr(const char *src,
+ const char *sub,
+ l_int32 *ploc)
+{
+const char *ptr;
+
+ PROCNAME("stringFindSubstr");
+
+ if (ploc) *ploc = -1;
+ if (!src || !sub)
+ return ERROR_INT("src and sub not both defined", procName, 0);
+ if (strlen(sub) == 0)
+ return ERROR_INT("substring length 0", procName, 0);
+ if (strlen(src) == 0)
+ return 0;
+
+ if ((ptr = strstr(src, sub)) == NULL) /* not found */
+ return 0;
+
+ if (ploc)
+ *ploc = ptr - src;
+ return 1;
+}
+
+
+/*!
+ * \brief arrayReplaceEachSequence()
+ *
+ * \param[in] datas source byte array
+ * \param[in] dataslen length of source data, in bytes
+ * \param[in] seq subarray of bytes to find in source data
+ * \param[in] seqlen length of subarray, in bytes
+ * \param[in] newseq replacement subarray; can be null
+ * \param[in] newseqlen length of replacement subarray, in bytes
+ * \param[out] pdatadlen length of dest byte array, in bytes
+ * \param[out] pcount [optional] the number of times that sub1
+ * is found in src; 0 if not found
+ * \return datad with all all subarrays replaced (or removed)
+ *
+ * <pre>
+ * Notes:
+ * (1) The byte arrays %datas, %seq and %newseq are not C strings,
+ * because they can contain null bytes. Therefore, for each
+ * we must give the length of the array.
+ * (2) If %newseq == NULL, this just removes all instances of %seq.
+ * Otherwise, it replaces every non-overlapping occurrence of
+ * %seq in %datas with %newseq. A new array %datad and its
+ * size are returned. See arrayFindEachSequence() for more
+ * details on finding non-overlapping occurrences.
+ * (3) If no instances of %seq are found, this returns a copy of %datas.
+ * (4) The returned %datad is null terminated.
+ * (5) Can use stringReplaceEachSubstr() if using C strings.
+ * </pre>
+ */
+l_uint8 *
+arrayReplaceEachSequence(const l_uint8 *datas,
+ size_t dataslen,
+ const l_uint8 *seq,
+ size_t seqlen,
+ const l_uint8 *newseq,
+ size_t newseqlen,
+ size_t *pdatadlen,
+ l_int32 *pcount)
+{
+l_uint8 *datad;
+size_t newsize;
+l_int32 n, i, j, di, si, index, incr;
+L_DNA *da;
+
+ PROCNAME("arrayReplaceEachSequence");
+
+ if (pcount) *pcount = 0;
+ if (!datas || !seq)
+ return (l_uint8 *)ERROR_PTR("datas & seq not both defined",
+ procName, NULL);
+ if (!pdatadlen)
+ return (l_uint8 *)ERROR_PTR("&datadlen not defined", procName, NULL);
+ *pdatadlen = 0;
+
+ /* Identify the locations of the sequence. If there are none,
+ * return a copy of %datas. */
+ if ((da = arrayFindEachSequence(datas, dataslen, seq, seqlen)) == NULL) {
+ *pdatadlen = dataslen;
+ return l_binaryCopy(datas, dataslen);
+ }
+
+ /* Allocate the output data; insure null termination */
+ n = l_dnaGetCount(da);
+ if (pcount) *pcount = n;
+ if (!newseq) newseqlen = 0;
+ newsize = dataslen + n * (newseqlen - seqlen) + 4;
+ if ((datad = (l_uint8 *)LEPT_CALLOC(newsize, sizeof(l_uint8))) == NULL) {
+ l_dnaDestroy(&da);
+ return (l_uint8 *)ERROR_PTR("datad not made", procName, NULL);
+ }
+
+ /* Replace each sequence instance with a new sequence */
+ l_dnaGetIValue(da, 0, &si);
+ for (i = 0, di = 0, index = 0; i < dataslen; i++) {
+ if (i == si) {
+ index++;
+ if (index < n) {
+ l_dnaGetIValue(da, index, &si);
+ incr = L_MIN(seqlen, si - i); /* amount to remove from datas */
+ } else {
+ incr = seqlen;
+ }
+ i += incr - 1; /* jump over the matched sequence in datas */
+ if (newseq) { /* add new sequence to datad */
+ for (j = 0; j < newseqlen; j++)
+ datad[di++] = newseq[j];
+ }
+ } else {
+ datad[di++] = datas[i];
+ }
+ }
+
+ *pdatadlen = di;
+ l_dnaDestroy(&da);
+ return datad;
+}
+
+
+/*!
+ * \brief arrayFindEachSequence()
+ *
+ * \param[in] data byte array
+ * \param[in] datalen length of data, in bytes
+ * \param[in] sequence subarray of bytes to find in data
+ * \param[in] seqlen length of sequence, in bytes
+ * \return dna of offsets where the sequence is found, or NULL if
+ * none are found or on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The byte arrays %data and %sequence are not C strings,
+ * because they can contain null bytes. Therefore, for each
+ * we must give the length of the array.
+ * (2) This finds every non-overlapping occurrence in %data of %sequence.
+ * After it finds each match, it moves forward by the length
+ * of the sequence before continuing the search. So for example,
+ * if you search for the sequence 'aa' in the data 'baaabbb',
+ * you find one match at position 1.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief arrayFindSequence()
+ *
+ * \param[in] data byte array
+ * \param[in] datalen length of data, in bytes
+ * \param[in] sequence subarray of bytes to find in data
+ * \param[in] seqlen length of sequence, in bytes
+ * \param[out] poffset offset from beginning of
+ * data where the sequence begins
+ * \param[out] pfound 1 if sequence is found; 0 otherwise
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The byte arrays 'data' and 'sequence' are not C strings,
+ * because 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.
+ * </pre>
+ */
+l_ok
+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 *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief reallocNew()
+ *
+ * \param[in,out] pindata nulls indata before reallocing
+ * \param[in] oldsize size of input data to be copied, in bytes
+ * \param[in] newsize size of buffer 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
+ *
+ * <pre>
+ * Notes:
+ * (1) If newsize == 0, frees input data and nulls ptr
+ * (2) If input data is null, only 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.
+ * </pre>
+ */
+void *
+reallocNew(void **pindata,
+ size_t oldsize,
+ size_t newsize)
+{
+size_t 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(newdata, indata, minsize);
+ LEPT_FREE(indata);
+ *pindata = NULL;
+ return newdata;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Read and write between file and memory *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief l_binaryRead()
+ *
+ * \param[in] filename
+ * \param[out] pnbytes 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;
+}
+
+
+/*!
+ * \brief l_binaryReadStream()
+ *
+ * \param[in] fp file stream opened to read; can be stdin
+ * \param[out] pnbytes number of bytes read
+ * \return null-terminated array, or NULL on error; reading 0 bytes
+ * is not an error
+ *
+ * <pre>
+ * 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:
+ * \code
+ * cat <imagefile> | readprog
+ * readprog < <imagefile>
+ * \endcode
+ * where readprog is:
+ * \code
+ * l_uint8 *data = l_binaryReadStream(stdin, &nbytes);
+ * Pix *pix = pixReadMem(data, nbytes);
+ * \endcode
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_binaryReadSelect()
+ *
+ * \param[in] filename
+ * \param[in] start first byte to read
+ * \param[in] nbytes number of bytes to read; use 0 to read to end of file
+ * \param[out] pnread number of bytes actually read
+ * \return data, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief l_binaryReadSelectStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] start first byte to read
+ * \param[in] nbytes number of bytes to read; use 0 to read to end of file
+ * \param[out] pnread number of bytes actually read
+ * \return null-terminated array, or NULL on error; reading 0 bytes
+ * is not an error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 = %zu but filebytes = %zu\n", procName,
+ start, 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("%zu bytes requested; %zu bytes read\n", procName,
+ nbytes, nread);
+ *pnread = nread;
+ fseek(fp, 0, SEEK_SET);
+ return data;
+}
+
+
+/*!
+ * \brief l_binaryWrite()
+ *
+ * \param[in] filename output file
+ * \param[in] operation "w" for write; "a" for append
+ * \param[in] data binary data to be written
+ * \param[in] nbytes size of data array
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+l_binaryWrite(const char *filename,
+ const char *operation,
+ const 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);
+ stringCat(actualOperation, 20, "b");
+
+ if ((fp = fopenWriteStream(filename, actualOperation)) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+ fwrite(data, 1, nbytes, fp);
+ fclose(fp);
+ return 0;
+}
+
+
+/*!
+ * \brief nbytesInFile()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief fnbytesInFile()
+ *
+ * \param[in] fp file stream
+ * \return nbytes in file; 0 on error
+ */
+size_t
+fnbytesInFile(FILE *fp)
+{
+l_int64 pos, nbytes;
+
+ PROCNAME("fnbytesInFile");
+
+ if (!fp)
+ return ERROR_INT("stream not open", procName, 0);
+
+ pos = ftell(fp); /* initial position */
+ if (pos < 0)
+ return ERROR_INT("seek position must be > 0", procName, 0);
+ fseek(fp, 0, SEEK_END); /* EOF */
+ nbytes = ftell(fp);
+ if (nbytes < 0)
+ return ERROR_INT("nbytes is < 0", procName, 0);
+ fseek(fp, pos, SEEK_SET); /* back to initial position */
+ return nbytes;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Copy and compare in memory *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief l_binaryCopy()
+ *
+ * \param[in] datas
+ * \param[in] size of data array
+ * \return datad on heap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_uint8 *
+l_binaryCopy(const 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;
+}
+
+
+/*!
+ * \brief l_binaryCompare()
+ *
+ * \param[in] data1
+ * \param[in] size1 of data1
+ * \param[in] data2
+ * \param[in] size2 of data1
+ * \param[out] psame (1 if the same, 0 if different)
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This can also be used to compare C strings str1 and str2.
+ * If the string lengths are not known, use strlen():
+ * l_binaryCompare((l_uint8 *)str1, strlen(str1),
+ (l_uint8 *)str2, strlen(str2));
+ * </pre>
+ */
+l_ok
+l_binaryCompare(const l_uint8 *data1,
+ size_t size1,
+ const l_uint8 *data2,
+ size_t size2,
+ l_int32 *psame)
+{
+l_int32 i;
+
+ PROCNAME("l_binaryCompare");
+
+ if (!psame)
+ return ERROR_INT("&same not defined", procName, 1);
+ *psame = FALSE;
+ if (!data1 || !data2)
+ return ERROR_INT("data1 and data2 not both defined", procName, 1);
+ if (size1 != size2) return 0;
+ for (i = 0; i < size1; i++) {
+ if (data1[i] != data2[i])
+ return 0;
+ }
+ *psame = TRUE;
+ return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ * File copy operations *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fileCopy()
+ *
+ * \param[in] srcfile copy from this file
+ * \param[in] newfile copy to this file
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fileConcatenate()
+ *
+ * \param[in] srcfile append data from this file
+ * \param[in] destfile add data to this file
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief fileAppendString()
+ *
+ * \param[in] filename
+ * \param[in] str string to append to file
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*--------------------------------------------------------------------*
+ * File split operations *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fileSplitLinesUniform()
+ *
+ * \param[in] filename input file
+ * \param[in] n number of output files (>= 1)
+ * \param[in] save_empty 1 to save empty lines; 0 to remove them
+ * \param[in] rootpath root pathname of output files
+ * \param[in] ext output extension, including the '.'; can be NULL
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This splits an input text file into %n files with roughly
+ * equal numbers of text lines in each file.
+ * (2) if %save_empty == 1, empty lines are included, and concatention
+ * of the text in the split files will be identical to the original.
+ * (3) The output filenames are in the form:
+ * <rootpath>_N.<ext>, N = 1, ... n
+ * (4) This handles the temp directory pathname conversion on windows:
+ * /tmp ==> [Windows Temp directory]
+ * (5) Files can also be sharded into sets of lines by the program 'split':
+ * split -n l/<n> <filename>
+ * Using 'split', the resulting files have approximately equal
+ * numbers of bytes, rather than equal numbers of lines.
+ * </pre>
+ */
+l_ok
+fileSplitLinesUniform(const char *filename,
+ l_int32 n,
+ l_int32 save_empty,
+ const char *rootpath,
+ const char *ext)
+{
+l_int32 i, totlines, nlines, index;
+size_t nbytes;
+l_uint8 *data;
+char *str;
+char outname[512];
+NUMA *na;
+SARRAY *sa;
+
+ PROCNAME("fileSplitLinesUniform");
+
+ if (!filename)
+ return ERROR_INT("filename not defined", procName, 1);
+ if (!rootpath)
+ return ERROR_INT("rootpath not defined", procName, 1);
+ if (n <= 0)
+ return ERROR_INT("n must be > 0", procName, 1);
+ if (save_empty != 0 && save_empty != 1)
+ return ERROR_INT("save_empty not 0 or 1", procName, 1);
+
+ /* Make sarray of lines; the newlines are stripped off */
+ if ((data = l_binaryRead(filename, &nbytes)) == NULL)
+ return ERROR_INT("data not read", procName, 1);
+ sa = sarrayCreateLinesFromString((const char *)data, save_empty);
+ LEPT_FREE(data);
+ if (!sa)
+ return ERROR_INT("sa not made", procName, 1);
+ totlines = sarrayGetCount(sa);
+ if (n > totlines) {
+ sarrayDestroy(&sa);
+ L_ERROR("num files = %d > num lines = %d\n", procName, n, totlines);
+ return 1;
+ }
+
+ /* Write n sets of lines to n files, adding the newlines back */
+ na = numaGetUniformBinSizes(totlines, n);
+ index = 0;
+ for (i = 0; i < n; i++) {
+ if (ext == NULL)
+ snprintf(outname, sizeof(outname), "%s_%d", rootpath, i);
+ else
+ snprintf(outname, sizeof(outname), "%s_%d%s", rootpath, i, ext);
+ numaGetIValue(na, i, &nlines);
+ str = sarrayToStringRange(sa, index, nlines, 1); /* add newlines */
+ l_binaryWrite(outname, "w", str, strlen(str));
+ LEPT_FREE(str);
+ index += nlines;
+ }
+ numaDestroy(&na);
+ sarrayDestroy(&sa);
+ return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Multi-platform functions for opening file streams *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fopenReadStream()
+ *
+ * \param[in] filename
+ * \return stream, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This should be used whenever you want to run fopen() to
+ * read from a stream. Never call fopen() directory.
+ * (2) This handles the temp directory pathname conversion on windows:
+ * /tmp ==> [Windows Temp directory]
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief fopenWriteStream()
+ *
+ * \param[in] filename
+ * \param[in] modestring
+ * \return stream, or NULL on error
+ *
+ * <pre>
+ * 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 handles the temp directory pathname conversion on windows:
+ * /tmp ==> [Windows Temp directory]
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief fopenReadFromMemory()
+ *
+ * \param[in] data, size
+ * \return file stream, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Work-around if fmemopen() not available.
+ * (2) Windows tmpfile() writes into the root C:\ directory, which
+ * requires admin privileges. This also works around that.
+ * </pre>
+ */
+FILE *
+fopenReadFromMemory(const l_uint8 *data,
+ size_t size)
+{
+FILE *fp;
+
+ PROCNAME("fopenReadFromMemory");
+
+ if (!data)
+ return (FILE *)ERROR_PTR("data not defined", procName, NULL);
+
+#if HAVE_FMEMOPEN
+ if ((fp = fmemopen((void *)data, size, "rb")) == NULL)
+ return (FILE *)ERROR_PTR("stream not opened", procName, NULL);
+#else /* write to tmp file */
+ L_INFO("work-around: writing to a temp file\n", procName);
+ #ifdef _WIN32
+ if ((fp = fopenWriteWinTempfile()) == NULL)
+ return (FILE *)ERROR_PTR("tmpfile stream not opened", procName, NULL);
+ #else
+ if ((fp = tmpfile()) == NULL)
+ return (FILE *)ERROR_PTR("tmpfile stream not opened", procName, NULL);
+ #endif /* _WIN32 */
+ fwrite(data, 1, size, fp);
+ rewind(fp);
+#endif /* HAVE_FMEMOPEN */
+
+ return fp;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Opening a windows tmpfile for writing *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief fopenWriteWinTempfile()
+ *
+ * \return file stream, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The Windows version of tmpfile() writes into the root
+ * C:\ directory, which requires admin privileges. This
+ * function provides an alternative implementation.
+ * </pre>
+ */
+FILE *
+fopenWriteWinTempfile(void)
+{
+#ifdef _WIN32
+l_int32 handle;
+FILE *fp;
+char *filename;
+
+ PROCNAME("fopenWriteWinTempfile");
+
+ if ((filename = l_makeTempFilename()) == NULL) {
+ L_ERROR("l_makeTempFilename failed, %s\n", procName, strerror(errno));
+ return NULL;
+ }
+
+ handle = _open(filename, _O_CREAT | _O_RDWR | _O_SHORT_LIVED |
+ _O_TEMPORARY | _O_BINARY, _S_IREAD | _S_IWRITE);
+ lept_free(filename);
+ if (handle == -1) {
+ L_ERROR("_open failed, %s\n", procName, strerror(errno));
+ return NULL;
+ }
+
+ if ((fp = _fdopen(handle, "r+b")) == NULL) {
+ L_ERROR("_fdopen failed, %s\n", procName, strerror(errno));
+ return NULL;
+ }
+
+ return fp;
+#else
+ return NULL;
+#endif /* _WIN32 */
+}
+
+
+/*--------------------------------------------------------------------*
+ * Multi-platform functions that avoid C-runtime boundary *
+ * crossing for applications with Windows 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.
+ */
+/*!
+ * \brief lept_fopen()
+ *
+ * \param[in] filename
+ * \param[in] mode same as for fopen(); e.g., "rb"
+ * \return stream or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This must be used by any application that passes
+ * a file handle to a leptonica Windows DLL.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief lept_fclose()
+ *
+ * \param[in] fp file stream
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This should be used by any application that accepts
+ * a file handle generated by a leptonica Windows DLL.
+ * </pre>
+ */
+l_ok
+lept_fclose(FILE *fp)
+{
+ PROCNAME("lept_fclose");
+
+ if (!fp)
+ return ERROR_INT("stream not defined", procName, 1);
+
+ return fclose(fp);
+}
+
+
+/*!
+ * \brief lept_calloc()
+ *
+ * \param[in] nmemb number of members
+ * \param[in] size of each member
+ * \return void ptr, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+void *
+lept_calloc(size_t nmemb,
+ size_t size)
+{
+ if (nmemb <= 0 || size <= 0)
+ return NULL;
+ return LEPT_CALLOC(nmemb, size);
+}
+
+
+/*!
+ * \brief lept_free()
+ *
+ * \param[in] ptr
+ *
+ * <pre>
+ * Notes:
+ * (1) This should be used by any application that accepts
+ * heap data allocated by a leptonica Windows DLL.
+ * </pre>
+ */
+void
+lept_free(void *ptr)
+{
+ if (!ptr) return;
+ LEPT_FREE(ptr);
+}
+
+
+/*--------------------------------------------------------------------*
+ * Multi-platform file system operations *
+ * [ These only write to /tmp or its subdirectories ] *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief lept_mkdir()
+ *
+ * \param[in] subdir of /tmp or its equivalent on Windows
+ * \return 0 on success, non-zero on failure
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+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 (!LeptDebugOK) {
+ L_INFO("making named temp subdirectory %s is disabled\n",
+ procName, subdir);
+ return 0;
+ }
+
+ 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
+ if (CreateDirectory(tmpdir, NULL) == 0)
+ ret += (GetLastError () != ERROR_ALREADY_EXISTS);
+#endif
+ LEPT_FREE(dir);
+ dir = tmpdir;
+ }
+ LEPT_FREE(dir);
+ sarrayDestroy(&sa);
+ if (ret > 0)
+ L_ERROR("failure to create %d directories\n", procName, ret);
+ return ret;
+}
+
+
+/*!
+ * \brief lept_rmdir()
+ *
+ * \param[in] subdir of /tmp or its equivalent on Windows
+ * \return 0 on success, non-zero on failure
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_int32
+lept_rmdir(const char *subdir)
+{
+char *dir, *fname, *fullname;
+l_int32 exists, ret, i, nfiles;
+SARRAY *sa;
+#ifdef _WIN32
+char *newpath;
+#else
+char *realdir;
+#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 */
+ dir = pathJoin("/tmp", subdir);
+ 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 in that directory */
+ 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
+ realdir = genPathname("/tmp", subdir);
+ ret = rmdir(realdir);
+ LEPT_FREE(realdir);
+#else
+ newpath = genPathname(dir, NULL);
+ ret = (RemoveDirectory(newpath) ? 0 : 1);
+ LEPT_FREE(newpath);
+#endif /* !_WIN32 */
+
+ sarrayDestroy(&sa);
+ LEPT_FREE(dir);
+ return ret;
+}
+
+
+/*!
+ * \brief lept_direxists()
+ *
+ * \param[in] dir
+ * \param[out] pexists 1 if it exists; 0 otherwise
+ * \return void
+ *
+ * <pre>
+ * 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)
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief lept_rm_match()
+ *
+ * \param[in] subdir [optional] if NULL, the removed files are in /tmp
+ * \param[in] substr [optional] pattern to match in filename
+ * \return 0 on success, non-zero on failure
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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, sizeof(tempdir), 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;
+}
+
+
+/*!
+ * \brief lept_rm()
+ *
+ * \param[in] subdir [optional] subdir of '/tmp'; can be NULL
+ * \param[in] tail filename without the directory
+ * \return 0 on success, non-zero on failure
+ *
+ * <pre>
+ * Notes:
+ * (1) By calling genPathname(), this does an automatic directory
+ * translation on windows to a path in the windows [Temp] directory:
+ * "/tmp/..." ==> [Temp]/... (windows)
+ * </pre>
+ */
+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, sizeof(newtemp), subdir))
+ return ERROR_INT("temp dirname not made", procName, 1);
+ path = genPathname(newtemp, tail);
+ ret = lept_rmfile(path);
+ LEPT_FREE(path);
+ return ret;
+}
+
+
+/*!
+ * \brief
+ *
+ * lept_rmfile()
+ *
+ * \param[in] filepath full path to file including the directory
+ * \return 0 on success, non-zero on failure
+ *
+ * <pre>
+ * Notes:
+ * (1) This removes the named file.
+ * (2) Use unix pathname separators.
+ * (3) There is no name translation.
+ * (4) 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief lept_mv()
+ *
+ * \param[in] srcfile
+ * \param[in] newdir [optional]; can be NULL
+ * \param[in] newtail [optional]; can be NULL
+ * \param[out] pnewpath [optional] of actual path; can be NULL
+ * \return 0 on success, non-zero on failure
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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, sizeof(newtemp), newdir) == 1)
+ return ERROR_INT("newdir not NULL or a subdir of /tmp", procName, 1);
+
+ /* Get canonical src pathname */
+ splitPathAtDirectory(srcfile, &dir, &srctail);
+
+#ifndef _WIN32
+ srcpath = pathJoin(dir, srctail);
+ LEPT_FREE(dir);
+
+ /* Generate output pathname */
+ if (!newtail || newtail[0] == '\0')
+ newpath = pathJoin(newtemp, srctail);
+ else
+ newpath = pathJoin(newtemp, newtail);
+ LEPT_FREE(srctail);
+
+ /* Overwrite any existing file at 'newpath' */
+ ret = fileCopy(srcpath, newpath);
+ if (!ret) { /* and remove srcfile */
+ char *realpath = genPathname(srcpath, NULL);
+ remove(realpath);
+ LEPT_FREE(realpath);
+ }
+#else
+ 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' */
+ ret = MoveFileEx(srcpath, newpath,
+ MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) ? 0 : 1;
+#endif /* ! _WIN32 */
+
+ LEPT_FREE(srcpath);
+ if (pnewpath)
+ *pnewpath = newpath;
+ else
+ LEPT_FREE(newpath);
+ return ret;
+}
+
+
+/*!
+ * \brief lept_cp()
+ *
+ * \param[in] srcfile
+ * \param[in] newdir [optional]; can be NULL
+ * \param[in] newtail [optional]; can be NULL
+ * \param[out] pnewpath [optional] of actual path; can be NULL
+ * \return 0 on success, non-zero on failure
+ *
+ * <pre>
+ * 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
+ *
+ * </pre>
+ */
+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, sizeof(newtemp), newdir) == 1)
+ return ERROR_INT("newdir not NULL or a subdir of /tmp", procName, 1);
+
+ /* Get canonical src pathname */
+ splitPathAtDirectory(srcfile, &dir, &srctail);
+
+#ifndef _WIN32
+ srcpath = pathJoin(dir, srctail);
+ LEPT_FREE(dir);
+
+ /* Generate output pathname */
+ if (!newtail || newtail[0] == '\0')
+ newpath = pathJoin(newtemp, srctail);
+ else
+ newpath = pathJoin(newtemp, newtail);
+ LEPT_FREE(srctail);
+
+ /* Overwrite any existing file at 'newpath' */
+ ret = fileCopy(srcpath, newpath);
+#else
+ 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' */
+ ret = CopyFile(srcpath, newpath, FALSE) ? 0 : 1;
+#endif /* !_WIN32 */
+
+ LEPT_FREE(srcpath);
+ if (pnewpath)
+ *pnewpath = newpath;
+ else
+ LEPT_FREE(newpath);
+ return ret;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Special debug/test function for calling 'system' *
+ *--------------------------------------------------------------------*/
+#if defined(__APPLE__)
+ #include "TargetConditionals.h"
+#endif /* __APPLE__ */
+
+/*!
+ * \brief callSystemDebug()
+ *
+ * \param[in] cmd command to be exec'd
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) The C library 'system' call is only made through this function.
+ * It only works in debug/test mode, where the global variable
+ * LeptDebugOK == TRUE. This variable is set to FALSE in the
+ * library as distributed, and calling this function will
+ * generate an error message.
+ * </pre>
+ */
+void
+callSystemDebug(const char *cmd)
+{
+l_int32 ret;
+
+ PROCNAME("callSystemDebug");
+
+ if (!cmd) {
+ L_ERROR("cmd not defined\n", procName);
+ return;
+ }
+ if (LeptDebugOK == FALSE) {
+ L_INFO("'system' calls are disabled\n", procName);
+ return;
+ }
+
+#if defined(__APPLE__) /* iOS 11 does not support system() */
+
+ #if TARGET_OS_OSX /* Mac OS X */
+ ret = system(cmd);
+ #elif TARGET_OS_IPHONE || defined(OS_IOS) /* iOS */
+ L_ERROR("iOS 11 does not support system()\n", procName);
+ #endif /* TARGET_OS_OSX */
+
+#else /* ! __APPLE__ */
+
+ ret = system(cmd);
+
+#endif /* __APPLE__ */
+}
+
+
+/*--------------------------------------------------------------------*
+ * General file name operations *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief splitPathAtDirectory()
+ *
+ * \param[in] pathname full path; can be a directory
+ * \param[out] pdir [optional] root directory name of
+ * input path, including trailing '/'
+ * \param[out] ptail [optional] 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
+ *
+ * <pre>
+ * 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.d --> dir: /usr/tmp/ tail: abc.d
+ * /usr/tmp/ --> dir: /usr/tmp/ tail: [empty string]
+ * /usr/tmp --> dir: /usr/ tail: tmp
+ * abc.d --> dir: [empty string] tail: abc.d
+ * (4 Consider the first example above: /usr/tmp/abc.d.
+ * Suppose you want the stem of the file, abc, without either
+ * the directory or the extension. This can be extracted in two steps:
+ * splitPathAtDirectory("usr/tmp/abc.d", NULL, &tail);
+ * [sets tail: "abc.d"]
+ * splitPathAtExtension(tail, &basename, NULL);
+ * [sets basename: "abc"]
+ * (5) 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).
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief splitPathAtExtension()
+ *
+ * \param[in] pathname full path; can be a directory
+ * \param[out] pbasename [optional] pathname not including the
+ * last dot and characters after that
+ * \param[out] pextension [optional] 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
+ *
+ * <pre>
+ * 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.
+ * (5) Note that basename, as used here, is different from the result
+ * of the unix program 'basename'. Here, basename is the entire
+ * pathname up to a final extension and its preceding dot.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief pathJoin()
+ *
+ * \param[in] dir [optional] can be null
+ * \param[in] fname [optional] can be null
+ * \return specially concatenated path, or NULL on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+char *
+pathJoin(const char *dir,
+ const char *fname)
+{
+const char *slash = "/";
+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 && strlen(dir) >= 2 && dir[0] == '.' && dir[1] == '.')
+ return (char *)ERROR_PTR("dir starts with '..'", procName, NULL);
+ if (fname && strlen(fname) >= 2 && fname[0] == '.' && fname[1] == '.')
+ 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;
+}
+
+
+/*!
+ * \brief appendSubdirs()
+ *
+ * \param[in] basedir
+ * \param[in] subdirs
+ * \return concatenated full directory path without trailing slash,
+ * or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Use unix pathname separators
+ * (2) Allocates a new string: [basedir]/[subdirs]
+ * </pre>
+ */
+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 + 8;
+ if ((newdir = (char *)LEPT_CALLOC(len3, 1)) == NULL)
+ return (char *)ERROR_PTR("newdir not made", procName, NULL);
+ stringCat(newdir, len3, basedir);
+ if (newdir[len1 - 1] != '/') /* add '/' if necessary */
+ newdir[len1] = '/';
+ if (subdirs[0] == '/') /* add subdirs, stripping leading '/' */
+ stringCat(newdir, len3, subdirs + 1);
+ else
+ stringCat(newdir, len3, subdirs);
+ len4 = strlen(newdir);
+ if (newdir[len4 - 1] == '/') /* strip trailing '/' */
+ newdir[len4 - 1] = '\0';
+
+ return newdir;
+}
+
+
+/*--------------------------------------------------------------------*
+ * Special file name operations *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief convertSepCharsInPath()
+ *
+ * \param[in] path
+ * \param[in] type UNIX_PATH_SEPCHAR, WIN_PATH_SEPCHAR
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * (4) The backslash is a valid character in unix pathnames and should
+ * not be converted. Each backslash needs to be escaped with a
+ * preceding backslash for the shell, but the actual filename
+ * does not include these escape characters.
+ * </pre>
+ */
+l_ok
+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) {
+#ifdef _WIN32 /* only convert on windows */
+ for (i = 0; i < len; i++) {
+ if (path[i] == '\\')
+ path[i] = '/';
+ }
+#endif /* _WIN32 */
+ } else { /* WIN_PATH_SEPCHAR */
+ for (i = 0; i < len; i++) {
+ if (path[i] == '/')
+ path[i] = '\\';
+ }
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief genPathname()
+ *
+ * \param[in] dir [optional] directory or full path name,
+ * with or without the trailing '/'
+ * \param[in] fname [optional] file name within a directory
+ * \return pathname either a directory or full path, or NULL on error
+ *
+ * <pre>
+ * 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) On windows, if the root of %dir is '/tmp', this does a name
+ * translation:
+ * "/tmp" ==> [Temp] (windows)
+ * where [Temp] is the windows temp directory.
+ * (3) On unix, the TMPDIR variable is ignored. No rewriting
+ * of temp directories is permitted.
+ * (4) 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
+ * (5) In all cases, the resulting pathname is not terminated with a slash
+ * (6) The caller is responsible for freeing the returned pathname.
+ * </pre>
+ */
+char *
+genPathname(const char *dir,
+ const char *fname)
+{
+l_int32 is_win32 = FALSE;
+char *cdir, *pathout;
+l_int32 dirlen, namelen;
+size_t 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) {
+ LEPT_FREE(cdir);
+ return (char *)ERROR_PTR("pathout not made", procName, NULL);
+ }
+
+#ifdef _WIN32
+ is_win32 = TRUE;
+#endif /* _WIN32 */
+
+ /* First handle %dir (which may be a full pathname).
+ * There is no path rewriting on unix, and on win32, we do not
+ * rewrite unless the specified directory is /tmp or
+ * a subdirectory of /tmp */
+ if (!is_win32 || dirlen < 4 ||
+ (dirlen == 4 && strncmp(cdir, "/tmp", 4) != 0) || /* not in "/tmp" */
+ (dirlen > 4 && strncmp(cdir, "/tmp/", 5) != 0)) { /* not in "/tmp/" */
+ stringCopy(pathout, cdir, dirlen);
+ } else { /* Rewrite for win32 with "/tmp" specified for the directory. */
+#ifdef _WIN32
+ l_int32 tmpdirlen;
+ char tmpdir[MAX_PATH];
+ GetTempPath(sizeof(tmpdir), tmpdir); /* get the windows temp dir */
+ tmpdirlen = strlen(tmpdir);
+ if (tmpdirlen > 0 && tmpdir[tmpdirlen - 1] == '\\') {
+ tmpdir[tmpdirlen - 1] = '\0'; /* trim the trailing '\' */
+ }
+ tmpdirlen = strlen(tmpdir);
+ stringCopy(pathout, tmpdir, tmpdirlen);
+
+ /* Add the rest of cdir */
+ if (dirlen > 4)
+ stringCat(pathout, size, cdir + 4);
+#endif /* _WIN32 */
+ }
+
+ /* Now handle %fname */
+ if (fname && strlen(fname) > 0) {
+ dirlen = strlen(pathout);
+ pathout[dirlen] = '/';
+ stringCat(pathout, size, fname);
+ }
+
+ LEPT_FREE(cdir);
+ return pathout;
+}
+
+
+/*!
+ * \brief makeTempDirname()
+ *
+ * \param[in] result preallocated on stack or heap and passed in
+ * \param[in] nbytes size of %result array, in bytes
+ * \param[in] subdir [optional]; can be NULL or an empty string
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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/%subdir (unix)
+ * [Temp]/%subdir (windows, mac, ios)
+ * where [Temp] is a path determined
+ * - on windows, mac: by GetTempPath()
+ * - on ios: by confstr() (see man page)
+ * and %subdir is in general a set of nested subdirectories:
+ * dir1/dir2/.../dirN
+ * which in use would not typically exceed 2 levels.
+ * (3) Usage example:
+ * \code
+ * char result[256];
+ * makeTempDirname(result, sizeof(result), "lept/golden");
+ * \endcode
+ * </pre>
+ */
+l_ok
+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);
+
+#ifdef OS_IOS
+ {
+ size_t n = confstr(_CS_DARWIN_USER_TEMP_DIR, result, nbytes);
+ if (n == 0) {
+ L_ERROR("failed to find tmp dir, %s\n", procName, strerror(errno));
+ return 1;
+ } else if (n > nbytes) {
+ return ERROR_INT("result array too small for path\n", procName, 1);
+ }
+ dir = pathJoin(result, subdir);
+ }
+#else
+ dir = pathJoin("/tmp", subdir);
+#endif /* ~ OS_IOS */
+
+#ifndef _WIN32
+ path = stringNew(dir);
+#else
+ path = genPathname(dir, NULL);
+#endif /* ~ _WIN32 */
+ pathlen = strlen(path);
+ if (pathlen < nbytes - 1) {
+ stringCat(result, nbytes, path);
+ } else {
+ L_ERROR("result array too small for path\n", procName);
+ ret = 1;
+ }
+
+ LEPT_FREE(dir);
+ LEPT_FREE(path);
+ return ret;
+}
+
+
+/*!
+ * \brief modifyTrailingSlash()
+ *
+ * \param[in] path preallocated on stack or heap and passed in
+ * \param[in] nbytes size of %path array, in bytes
+ * \param[in] flag L_ADD_TRAIL_SLASH or L_REMOVE_TRAIL_SLASH
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This carries out the requested action if necessary.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief l_makeTempFilename()
+ *
+ * \return fname : heap allocated filename; returns NULL on failure.
+ *
+ * <pre>
+ * Notes:
+ * (1) On unix, this makes a filename of the form
+ * "/tmp/lept.XXXXXX",
+ * where each X is a random character.
+ * (2) On windows, this makes a filename of the form
+ * "/[Temp]/lp.XXXXXX".
+ * (3) On all systems, this fails if the file is not writable.
+ * (4) Safest usage is to write to a subdirectory in debug code.
+ * (5) The returned filename must be freed by the caller, using lept_free.
+ * (6) The tail of the filename has a '.', so that cygwin interprets
+ * the file as having an extension. Otherwise, cygwin assumes it
+ * is an executable and appends ".exe" to the filename.
+ * (7) On unix, whenever possible use tmpfile() instead. tmpfile()
+ * hides the file name, returns a stream opened for write,
+ * and deletes the temp file when the stream is closed.
+ * </pre>
+ */
+char *
+l_makeTempFilename(void)
+{
+char dirname[240];
+
+ PROCNAME("l_makeTempFilename");
+
+ if (makeTempDirname(dirname, sizeof(dirname), NULL) == 1)
+ return (char *)ERROR_PTR("failed to make dirname", procName, NULL);
+
+#ifndef _WIN32
+{
+ char *pattern;
+ l_int32 fd;
+ pattern = stringConcatNew(dirname, "/lept.XXXXXX", NULL);
+ fd = mkstemp(pattern);
+ if (fd == -1) {
+ LEPT_FREE(pattern);
+ return (char *)ERROR_PTR("mkstemp failed", procName, NULL);
+ }
+ close(fd);
+ return pattern;
+}
+#else
+{
+ char fname[MAX_PATH];
+ FILE *fp;
+ if (GetTempFileName(dirname, "lp.", 0, fname) == 0)
+ return (char *)ERROR_PTR("GetTempFileName failed", procName, NULL);
+ if ((fp = fopen(fname, "wb")) == NULL)
+ return (char *)ERROR_PTR("file cannot be written to", procName, NULL);
+ fclose(fp);
+ return stringNew(fname);
+}
+#endif /* ~ _WIN32 */
+}
+
+
+/*!
+ * \brief extractNumberFromFilename()
+ *
+ * \param[in] fname
+ * \param[in] numpre number of characters before the digits to be found
+ * \param[in] numpost number of characters after the digits to be found
+ * \return num number embedded in the filename; -1 on error or if
+ * not found
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+}
diff --git a/leptonica/src/warper.c b/leptonica/src/warper.c
new file mode 100644
index 00000000..0f0b79eb
--- /dev/null
+++ b/leptonica/src/warper.c
@@ -0,0 +1,1394 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file warper.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 DefaultRedWeight = 0.0f;
+static const l_float32 DefaultGreenWeight = 0.7f;
+static const l_float32 DefaultBlueWeight = 0.3f;
+
+
+/*----------------------------------------------------------------------*
+ * High-level example captcha interface *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixSimpleCaptcha()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] border added white pixels on each side
+ * \param[in] nterms number of x and y harmonic terms
+ * \param[in] seed of random number generator
+ * \param[in] color for colorizing; in 0xrrggbb00 format; use 0 for black
+ * \param[in] cmapflag 1 for colormap output; 0 for rgb
+ * \return pixd 8 bpp cmap or 32 bpp rgb, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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.0f, 5.0f, 4.0f, 3.0f};
+l_float32 ymag[] = {10.0f, 8.0f, 6.0f, 5.0f};
+l_float32 xfreq[] = {0.12f, 0.10f, 0.10f, 0.11f};
+l_float32 yfreq[] = {0.15f, 0.13f, 0.13f, 0.11f};
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixRandomHarmonicWarp()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] xmag, ymag maximum magnitude of x and y distortion
+ * \param[in] xfreq, yfreq maximum magnitude of x and y frequency
+ * \param[in] nx, ny number of x and y harmonic terms
+ * \param[in] seed of random number generator
+ * \param[in] grayval color brought in from the outside;
+ * 0 for black, 255 for white
+ * \return pixd 8 bpp; no colormap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief 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);
+
+/*!
+ * \brief pixRandomHarmonicWarpLUT()
+ *
+ * \param[in] pixs 8 bpp; no colormap
+ * \param[in] xmag, ymag maximum magnitude of x and y distortion
+ * \param[in] xfreq, yfreq maximum magnitude of x and y frequency
+ * \param[in] nx, ny number of x and y harmonic terms
+ * \param[in] seed of random number generator
+ * \param[in] grayval color brought in from the outside;
+ * 0 for black, 255 for white
+ * \return pixd 8 bpp; no colormap, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief 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 *
+ *---------------------------------------------------------------------------*/
+/*!
+ * \brief pixWarpStereoscopic()
+ *
+ * \param[in] pixs any depth, colormap ok
+ * \param[in] 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
+ * \param[in] 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)
+ * \param[in] 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)
+ * \param[in] ybendt multiplicative parameter for in-plane vertical
+ * displacement at the left or right edge at the top:
+ * y = ybendt * (2x/w - 1)^2
+ * \param[in] ybendb same as ybendt, except at the left or right edge
+ * at the bottom
+ * \param[in] redleft 1 if the red filter is on the left; 0 otherwise
+ * \return pixd 32 bpp, or NULL on error
+ *
+ * <pre>
+ * 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);
+ * </pre>
+ */
+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 *pix1, *pix2, *pix3, *pix4, *pixr, *pixg, *pixb;
+PIX *pixv1, *pixv2, *pixv3, *pixv4;
+PIX *pixrs, *pixrss;
+PIX *pixd;
+
+ PROCNAME("pixWarpStereoscopic");
+
+ if (!pixs)
+ return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+ /* Convert to the output depth, 32 bpp. */
+ pix1 = 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(pix1, boxleft, NULL);
+ pixv2 = pixClipRectangle(pix1, 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);
+ pix2 = pixCreate(w, h, 32);
+ pixRasterop(pix2, 0, 0, w / 2, h, PIX_SRC, pixv3, 0, 0);
+ pixRasterop(pix2, w / 2, 0, w - w / 2, h, PIX_SRC, pixv4, 0, 0);
+ pixDestroy(&pixv1);
+ pixDestroy(&pixv2);
+ pixDestroy(&pixv3);
+ pixDestroy(&pixv4);
+ } else {
+ pix2 = pixClone(pix1);
+ }
+ pixDestroy(&pix1);
+
+ /* Split out the 3 components */
+ pixr = pixGetRGBComponent(pix2, COLOR_RED);
+ pixg = pixGetRGBComponent(pix2, COLOR_GREEN);
+ pixb = pixGetRGBComponent(pix2, COLOR_BLUE);
+ pixDestroy(&pix2);
+
+ /* 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 {
+ pix1 = pixClipRectangle(pixr, boxleft, NULL);
+ pix2 = pixClipRectangle(pixr, boxright, NULL);
+ pix3 = pixStretchHorizontal(pix1, L_WARP_TO_LEFT, L_QUADRATIC_WARP,
+ zbend, L_INTERPOLATED, L_BRING_IN_WHITE);
+ pix4 = pixStretchHorizontal(pix2, 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, pix3, 0, 0);
+ pixRasterop(pixrs, w / 2, 0, w - w / 2, h, PIX_SRC, pix4, 0, 0);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ pixDestroy(&pix3);
+ pixDestroy(&pix4);
+ }
+
+ /* 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_MAX(1.0, (l_float32)pixGetHeight(pixrs));
+ zshift = (zshiftt + zshiftb) / 2;
+ pix1 = pixTranslate(NULL, pixrs, zshift, 0, L_BRING_IN_WHITE);
+ pixrss = pixHShearLI(pix1, h / 2, angle, L_BRING_IN_WHITE);
+ pixDestroy(&pix1);
+ }
+
+ /* 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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixStretchHorizontal()
+ *
+ * \param[in] pixs 1, 8 or 32 bpp
+ * \param[in] dir L_WARP_TO_LEFT or L_WARP_TO_RIGHT
+ * \param[in] type L_LINEAR_WARP or L_QUADRATIC_WARP
+ * \param[in] hmax horizontal displacement at edge
+ * \param[in] operation L_SAMPLED or L_INTERPOLATED
+ * \param[in] incolor L_BRING_IN_WHITE or L_BRING_IN_BLACK
+ * \return pixd stretched/compressed, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixStretchHorizontalSampled()
+ *
+ * \param[in] pixs 1, 8 or 32 bpp
+ * \param[in] dir L_WARP_TO_LEFT or L_WARP_TO_RIGHT
+ * \param[in] type L_LINEAR_WARP or L_QUADRATIC_WARP
+ * \param[in] hmax horizontal displacement at edge
+ * \param[in] incolor L_BRING_IN_WHITE or L_BRING_IN_BLACK
+ * \return pixd stretched/compressed, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixStretchHorizontal() for details.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixStretchHorizontalLI()
+ *
+ * \param[in] pixs 1, 8 or 32 bpp
+ * \param[in] dir L_WARP_TO_LEFT or L_WARP_TO_RIGHT
+ * \param[in] type L_LINEAR_WARP or L_QUADRATIC_WARP
+ * \param[in] hmax horizontal displacement at edge
+ * \param[in] incolor L_BRING_IN_WHITE or L_BRING_IN_BLACK
+ * \return pixd stretched/compressed, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixStretchHorizontal() for details.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixQuadraticVShear()
+ *
+ * \param[in] pixs 1, 8 or 32 bpp
+ * \param[in] dir L_WARP_TO_LEFT or L_WARP_TO_RIGHT
+ * \param[in] vmaxt max vertical displacement at edge and at top
+ * \param[in] vmaxb max vertical displacement at edge and at bottom
+ * \param[in] operation L_SAMPLED or L_INTERPOLATED
+ * \param[in] incolor L_BRING_IN_WHITE or L_BRING_IN_BLACK
+ * \return pixd stretched, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*!
+ * \brief pixQuadraticVShearSampled()
+ *
+ * \param[in] pixs 1, 8 or 32 bpp
+ * \param[in] dir L_WARP_TO_LEFT or L_WARP_TO_RIGHT
+ * \param[in] vmaxt max vertical displacement at edge and at top
+ * \param[in] vmaxb max vertical displacement at edge and at bottom
+ * \param[in] incolor L_BRING_IN_WHITE or L_BRING_IN_BLACK
+ * \return pixd stretched, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixQuadraticVShear() for details.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixQuadraticVShearLI()
+ *
+ * \param[in] pixs 8 or 32 bpp, or colormapped
+ * \param[in] dir L_WARP_TO_LEFT or L_WARP_TO_RIGHT
+ * \param[in] vmaxt max vertical displacement at edge and at top
+ * \param[in] vmaxb max vertical displacement at edge and at bottom
+ * \param[in] incolor L_BRING_IN_WHITE or L_BRING_IN_BLACK
+ * \return pixd stretched, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixQuadraticVShear() for details.
+ * </pre>
+ */
+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 *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief pixStereoFromPair()
+ *
+ * \param[in] pix1 32 bpp rgb
+ * \param[in] pix2 32 bpp rgb
+ * \param[in] 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
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 = DefaultRedWeight;
+ gwt = DefaultGreenWeight;
+ bwt = DefaultBlueWeight;
+ }
+ 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/leptonica/src/watershed.c b/leptonica/src/watershed.c
new file mode 100644
index 00000000..cbc4b2a7
--- /dev/null
+++ b/leptonica/src/watershed.c
@@ -0,0 +1,1126 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file watershed.c
+ * <pre>
+ *
+ * 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 labeled
+ * with the index value of its parent.
+ * (3) Each pixel is also labeled, 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 labeled 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);
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#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 */
+
+/*! New pixel coordinates */
+struct L_NewPixel
+{
+ l_int32 x; /*!< x coordinate */
+ l_int32 y; /*!< y coordinate */
+};
+typedef struct L_NewPixel L_NEWPIXEL;
+
+/*! Wartshed pixel */
+struct L_WSPixel
+{
+ l_float32 val; /*!< pixel value */
+ l_int32 x; /*!< x coordinate */
+ l_int32 y; /*!< y coordinate */
+ 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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief wshedCreate()
+ *
+ * \param[in] pixs 8 bpp source
+ * \param[in] pixm 1 bpp 'marker' seed
+ * \param[in] mindepth minimum depth; anything less is not saved
+ * \param[in] debugflag 1 for debug output
+ * \return WShed, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief wshedDestroy()
+ *
+ * \param[in,out] pwshed 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;
+}
+
+
+/*!
+ * \brief wshedApply()
+ *
+ * \param[in] wshed generated from wshedCreate()
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) N.B. 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.
+ * </pre>
+ */
+l_ok
+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);
+/* lept_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) {
+ lept_stderr("clabel,hlabel = %d,%d\n", clabel, hlabel);
+ lept_stderr("hmin = %d, hmax = %d\n", hmin, hmax);
+ lept_stderr("cindex,hindex = %d,%d\n", cindex, hindex);
+ if (hmin < mindepth)
+ lept_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) lept_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 *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief wshedSaveBasin()
+ *
+ * \param[in] wshed
+ * \param[in] index index of basin to be located
+ * \param[in] level filling level reached at the time this function
+ * is called
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ }
+}
+
+
+/*!
+ * \brief identifyWatershedBasin()
+ *
+ * \param[in] wshed
+ * \param[in] index index of basin to be located
+ * \param[in] level of basin at point at which the two basins met
+ * \param[out] pbox bounding box of basin
+ * \param[out] ppixd pix of basin, cropped to its bounding box
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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) lept_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;
+}
+
+
+/*!
+ * \brief mergeLookup()
+ *
+ * \param[in] wshed
+ * \param[in] sindex primary index being changed in the merge
+ * \param[in] dindex index that %sindex will point to after the merge
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief wshedGetHeight()
+ *
+ * \param[in] wshed array of current indices
+ * \param[in] val value of current pixel popped off queue
+ * \param[in] label of pixel or 32 bpp label image
+ * \param[out] pheight height of current value from seed
+ * or minimum of watershed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * </pre>
+ */
+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;
+}
+
+
+/*
+ * \brief pushNewPixel()
+ *
+ * \param[in] lqueue
+ * \param[in] x, y pixel coordinates
+ * \param[out] pminx, pmaxx, pminy, pmaxy bounding box update
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+}
+
+
+/*
+ * \brief popNewPixel()
+ *
+ * \param[in] lqueue
+ * \param[out] px, py pixel coordinates
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+}
+
+
+/*
+ * \brief pushWSPixel()
+ *
+ * \param[in] lh priority queue
+ * \param[in] stack of reusable WSPixels
+ * \param[in] val pixel value: used for ordering the heap
+ * \param[in] x, y pixel coordinates
+ * \param[in] index label for set to which pixel belongs
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a wrapper for adding a WSPixel to a heap. It
+ * uses the storage stack to retrieve a WSPixel.
+ * </pre>
+ */
+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);
+}
+
+
+/*
+ * \brief popWSPixel()
+ *
+ * \param[in] lh priority queue
+ * \param[in] stack of reusable WSPixels
+ * \param[out] pval pixel value
+ * \param[out] px, py pixel coordinates
+ * \param[out] pindex label for set to which pixel belongs
+ * \return void
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 */
+}
+
+
+static void
+debugPrintLUT(l_int32 *lut,
+ l_int32 size,
+ l_int32 debug)
+{
+l_int32 i;
+
+ if (!debug) return;
+ lept_stderr("lut: ");
+ for (i = 0; i < size; i++)
+ lept_stderr( "%d ", lut[i]);
+ lept_stderr("\n");
+}
+
+
+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;
+ lept_stderr("%s:\n", descr);
+ lept_stderr(" (x, y) = (%d, %d)\n", x, y);
+ lept_stderr(" clabel = %d, cindex = %d\n", label, index);
+}
+
+
+/*-----------------------------------------------------------------------*
+ * Output *
+ *-----------------------------------------------------------------------*/
+/*!
+ * \brief wshedBasins()
+ *
+ * \param[in] wshed
+ * \param[out] ppixa [optional] mask of watershed basins
+ * \param[out] pnalevels [optional] watershed levels
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief wshedRenderFill()
+ *
+ * \param[in] 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;
+}
+
+
+/*!
+ * \brief wshedRenderColors()
+ *
+ * \param[in] 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/leptonica/src/watershed.h b/leptonica/src/watershed.h
new file mode 100644
index 00000000..d6b20775
--- /dev/null
+++ b/leptonica/src/watershed.h
@@ -0,0 +1,64 @@
+/*====================================================================*
+ - 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
+
+/*!
+ * \file watershed.h
+ *
+ * Simple data structure to hold watershed data.
+ * All data here is owned by the L_WShed and must be freed.
+ */
+
+/*! Simple data structure to hold watershed data. */
+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/leptonica/src/webpanimio.c b/leptonica/src/webpanimio.c
new file mode 100644
index 00000000..c47b5643
--- /dev/null
+++ b/leptonica/src/webpanimio.c
@@ -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.
+ *====================================================================*/
+
+/*!
+ * \file webpanimio.c
+ * <pre>
+ *
+ * Writing animated WebP
+ * l_int32 pixaWriteWebPAnim()
+ * l_int32 pixaWriteStreamWebPAnim()
+ * l_int32 pixaWriteMemWebPAnim()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* -----------------------------------------------*/
+#if HAVE_LIBWEBP_ANIM /* defined in environ.h */
+/* -----------------------------------------------*/
+#include "webp/decode.h"
+#include "webp/encode.h"
+#include "webp/mux.h"
+#include "webp/demux.h"
+
+/*---------------------------------------------------------------------*
+ * Writing animated WebP *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaWriteWebPAnim()
+ *
+ * \param[in] filename
+ * \param[in] pixa with images of all depths; cmap OK
+ * \param[in] loopcount [0 for infinite]
+ * \param[in] duration in ms, for each image
+ * \param[in] quality 0 - 100 for lossy; default ~80
+ * \param[in] lossless use 1 for lossless; 0 for lossy
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Special top-level function allowing specification of quality.
+ * </pre>
+ */
+l_ok
+pixaWriteWebPAnim(const char *filename,
+ PIXA *pixa,
+ l_int32 loopcount,
+ l_int32 duration,
+ l_int32 quality,
+ l_int32 lossless)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("pixaWriteWebPAnim");
+
+ 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);
+ ret = pixaWriteStreamWebPAnim(fp, pixa, loopcount, duration,
+ quality, lossless);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("pixs not compressed to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixaWriteStreamWebPAnim()
+ *
+ * \param[in] fp file stream
+ * \param[in] pixa with images of all depths; cmap OK
+ * \param[in] loopcount [0 for infinite]
+ * \param[in] duration in ms, for each image
+ * \param[in] quality 0 - 100 for lossy; default ~80
+ * \param[in] lossless use 1 for lossless; 0 for lossy
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+pixaWriteStreamWebPAnim(FILE *fp,
+ PIXA *pixa,
+ l_int32 loopcount,
+ l_int32 duration,
+ l_int32 quality,
+ l_int32 lossless)
+{
+l_uint8 *filedata;
+size_t filebytes, nbytes;
+
+ PROCNAME("pixaWriteStreamWebpAnim");
+
+ if (!fp)
+ return ERROR_INT("stream not open", procName, 1);
+ if (!pixa)
+ return ERROR_INT("pixa not defined", procName, 1);
+
+ filedata = NULL;
+ pixaWriteMemWebPAnim(&filedata, &filebytes, pixa, loopcount,
+ duration, quality, lossless);
+ rewind(fp);
+ if (!filedata)
+ return ERROR_INT("filedata not made", procName, 1);
+ nbytes = fwrite(filedata, 1, filebytes, fp);
+ free(filedata);
+ if (nbytes != filebytes)
+ return ERROR_INT("Write error", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixaWriteMemWebPAnim()
+ *
+ * \param[out] pencdata webp encoded data of pixs
+ * \param[out] pencsize size of webp encoded data
+ * \param[in] pixa with images of any depth, cmapped OK
+ * \param[in] loopcount [0 for infinite]
+ * \param[in] duration in ms, for each image
+ * \param[in] quality 0 - 100 for lossy; default ~80
+ * \param[in] lossless use 1 for lossless; 0 for lossy
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See pixWriteMemWebP() for details of webp encoding of images.
+ * </pre>
+ */
+l_ok
+pixaWriteMemWebPAnim(l_uint8 **pencdata,
+ size_t *pencsize,
+ PIXA *pixa,
+ l_int32 loopcount,
+ l_int32 duration,
+ l_int32 quality,
+ l_int32 lossless)
+{
+l_int32 i, n, same, w, h, wpl, ret;
+l_uint8 *data;
+PIX *pix1, *pix2;
+WebPAnimEncoder *enc;
+WebPAnimEncoderOptions enc_options;
+WebPConfig config;
+WebPData webp_data;
+WebPMux *mux = NULL;
+WebPMuxAnimParams newparams;
+WebPPicture frame;
+
+ PROCNAME("pixaWriteMemWebPAnim");
+
+ 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 (!pixa)
+ return ERROR_INT("&pixa not defined", procName, 1);
+ if ((n = pixaGetCount(pixa)) == 0)
+ return ERROR_INT("no images in pixa", procName, 1);
+ if (loopcount < 0) loopcount = 0;
+ if (lossless == 0 && (quality < 0 || quality > 100))
+ return ERROR_INT("quality not in [0 ... 100]", procName, 1);
+
+ pixaVerifyDimensions(pixa, &same, &w, &h);
+ if (!same)
+ return ERROR_INT("sizes of all pix are not the same", procName, 1);
+
+ /* Set up the encoder */
+ WebPAnimEncoderOptionsInit(&enc_options);
+ enc = WebPAnimEncoderNew(w, h, &enc_options);
+
+ for (i = 0; i < n; i++) {
+ /* Make a frame for each image. Convert the pix to RGBA with
+ * an opaque alpha layer, and put the raster data in the frame. */
+ pix1 = pixaGetPix(pixa, i, L_CLONE);
+ pix2 = pixConvertTo32(pix1);
+ pixSetComponentArbitrary(pix2, L_ALPHA_CHANNEL, 255);
+ pixEndianByteSwap(pix2);
+ data = (l_uint8 *)pixGetData(pix2);
+ wpl = pixGetWpl(pix2);
+ WebPPictureInit(&frame);
+ frame.width = w;
+ frame.height = h;
+ WebPPictureImportRGBA(&frame, data, 4 * wpl);
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ /* Add the frame data to the encoder, and clear its memory */
+ WebPConfigInit(&config);
+ config.lossless = lossless;
+ config.quality = quality;
+ WebPAnimEncoderAdd(enc, &frame, duration * i, &config);
+ WebPPictureFree(&frame);
+ }
+ WebPAnimEncoderAdd(enc, NULL, duration * i, NULL); /* add a blank frame */
+ WebPAnimEncoderAssemble(enc, &webp_data); /* encode the data */
+ WebPAnimEncoderDelete(enc);
+
+ /* Set the loopcount if requested. Note that when you make a mux,
+ * it imports the webp_data that was previously made, including
+ * the webp encoded images. Before you re-export that data using
+ * WebPMuxAssemble(), free the heap data in webp_data. There is an
+ * example for setting the loop count in the webp distribution;
+ * see gif2webp.c. */
+ if (loopcount > 0) {
+ mux = WebPMuxCreate(&webp_data, 1);
+ if (!mux) {
+ L_ERROR("could not re-mux to add loop count\n", procName);
+ } else {
+ ret = WebPMuxGetAnimationParams(mux, &newparams);
+ if (ret != WEBP_MUX_OK) {
+ L_ERROR("failed to get loop count\n", procName);
+ } else {
+ newparams.loop_count = loopcount;
+ ret = WebPMuxSetAnimationParams(mux, &newparams);
+ if (ret != WEBP_MUX_OK)
+ L_ERROR("failed to set loop count\n", procName);
+ }
+ WebPDataClear(&webp_data);
+ WebPMuxAssemble(mux, &webp_data);
+ WebPMuxDelete(mux);
+ }
+ }
+
+ *pencdata = (l_uint8 *)webp_data.bytes;
+ *pencsize = webp_data.size;
+ L_INFO("data size = %zu\n", procName, webp_data.size);
+ return 0;
+}
+
+
+/* --------------------------------------------*/
+#endif /* HAVE_LIBWEBP_ANIM */
+/* --------------------------------------------*/
diff --git a/leptonica/src/webpanimiostub.c b/leptonica/src/webpanimiostub.c
new file mode 100644
index 00000000..b186196f
--- /dev/null
+++ b/leptonica/src/webpanimiostub.c
@@ -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.
+ *====================================================================*/
+
+/*!
+ * \file webpanimiostub.c
+ * <pre>
+ *
+ * Stubs for webpanimio.c functions
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* -----------------------------------------------*/
+#if !HAVE_LIBWEBP_ANIM /* defined in environ.h */
+/* -----------------------------------------------*/
+
+l_ok pixaWriteWebPAnim(const char *filename, PIXA *pixa, l_int32 loopcount,
+ l_int32 duration, l_int32 quality, l_int32 lossless)
+{
+ return ERROR_INT("function not present", "pixaWriteWebPAnim", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixaWriteStreamWebPAnim(FILE *fp, PIXA *pixa, l_int32 loopcount,
+ l_int32 duration, l_int32 quality,
+ l_int32 lossless)
+{
+ return ERROR_INT("function not present", "pixaWriteStreamWebPAnim", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixaWriteMemWebPAnim(l_uint8 **pencdata, size_t *pencsize, PIXA *pixa,
+ l_int32 loopcount, l_int32 duration,
+ l_int32 quality, l_int32 lossless)
+{
+ return ERROR_INT("function not present", "pixaWriteMemWebPAnim", 1);
+}
+
+/* --------------------------------------------*/
+#endif /* !HAVE_LIBWEBP_ANIM */
+/* --------------------------------------------*/
diff --git a/leptonica/src/webpio.c b/leptonica/src/webpio.c
new file mode 100644
index 00000000..db7f8b3e
--- /dev/null
+++ b/leptonica/src/webpio.c
@@ -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.
+ *====================================================================*/
+
+/*!
+ * \file webpio.c
+ * <pre>
+ *
+ * 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()
+ * </pre>
+ */
+
+#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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixReadStreamWebP()
+ *
+ * \param[in] fp file 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;
+}
+
+
+/*!
+ * \brief pixReadMemWebP()
+ *
+ * \param[in] filedata webp compressed data in memory
+ * \param[in] filesize number of bytes in data
+ * \return pix 32 bpp, or NULL on error
+ *
+ * <pre>
+ * 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().
+ * </pre>
+ */
+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 = (size_t)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);
+ }
+
+ /* The WebP API expects data in RGBA order. The pix stores
+ * in host-dependent order with R as the MSB and A as the LSB.
+ * On little-endian machines, the bytes in the word must
+ * be swapped; e.g., R goes from byte 0 (LSB) to byte 3 (MSB).
+ * No swapping is necessary for big-endians. */
+ pixEndianByteSwap(pix);
+ return pix;
+}
+
+
+/*!
+ * \brief readHeaderWebP()
+ *
+ * \param[in] filename
+ * \param[out] pw width
+ * \param[out] ph height
+ * \param[out] pspp spp (3 or 4)
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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(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);
+}
+
+
+/*!
+ * \brief readHeaderMemWebP()
+ *
+ * \param[in] data
+ * \param[in] size 100 bytes is sufficient
+ * \param[out] pw width
+ * \param[out] ph height
+ * \param[out] pspp spp (3 or 4)
+ * \return 0 if OK, 1 on error
+ */
+l_ok
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixWriteWebP()
+ *
+ * \param[in] filename
+ * \param[in] pixs
+ * \param[in] quality 0 - 100; default ~80
+ * \param[in] lossless use 1 for lossless; 0 for lossy
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Special top-level function allowing specification of quality.
+ * </pre>
+ */
+l_ok
+pixWriteWebP(const char *filename,
+ PIX *pixs,
+ l_int32 quality,
+ l_int32 lossless)
+{
+l_int32 ret;
+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);
+ ret = pixWriteStreamWebP(fp, pixs, quality, lossless);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("pixs not compressed to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixWriteStreampWebP()
+ *
+ * \param[in] fp file stream
+ * \param[in] pixs all depths
+ * \param[in] quality 0 - 100; default ~80
+ * \param[in] lossless use 1 for lossless; 0 for lossy
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+
+ pixSetPadBits(pixs, 0);
+ 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;
+}
+
+
+/*!
+ * \brief pixWriteMemWebP()
+ *
+ * \param[out] pencdata webp encoded data of pixs
+ * \param[out] pencsize size of webp encoded data
+ * \param[in] pixs any depth, cmapped OK
+ * \param[in] quality 0 - 100; default ~80
+ * \param[in] lossless use 1 for lossless; 0 for lossy
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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);
+
+ /* The WebP API expects data in RGBA order. The pix stores
+ * in host-dependent order with R as the MSB and A as the LSB.
+ * On little-endian machines, the bytes in the word must
+ * be swapped; e.g., R goes from byte 0 (LSB) to byte 3 (MSB).
+ * No swapping is necessary for big-endians. */
+ 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/leptonica/src/webpiostub.c b/leptonica/src/webpiostub.c
new file mode 100644
index 00000000..955ac085
--- /dev/null
+++ b/leptonica/src/webpiostub.c
@@ -0,0 +1,99 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file webpiostub.c
+ * <pre>
+ *
+ * Stubs for webpio.c functions
+ * </pre>
+ */
+
+#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_ok readHeaderWebP(const char *filename, l_int32 *pw, l_int32 *ph,
+ l_int32 *pspp)
+{
+ return ERROR_INT("function not present", "readHeaderWebP", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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_ok pixWriteWebP(const char *filename, PIX *pixs, l_int32 quality,
+ l_int32 lossless)
+{
+ return ERROR_INT("function not present", "pixWriteWebP", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok pixWriteStreamWebP(FILE *fp, PIX *pixs, l_int32 quality,
+ l_int32 lossless)
+{
+ return ERROR_INT("function not present", "pixWriteStreamWebP", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_ok 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/leptonica/src/writefile.c b/leptonica/src/writefile.c
new file mode 100644
index 00000000..0f5527ec
--- /dev/null
+++ b/leptonica/src/writefile.c
@@ -0,0 +1,1209 @@
+/*====================================================================*
+ - Copyright (C) 2001-2016 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
+ *
+ * Set jpeg quality for pixWrite() and pixWriteMem()
+ * l_int32 l_jpegSetQuality()
+ *
+ * Set global variable LeptDebugOK for writing to named temp files
+ * l_int32 setLeptDebugOK()
+ *
+ * High-level procedures for writing images to file:
+ * l_int32 pixaWriteFiles()
+ * l_int32 pixWriteDebug()
+ * l_int32 pixWrite()
+ * l_int32 pixWriteAutoFormat()
+ * l_int32 pixWriteStream()
+ * l_int32 pixWriteImpliedFormat()
+ *
+ * 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 l_fileDisplay()
+ * l_int32 pixDisplay()
+ * l_int32 pixDisplayWithTitle()
+ * PIX *pixMakeColorSquare()
+ * void l_chooseDisplayProg()
+ *
+ * Change format for missing library
+ * void changeFormatForMissingLib()
+ *
+ * Nonfunctional stub of pix output for debugging
+ * l_int32 pixDisplayWrite()
+ *
+ * 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)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+ /* 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 */
+
+#define Bufsize 512
+static const l_int32 MaxDisplayWidth = 1000;
+static const l_int32 MaxDisplayHeight = 800;
+static const l_int32 MaxSizeForPng = 200;
+
+ /* PostScript output for printing */
+static const l_float32 DefaultScaling = 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 = 20; /* array size */
+LEPT_DLL const char *ImageFileFormatExtensions[] =
+ {"unknown",
+ "bmp",
+ "jpg",
+ "png",
+ "tif",
+ "tif",
+ "tif",
+ "tif",
+ "tif",
+ "tif",
+ "tif",
+ "pnm",
+ "ps",
+ "gif",
+ "jp2",
+ "webp",
+ "pdf",
+ "tif",
+ "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 } };
+
+
+/*---------------------------------------------------------------------*
+ * Set jpeg quality for pixWrite() and pixWriteMem() *
+ *---------------------------------------------------------------------*/
+ /* Parameter that controls jpeg quality for high-level calls. */
+static l_int32 var_JPEG_QUALITY = 75; /* default */
+
+/*!
+ * \brief l_jpegSetQuality()
+ *
+ * \param[in] new_quality 1 - 100; 75 is default; 0 defaults to 75
+ * \return prev previous quality
+ *
+ * <pre>
+ * Notes:
+ * (1) This variable is used in pixWriteStream() and pixWriteMem(),
+ * to control the jpeg quality. The default is 75.
+ * (2) It returns the previous quality, so for example:
+ * l_int32 prev = l_jpegSetQuality(85); //sets to 85
+ * pixWriteStream(...);
+ * l_jpegSetQuality(prev); // resets to previous value
+ * (3) On error, logs a message and does not change the variable.
+ */
+l_int32
+l_jpegSetQuality(l_int32 new_quality)
+{
+l_int32 prevq, newq;
+
+ PROCNAME("l_jpeqSetQuality");
+
+ prevq = var_JPEG_QUALITY;
+ newq = (new_quality == 0) ? 75 : new_quality;
+ if (newq < 1 || newq > 100)
+ L_ERROR("invalid jpeg quality; unchanged\n", procName);
+ else
+ var_JPEG_QUALITY = newq;
+ return prevq;
+}
+
+
+/*----------------------------------------------------------------------*
+ * Set global variable LeptDebugOK for writing to named temp files *
+ *----------------------------------------------------------------------*/
+LEPT_DLL l_int32 LeptDebugOK = 0; /* default value */
+/*!
+ * \brief setLeptDebugOK()
+ *
+ * \param[in] allow TRUE (1) or FALSE (0)
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) This sets or clears the global variable LeptDebugOK, to
+ * control writing files in a temp directory with names that
+ * are compiled in.
+ * (2) The default in the library distribution is 0. Call with
+ * %allow = 1 for development and debugging.
+ */
+void
+setLeptDebugOK(l_int32 allow)
+{
+ if (allow != 0) allow = 1;
+ LeptDebugOK = allow;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Top-level procedures for writing images to file *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixaWriteFiles()
+ *
+ * \param[in] rootname
+ * \param[in] pixa
+ * \param[in] format defined in imageio.h; see notes for default
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Use %format = IFF_DEFAULT to decide the output format
+ * individually for each pix.
+ * </pre>
+ */
+l_ok
+pixaWriteFiles(const char *rootname,
+ PIXA *pixa,
+ l_int32 format)
+{
+char bigbuf[Bufsize];
+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, Bufsize, "%s%03d.%s", rootname, i,
+ ImageFileFormatExtensions[pixformat]);
+ pixWrite(bigbuf, pix, pixformat);
+ pixDestroy(&pix);
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixWriteDebug()
+ *
+ * \param[in] fname
+ * \param[in] pix
+ * \param[in] format defined in imageio.h
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) Debug version, intended for use in the library when writing
+ * to files in a temp directory with names that are compiled in.
+ * This is used instead of pixWrite() for all such library calls.
+ * (2) The global variable LeptDebugOK defaults to 0, and can be set
+ * or cleared by the function setLeptDebugOK().
+ * </pre>
+ */
+l_ok
+pixWriteDebug(const char *fname,
+ PIX *pix,
+ l_int32 format)
+{
+ PROCNAME("pixWriteDebug");
+
+ if (LeptDebugOK) {
+ return pixWrite(fname, pix, format);
+ } else {
+ L_INFO("write to named temp file %s is disabled\n", procName, fname);
+ return 0;
+ }
+}
+
+
+/*!
+ * \brief pixWrite()
+ *
+ * \param[in] fname
+ * \param[in] pix
+ * \param[in] format defined in imageio.h
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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) The default jpeg quality is 75. For some other value,
+ * Use l_jpegSetQuality().
+ * </pre>
+ */
+l_ok
+pixWrite(const char *fname,
+ PIX *pix,
+ l_int32 format)
+{
+l_int32 ret;
+FILE *fp;
+
+ PROCNAME("pixWrite");
+
+ if (!pix)
+ return ERROR_INT("pix not defined", procName, 1);
+ if (!fname)
+ return ERROR_INT("fname not defined", procName, 1);
+
+ if ((fp = fopenWriteStream(fname, "wb+")) == NULL)
+ return ERROR_INT("stream not opened", procName, 1);
+
+ ret = pixWriteStream(fp, pix, format);
+ fclose(fp);
+ if (ret)
+ return ERROR_INT("pix not written to stream", procName, 1);
+ return 0;
+}
+
+
+/*!
+ * \brief pixWriteAutoFormat()
+ *
+ * \param[in] filename
+ * \param[in] pix
+ * \return 0 if OK; 1 on error
+ */
+l_ok
+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);
+}
+
+
+/*!
+ * \brief pixWriteStream()
+ *
+ * \param[in] fp file stream
+ * \param[in] pix
+ * \param[in] format
+ * \return 0 if OK; 1 on error.
+ */
+l_ok
+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);
+
+ /* Use bmp format for testing if library for requested
+ * format for jpeg, png or tiff is not available */
+ changeFormatForMissingLib(&format);
+
+ switch(format)
+ {
+ case IFF_BMP:
+ pixWriteStreamBmp(fp, pix);
+ break;
+
+ case IFF_JFIF_JPEG: /* default quality; baseline sequential */
+ return pixWriteStreamJpeg(fp, pix, var_JPEG_QUALITY, 0);
+
+ case IFF_PNG: /* no gamma value stored */
+ return pixWriteStreamPng(fp, pix, 0.0);
+
+ 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 */
+ case IFF_TIFF_JPEG: /* compressed, 8 bpp gray and 32 bpp rgb */
+ return pixWriteStreamTiff(fp, pix, format);
+
+ case IFF_PNM:
+ return pixWriteStreamPnm(fp, pix);
+
+ case IFF_PS:
+ return pixWriteStreamPS(fp, pix, NULL, 0, DefaultScaling);
+
+ case IFF_GIF:
+ return pixWriteStreamGif(fp, pix);
+
+ case IFF_JP2:
+ return pixWriteStreamJp2k(fp, pix, 34, 4, 0, 0);
+
+ case IFF_WEBP:
+ return pixWriteStreamWebP(fp, pix, 80, 0);
+
+ case IFF_LPDF:
+ return pixWriteStreamPdf(fp, pix, 0, NULL);
+
+ case IFF_SPIX:
+ return pixWriteStreamSpix(fp, pix);
+
+ default:
+ return ERROR_INT("unknown format", procName, 1);
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief pixWriteImpliedFormat()
+ *
+ * \param[in] filename
+ * \param[in] pix
+ * \param[in] quality iff JPEG; 1 - 100, 0 for default
+ * \param[in] progressive iff JPEG; 0 for baseline seq., 1 for progressive
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Selection of output format if default is requested *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixChooseOutputFormat()
+ *
+ * \param[in] pix
+ * \return output format, or 0 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief getImpliedFileFormat()
+ *
+ * \param[in] filename
+ * \return output format, or IFF_UNKNOWN on error or invalid extension.
+ *
+ * <pre>
+ * Notes:
+ * (1) This determines the output file format from the extension
+ * of the input filename.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixGetAutoFormat()
+ *
+ * \param[in] pix
+ * \param[in] &format
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_ok
+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;
+}
+
+
+/*!
+ * \brief getFormatExtension()
+ *
+ * \param[in] format integer
+ * \return extension string, or NULL if format is out of range
+ *
+ * <pre>
+ * Notes:
+ * (1) This string is NOT owned by the caller; it is just a pointer
+ * to a global string. Do not free it.
+ * </pre>
+ */
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixWriteMem()
+ *
+ * \param[out] pdata data of tiff compressed image
+ * \param[out] psize size of returned data
+ * \param[in] pix
+ * \param[in] format defined in imageio.h
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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).
+ * (3) The default jpeg quality is 75. For some other value,
+ * Use l_jpegSetQuality().
+ * </pre>
+ */
+l_ok
+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);
+
+ /* Use bmp format for testing if library for requested
+ * format for jpeg, png or tiff is not available */
+ changeFormatForMissingLib(&format);
+
+ switch(format)
+ {
+ case IFF_BMP:
+ ret = pixWriteMemBmp(pdata, psize, pix);
+ break;
+
+ case IFF_JFIF_JPEG: /* default quality; baseline sequential */
+ ret = pixWriteMemJpeg(pdata, psize, pix, var_JPEG_QUALITY, 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 */
+ case IFF_TIFF_JPEG: /* compressed, 8 bpp gray or 32 bpp rgb */
+ 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, DefaultScaling);
+ 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);
+ }
+
+ return ret;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Image display for debugging *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief l_fileDisplay()
+ *
+ * \param[in] fname
+ * \param[in] x, y location of display frame on the screen
+ * \param[in] scale scale factor (use 0 to skip display)
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is a convenient wrapper for displaying image files.
+ * (2) It does nothing unless LeptDebugOK == TRUE.
+ * (2) Set %scale = 0 to disable display.
+ * (3) This downscales 1 bpp to gray.
+ * </pre>
+ */
+l_ok
+l_fileDisplay(const char *fname,
+ l_int32 x,
+ l_int32 y,
+ l_float32 scale)
+{
+PIX *pixs, *pixd;
+
+ PROCNAME("l_fileDisplay");
+
+ if (!LeptDebugOK) {
+ L_INFO("displaying files is disabled; "
+ "use setLeptDebugOK(1) to enable\n", procName);
+ return 0;
+ }
+ if (scale == 0.0)
+ return 0;
+ if (scale < 0.0)
+ return ERROR_INT("invalid scale factor", procName, 1);
+ if ((pixs = pixRead(fname)) == NULL)
+ return ERROR_INT("pixs not read", procName, 1);
+
+ if (scale == 1.0) {
+ pixd = pixClone(pixs);
+ } else {
+ if (scale < 1.0 && pixGetDepth(pixs) == 1)
+ pixd = pixScaleToGray(pixs, scale);
+ else
+ pixd = pixScale(pixs, scale, scale);
+ }
+ pixDisplay(pixd, x, y);
+ pixDestroy(&pixs);
+ pixDestroy(&pixd);
+ return 0;
+}
+
+
+/*!
+ * \brief pixDisplay()
+ *
+ * \param[in] pix 1, 2, 4, 8, 16, 32 bpp
+ * \param[in] x, y location of display frame on the screen
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This is debugging code that displays an image on the screen.
+ * It uses a static internal variable to number the output files
+ * written by a single process. Behavior with a shared library
+ * may be unpredictable.
+ * (2) It does nothing unless LeptDebugOK == TRUE.
+ * (3) It uses these programs to display the image:
+ * On Unix: xzgv, xli or xv
+ * On Windows: i_view
+ * 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.
+ * (4) Images with dimensions larger than MaxDisplayWidth or
+ * MaxDisplayHeight 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.
+ * (5) The temporary file is written as png if, after initial
+ * processing for special cases, any of these obtain:
+ * * pix dimensions are smaller than some thresholds
+ * * pix depth is less than 8 bpp
+ * * pix is colormapped
+ * (6) 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.
+ * </pre>
+ */
+l_ok
+pixDisplay(PIX *pixs,
+ l_int32 x,
+ l_int32 y)
+{
+ return pixDisplayWithTitle(pixs, x, y, NULL, 1);
+}
+
+
+/*!
+ * \brief pixDisplayWithTitle()
+ *
+ * \param[in] pix 1, 2, 4, 8, 16, 32 bpp
+ * \param[in] x, y location of display frame
+ * \param[in] title [optional] on frame; can be NULL;
+ * \param[in] dispflag 1 to write, else disabled
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See notes for pixDisplay().
+ * (2) This displays the image if dispflag == 1; otherwise it punts.
+ * </pre>
+ */
+l_ok
+pixDisplayWithTitle(PIX *pixs,
+ l_int32 x,
+ l_int32 y,
+ const char *title,
+ l_int32 dispflag)
+{
+char *tempname;
+char buffer[Bufsize];
+static l_int32 index = 0; /* caution: not .so or thread safe */
+l_int32 w, h, d, spp, maxheight, opaque, threeviews;
+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 (!LeptDebugOK) {
+ L_INFO("displaying images is disabled;\n "
+ "use setLeptDebugOK(1) to enable\n", procName);
+ return 0;
+ }
+
+#ifdef OS_IOS /* iOS 11 does not support system() */
+ return ERROR_INT("iOS 11 does not support system()", procName, 1);
+#endif /* OS_IOS */
+
+ 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) ? MaxDisplayHeight / 3 : MaxDisplayHeight;
+ if (w <= MaxDisplayWidth && h <= maxheight) {
+ if (d == 16) /* take MSB */
+ pix1 = pixConvert16To8(pix0, L_MS_BYTE);
+ else
+ pix1 = pixClone(pix0);
+ } else {
+ ratw = (l_float32)MaxDisplayWidth / (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) { /* erase any existing images */
+ lept_rmdir("lept/disp");
+ lept_mkdir("lept/disp");
+ }
+
+ index++;
+ if (pixGetDepth(pix2) < 8 || pixGetColormap(pix2) ||
+ (w < MaxSizeForPng && h < MaxSizeForPng)) {
+ snprintf(buffer, Bufsize, "/tmp/lept/disp/write.%03d.png", index);
+ pixWrite(buffer, pix2, IFF_PNG);
+ } else {
+ snprintf(buffer, Bufsize, "/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, Bufsize,
+ "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, Bufsize,
+ "xli -dispgamma 1.0 -quiet -geometry +%d+%d -title \"%s\" %s &",
+ x, y, title, tempname);
+ } else {
+ snprintf(buffer, Bufsize,
+ "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, Bufsize,
+ "xv -quit -geometry +%d+%d -name \"%s\" %s &",
+ x, y, title, tempname);
+ } else {
+ snprintf(buffer, Bufsize,
+ "xv -quit -geometry +%d+%d %s &", x, y, tempname);
+ }
+ } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_OPEN) {
+ snprintf(buffer, Bufsize, "open %s &", tempname);
+ }
+ callSystemDebug(buffer);
+
+#else /* _WIN32 */
+
+ /* Windows: L_DISPLAY_WITH_IV */
+ pathname = genPathname(tempname, NULL);
+ _fullpath(fullpath, pathname, sizeof(fullpath));
+ if (title) {
+ snprintf(buffer, Bufsize,
+ "i_view32.exe \"%s\" /pos=(%d,%d) /title=\"%s\"",
+ fullpath, x, y, title);
+ } else {
+ snprintf(buffer, Bufsize, "i_view32.exe \"%s\" /pos=(%d,%d)",
+ fullpath, x, y);
+ }
+ callSystemDebug(buffer);
+ LEPT_FREE(pathname);
+
+#endif /* _WIN32 */
+
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+ LEPT_FREE(tempname);
+ return 0;
+}
+
+
+/*!
+ * \brief pixMakeColorSquare()
+ *
+ * \param[in] color in 0xrrggbb00 format
+ * \param[in] size in pixels; >= 100; use 0 for default (min size)
+ * \param[in] addlabel use 1 to display the color component values
+ * \param[in] location of text: L_ADD_ABOVE, etc; ignored if %addlabel == 0
+ * \param[in] textcolor of text label; in 0xrrggbb00 format
+ * \return 32 bpp rgb pixd if OK; NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) If %addlabel == 0, %location and %textcolor are ignored.
+ * (2) To make an array of color squares, use pixDisplayColorArray().
+ * </pre>
+ */
+PIX *
+pixMakeColorSquare(l_uint32 color,
+ l_int32 size,
+ l_int32 addlabel,
+ l_int32 location,
+ l_uint32 textcolor)
+{
+char buf[32];
+l_int32 w, rval, gval, bval;
+L_BMF *bmf;
+PIX *pix1, *pix2;
+
+ PROCNAME("pixMakeColorSquare");
+
+ w = (size <= 0) ? 100 : size;
+ if (addlabel && w < 100) {
+ L_WARNING("size too small for label; omitting label\n", procName);
+ addlabel = 0;
+ }
+
+ if ((pix1 = pixCreate(w, w, 32)) == NULL)
+ return (PIX *)ERROR_PTR("pix1 not madel", procName, NULL);
+ pixSetAllArbitrary(pix1, color);
+ if (!addlabel)
+ return pix1;
+
+ /* Adding text of color component values */
+ if (location != L_ADD_ABOVE && location != L_ADD_AT_TOP &&
+ location != L_ADD_AT_BOT && location != L_ADD_BELOW) {
+ L_ERROR("invalid location: adding below\n", procName);
+ location = L_ADD_BELOW;
+ }
+ bmf = bmfCreate(NULL, 4);
+ extractRGBValues(color, &rval, &gval, &bval);
+ snprintf(buf, sizeof(buf), "%d,%d,%d", rval, gval, bval);
+ pix2 = pixAddSingleTextblock(pix1, bmf, buf, textcolor, location, NULL);
+ pixDestroy(&pix1);
+ bmfDestroy(&bmf);
+ return pix2;
+}
+
+
+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");
+ }
+}
+
+
+/*---------------------------------------------------------------------*
+ * Change format for missing lib *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief changeFormatForMissingLib()
+ *
+ * \param[in,out] pformat addr of requested output image format
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) This is useful for testing functionality when the library for
+ * the requested output format (jpeg, png or tiff) is not linked.
+ * In that case, the output format is changed to bmp.
+ * </pre>
+ */
+void
+changeFormatForMissingLib(l_int32 *pformat)
+{
+ PROCNAME("changeFormatForMissingLib");
+
+#if !defined(HAVE_LIBJPEG)
+ if (*pformat == IFF_JFIF_JPEG) {
+ L_WARNING("jpeg library missing; output bmp format\n", procName);
+ *pformat = IFF_BMP;
+ }
+#endif /* !defined(HAVE_LIBJPEG) */
+#if !defined(HAVE_LIBPNG)
+ if (*pformat == IFF_PNG) {
+ L_WARNING("png library missing; output bmp format\n", procName);
+ *pformat = IFF_BMP;
+ }
+#endif /* !defined(HAVE_LIBPNG) */
+#if !defined(HAVE_LIBTIFF)
+ if (L_FORMAT_IS_TIFF(*pformat)) {
+ L_WARNING("tiff library missing; output bmp format\n", procName);
+ *pformat = IFF_BMP;
+ }
+#endif /* !defined(HAVE_LIBTIFF) */
+}
+
+
+/*---------------------------------------------------------------------*
+ * Deprecated pix output for debugging *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief pixDisplayWrite()
+ *
+ * \param[in] pix
+ * \param[in] reduction
+ * \return 1 (error)
+ *
+ * <pre>
+ * Notes:
+ * As of 1.80, this is a non-functional stub.
+ * </pre>
+ */
+l_ok
+pixDisplayWrite(PIX *pixs,
+ l_int32 reduction)
+{
+ lept_stderr("\n########################################################\n"
+ " pixDisplayWrite() was last used in tesseract 3.04,"
+ " in Feb 2016. As of 1.80, it is a non-functional stub\n"
+ "########################################################"
+ "\n\n\n");
+ return 1;
+}
diff --git a/leptonica/src/zlibmem.c b/leptonica/src/zlibmem.c
new file mode 100644
index 00000000..42977f3d
--- /dev/null
+++ b/leptonica/src/zlibmem.c
@@ -0,0 +1,282 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+
+/*!
+ * \file zlibmem.c
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+
+#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 */
+
+
+/*!
+ * \brief zlibCompress()
+ *
+ * \param[in] datain byte buffer with input data
+ * \param[in] nin number of bytes of input data
+ * \param[out] pnout number of bytes of output data
+ * \return dataout compressed data, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+l_uint8 *
+zlibCompress(const l_uint8 *datain,
+ size_t nin,
+ size_t *pnout)
+{
+l_uint8 *dataout;
+l_int32 status, success;
+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 */
+ bufferin = (l_uint8 *)LEPT_CALLOC(L_BUF_SIZE, sizeof(l_uint8));
+ bufferout = (l_uint8 *)LEPT_CALLOC(L_BUF_SIZE, sizeof(l_uint8));
+
+ /* Set up bbuffers and load bbin with the data */
+ bbin = bbufferCreate(datain, nin);
+ bbout = bbufferCreate(NULL, 0);
+
+ success = TRUE;
+ if (!bufferin || !bufferout || !bbin || !bbout) {
+ L_ERROR("calloc fail for buffer\n", procName);
+ success = FALSE;
+ goto cleanup_arrays;
+ }
+
+ 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) {
+ L_ERROR("deflateInit failed\n", procName);
+ success = FALSE;
+ goto cleanup_arrays;
+ }
+
+ do {
+ if (z.avail_in == 0) {
+ z.next_in = bufferin;
+ bbufferWrite(bbin, bufferin, L_BUF_SIZE, &nbytes);
+#if DEBUG
+ lept_stderr(" wrote %zu bytes to bufferin\n", nbytes);
+#endif /* DEBUG */
+ z.avail_in = nbytes;
+ }
+ flush = (bbin->n) ? Z_SYNC_FLUSH : Z_FINISH;
+ status = deflate(&z, flush);
+#if DEBUG
+ lept_stderr(" status is %d, bytesleft = %u, totalout = %zu\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
+ lept_stderr(" read %zu bytes from bufferout\n", nbytes);
+#endif /* DEBUG */
+ }
+ z.next_out = bufferout;
+ z.avail_out = L_BUF_SIZE;
+ } while (flush != Z_FINISH);
+
+ deflateEnd(&z);
+
+cleanup_arrays:
+ if (success) {
+ dataout = bbufferDestroyAndSaveData(&bbout, pnout);
+ } else {
+ dataout = NULL;
+ bbufferDestroy(&bbout);
+ }
+ bbufferDestroy(&bbin);
+ LEPT_FREE(bufferin);
+ LEPT_FREE(bufferout);
+ return dataout;
+}
+
+
+/*!
+ * \brief zlibUncompress()
+ *
+ * \param[in] datain byte buffer with compressed input data
+ * \param[in] nin number of bytes of input data
+ * \param[out] pnout number of bytes of output data
+ * \return dataout uncompressed data, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) See zlibCompress().
+ * </pre>
+ */
+l_uint8 *
+zlibUncompress(const l_uint8 *datain,
+ size_t nin,
+ size_t *pnout)
+{
+l_uint8 *dataout;
+l_uint8 *bufferin, *bufferout;
+l_int32 status, success;
+size_t nbytes;
+L_BBUFFER *bbin, *bbout;
+z_stream z;
+
+ PROCNAME("zlibUncompress");
+
+ if (!datain)
+ return (l_uint8 *)ERROR_PTR("datain not defined", procName, NULL);
+
+ /* Set up fixed size buffers used in z_stream */
+ bufferin = (l_uint8 *)LEPT_CALLOC(L_BUF_SIZE, sizeof(l_uint8));
+ bufferout = (l_uint8 *)LEPT_CALLOC(L_BUF_SIZE, sizeof(l_uint8));
+
+ /* Set up bbuffers and load bbin with the data */
+ bbin = bbufferCreate(datain, nin);
+ bbout = bbufferCreate(NULL, 0);
+
+ success = TRUE;
+ if (!bufferin || !bufferout || !bbin || !bbout) {
+ L_ERROR("calloc fail for buffer\n", procName);
+ success = FALSE;
+ goto cleanup_arrays;
+ }
+
+ 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);
+#if DEBUG
+ lept_stderr(" wrote %d bytes to bufferin\n", nbytes);
+#endif /* DEBUG */
+ z.avail_in = nbytes;
+ }
+ if (z.avail_in == 0)
+ break;
+ status = inflate(&z, Z_SYNC_FLUSH);
+#if DEBUG
+ lept_stderr(" status is %d, bytesleft = %d, totalout = %d\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
+ lept_stderr(" read %d bytes from bufferout\n", nbytes);
+#endif /* DEBUG */
+ }
+ z.next_out = bufferout;
+ z.avail_out = L_BUF_SIZE;
+ }
+
+ inflateEnd(&z);
+
+cleanup_arrays:
+ if (success) {
+ dataout = bbufferDestroyAndSaveData(&bbout, pnout);
+ } else {
+ dataout = NULL;
+ bbufferDestroy(&bbout);
+ }
+ bbufferDestroy(&bbin);
+ LEPT_FREE(bufferin);
+ LEPT_FREE(bufferout);
+ return dataout;
+}
+
+/* --------------------------------------------*/
+#endif /* HAVE_LIBZ */
+/* --------------------------------------------*/
diff --git a/leptonica/src/zlibmemstub.c b/leptonica/src/zlibmemstub.c
new file mode 100644
index 00000000..5d035546
--- /dev/null
+++ b/leptonica/src/zlibmemstub.c
@@ -0,0 +1,59 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file zlibmemstub.c
+ * <pre>
+ *
+ * Stubs for zlibmem.c functions
+ * </pre>
+ */
+
+#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(const l_uint8 *datain, size_t nin, size_t *pnout)
+{
+ return (l_uint8 *)ERROR_PTR("function not present", "zlibCompress", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_uint8 * zlibUncompress(const 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/leptonica/style-guide.txt b/leptonica/style-guide.txt
new file mode 100644
index 00000000..045cf998
--- /dev/null
+++ b/leptonica/style-guide.txt
@@ -0,0 +1,230 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+ style-guide.txt
+
+ 10 May 2019
+
+This is not a complete guide to the coding style in leptonica.
+It covers most of the typographic issues and the most frequent
+coding patterns, such as how to check input args to functions.
+In general, you need to look at existing code to verify that 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 (omitting the 'struct' keyword); 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) The code is set up to work with doxygen. Function headers are in
+ this format:
+
+ /*!
+ * \brief pixSelectByAreaFraction()
+ *
+ * \param[in] pixs 1 bpp
+ * \param[in] thresh threshold ratio of fg pixels to (w * h)
+ * \param[in] connectivity 4 or 8
+ * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT,
+ * L_SELECT_IF_LTE, L_SELECT_IF_GTE
+ * \param[out] pchanged [optional] 1 if changed; 0 if clone returned
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The args specify constraints on the amount of foreground
+ * coverage of the components that are kept.
+ * ....
+ * </pre>
+ */
+
+ (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 more than one 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); otherwise it can
+ name the output 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) Static constants defined in a file should have the first letter of
+ each word capitalized. (There are also some that are formatted
+ like enums, with all caps and '_' between parts.)
+
+ (h) There are very few globals in the library. Of these, there
+ are just a handful of static globals that can be changed.
+ Const globals are named with each word beginning with a capital; e.g.,
+ ImageFileFormatExtensions[]
+ Static globals that can be changed are named like preprocessor
+ variables, except they are prepended by 'var_'; e.g.,
+ var_PNG_WRITE_ALPHA
+ Functions that set these static globals are named with a
+ pre-pended 'l_'; e.g.,
+ l_pngSetWriteAlpha()
+
+ (i) Where there may be issues with namespace collisions with other
+ libraries, function names can be prepended with 'l_'; e.g.,
+ l_amapInsert()
+
+(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. The following sequence of tests
+ and initializations guarantees that no uninitialized arguments
+ are returned:
+
+ (a) First, all optional values are initialized if possible:
+
+ if (pboxa) *pboxa = NULL; // Boxa **pboxa 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 can be 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/leptonica/sw.cpp b/leptonica/sw.cpp
new file mode 100644
index 00000000..9190975e
--- /dev/null
+++ b/leptonica/sw.cpp
@@ -0,0 +1,391 @@
+void build(Solution &s)
+{
+ auto add_deps = [](auto &t)
+ {
+ t += "HAVE_LIBGIF"_d;
+ t += "HAVE_LIBJP2K"_d;
+ t += "HAVE_LIBJPEG"_d;
+ t += "HAVE_LIBPNG"_d;
+ t += "HAVE_LIBTIFF"_d;
+ t += "HAVE_LIBWEBP"_d;
+ t += "HAVE_LIBWEBP_ANIM"_d;
+ t += "HAVE_LIBZ"_d;
+
+ t += "org.sw.demo.gif"_dep;
+ t += "org.sw.demo.jpeg"_dep;
+ t += "org.sw.demo.uclouvain.openjpeg.openjp2"_dep;
+ t += "org.sw.demo.glennrp.png"_dep;
+ t += "org.sw.demo.tiff"_dep;
+ t += "org.sw.demo.webmproject.webp"_dep;
+ };
+
+ auto &leptonica = s.addTarget<LibraryTarget>("danbloomberg.leptonica", "1.81.0");
+ leptonica += Git("https://github.com/DanBloomberg/leptonica");
+
+ {
+ leptonica.setChecks("leptonica");
+ leptonica.ApiName = "LEPT_DLL";
+
+ leptonica +=
+ "src/.*\\.c"_rr,
+ "src/.*\\.h"_rr,
+ "src/endianness.h.in";
+
+ leptonica.Public +=
+ "src"_id;
+
+ add_deps(leptonica);
+
+ leptonica += "LIBJP2K_HEADER=\"openjpeg.h\""_d;
+ leptonica.Public += "HAVE_CONFIG_H"_d;
+
+ if (leptonica.Variables["WORDS_BIGENDIAN"] == 1)
+ leptonica.Variables["ENDIANNESS"] = "L_BIG_ENDIAN";
+ else
+ leptonica.Variables["ENDIANNESS"] = "L_LITTLE_ENDIAN";
+
+ leptonica.Variables["APPLE_UNIVERSAL_BUILD"] = "defined (__APPLE_CC__)";
+
+ leptonica.configureFile("src/endianness.h.in", "endianness.h");
+ leptonica.writeFileOnce("config_auto.h");
+
+ if (leptonica.getBuildSettings().TargetOS.Type == OSType::Windows ||
+ leptonica.getBuildSettings().TargetOS.Type == OSType::Mingw)
+ leptonica += "User32.lib"_slib, "Gdi32.lib"_slib;
+ }
+
+ auto &progs = leptonica.addDirectory("progs");
+ {
+ auto add_prog = [&s, &progs, &leptonica, &add_deps](const String &name, const Files &files) -> decltype(auto)
+ {
+ auto &t = progs.addExecutable(name);
+ t.Scope = TargetScope::Test;
+ t.setRootDirectory("prog");
+ t += files;
+ t += leptonica;
+ add_deps(t);
+ return t;
+ };
+
+ StringMap<Files> m_progs{
+ {"adaptmap_reg", {"adaptmap_reg.c"}},
+ {"adaptnorm_reg", {"adaptnorm_reg.c"}},
+ {"affine_reg", {"affine_reg.c"}},
+ {"alltests_reg", {"alltests_reg.c"}},
+ {"alphaops_reg", {"alphaops_reg.c"}},
+ {"alphaxform_reg", {"alphaxform_reg.c"}},
+ {"baseline_reg", {"baseline_reg.c"}},
+ {"bilateral1_reg", {"bilateral1_reg.c"}},
+ {"bilateral2_reg", {"bilateral2_reg.c"}},
+ {"bilinear_reg", {"bilinear_reg.c"}},
+ {"binarize_reg", {"binarize_reg.c"}},
+ {"binmorph1_reg", {"binmorph1_reg.c"}},
+ {"binmorph2_reg", {"binmorph2_reg.c"}},
+ {"binmorph3_reg", {"binmorph3_reg.c"}},
+ {"binmorph4_reg", {"binmorph4_reg.c"}},
+ {"binmorph5_reg", {"binmorph5_reg.c"}},
+ {"binmorph6_reg", {"binmorph6_reg.c"}},
+ {"blackwhite_reg", {"blackwhite_reg.c"}},
+ {"blend1_reg", {"blend1_reg.c"}},
+ {"blend2_reg", {"blend2_reg.c"}},
+ {"blend3_reg", {"blend3_reg.c"}},
+ {"blend4_reg", {"blend4_reg.c"}},
+ {"blend5_reg", {"blend5_reg.c"}},
+ {"boxa1_reg", {"boxa1_reg.c"}},
+ {"boxa2_reg", {"boxa2_reg.c"}},
+ {"boxa3_reg", {"boxa3_reg.c"}},
+ {"boxa4_reg", {"boxa4_reg.c"}},
+ {"bytea_reg", {"bytea_reg.c"}},
+ {"ccbord_reg", {"ccbord_reg.c"}},
+ {"ccthin1_reg", {"ccthin1_reg.c"}},
+ {"ccthin2_reg", {"ccthin2_reg.c"}},
+ {"checkerboard_reg", {"checkerboard_reg.c"}},
+ {"circle_reg", {"circle_reg.c"}},
+ {"cmapquant_reg", {"cmapquant_reg.c"}},
+ {"colorcontent_reg", {"colorcontent_reg.c"}},
+ {"colorfill_reg", {"colorfill_reg.c"}},
+ {"coloring_reg", {"coloring_reg.c"}},
+ {"colorize_reg", {"colorize_reg.c"}},
+ {"colormask_reg", {"colormask_reg.c"}},
+ {"colormorph_reg", {"colormorph_reg.c"}},
+ {"colorquant_reg", {"colorquant_reg.c"}},
+ {"colorseg_reg", {"colorseg_reg.c"}},
+ {"colorspace_reg", {"colorspace_reg.c"}},
+ {"compare_reg", {"compare_reg.c"}},
+ {"compfilter_reg", {"compfilter_reg.c"}},
+ {"conncomp_reg", {"conncomp_reg.c"}},
+ {"conversion_reg", {"conversion_reg.c"}},
+ {"convolve_reg", {"convolve_reg.c"}},
+ {"crop_reg", {"crop_reg.c"}},
+ {"dewarp_reg", {"dewarp_reg.c"}},
+ {"distance_reg", {"distance_reg.c"}},
+ {"dither_reg", {"dither_reg.c"}},
+ {"dna_reg", {"dna_reg.c"}},
+ {"dwamorph1_reg", {"dwamorph1_reg.c", "dwalinear.3.c", "dwalinearlow.3.c"}},
+ {"dwamorph2_reg", {"dwamorph2_reg.c", "dwalinear.3.c", "dwalinearlow.3.c"}},
+ {"edge_reg", {"edge_reg.c"}},
+ {"encoding_reg", {"encoding_reg.c"}},
+ {"enhance_reg", {"enhance_reg.c"}},
+ {"equal_reg", {"equal_reg.c"}},
+ {"expand_reg", {"expand_reg.c"}},
+ {"extrema_reg", {"extrema_reg.c"}},
+ {"falsecolor_reg", {"falsecolor_reg.c"}},
+ {"fhmtauto_reg", {"fhmtauto_reg.c"}},
+ {"files_reg", {"files_reg.c"}},
+ {"findcorners_reg", {"findcorners_reg.c"}},
+ {"findpattern_reg", {"findpattern_reg.c"}},
+ {"flipdetect_reg", {"flipdetect_reg.c"}},
+ {"fmorphauto_reg", {"fmorphauto_reg.c"}},
+ {"fpix1_reg", {"fpix1_reg.c"}},
+ {"fpix2_reg", {"fpix2_reg.c"}},
+ {"genfonts_reg", {"genfonts_reg.c"}},
+ {"gifio_reg", {"gifio_reg.c"}},
+ {"grayfill_reg", {"grayfill_reg.c"}},
+ {"graymorph1_reg", {"graymorph1_reg.c"}},
+ {"graymorph2_reg", {"graymorph2_reg.c"}},
+ {"grayquant_reg", {"grayquant_reg.c"}},
+ {"hardlight_reg", {"hardlight_reg.c"}},
+ {"hash_reg", {"hash_reg.c"}},
+ {"heap_reg", {"heap_reg.c"}},
+ {"insert_reg", {"insert_reg.c"}},
+ {"ioformats_reg", {"ioformats_reg.c"}},
+ {"iomisc_reg", {"iomisc_reg.c"}},
+ {"italic_reg", {"italic_reg.c"}},
+ {"jbclass_reg", {"jbclass_reg.c"}},
+ {"jp2kio_reg", {"jp2kio_reg.c"}},
+ {"jpegio_reg", {"jpegio_reg.c"}},
+ {"kernel_reg", {"kernel_reg.c"}},
+ {"label_reg", {"label_reg.c"}},
+ {"lineremoval_reg", {"lineremoval_reg.c"}},
+ {"locminmax_reg", {"locminmax_reg.c"}},
+ {"logicops_reg", {"logicops_reg.c"}},
+ {"lowaccess_reg", {"lowaccess_reg.c"}},
+ {"lowsat_reg", {"lowsat_reg.c"}},
+ {"maze_reg", {"maze_reg.c"}},
+ {"mtiff_reg", {"mtiff_reg.c"}},
+ {"multitype_reg", {"multitype_reg.c"}},
+ {"nearline_reg", {"nearline_reg.c"}},
+ {"newspaper_reg", {"newspaper_reg.c"}},
+ {"numa1_reg", {"numa1_reg.c"}},
+ {"numa2_reg", {"numa2_reg.c"}},
+ {"numa3_reg", {"numa3_reg.c"}},
+ {"overlap_reg", {"overlap_reg.c"}},
+ {"pageseg_reg", {"pageseg_reg.c"}},
+ {"paintmask_reg", {"paintmask_reg.c"}},
+ {"paint_reg", {"paint_reg.c"}},
+ {"pdfio1_reg", {"pdfio1_reg.c"}},
+ {"pdfio2_reg", {"pdfio2_reg.c"}},
+ {"pdfseg_reg", {"pdfseg_reg.c"}},
+ {"pixa1_reg", {"pixa1_reg.c"}},
+ {"pixa2_reg", {"pixa2_reg.c"}},
+ {"pixadisp_reg", {"pixadisp_reg.c"}},
+ {"pixalloc_reg", {"pixalloc_reg.c"}},
+ {"pixcomp_reg", {"pixcomp_reg.c"}},
+ {"pixmem_reg", {"pixmem_reg.c"}},
+ {"pixserial_reg", {"pixserial_reg.c"}},
+ {"pixtile_reg", {"pixtile_reg.c"}},
+ {"pngio_reg", {"pngio_reg.c"}},
+ {"pnmio_reg", {"pnmio_reg.c"}},
+ {"projection_reg", {"projection_reg.c"}},
+ {"projective_reg", {"projective_reg.c"}},
+ {"psioseg_reg", {"psioseg_reg.c"}},
+ {"psio_reg", {"psio_reg.c"}},
+ {"pta_reg", {"pta_reg.c"}},
+ {"ptra1_reg", {"ptra1_reg.c"}},
+ {"ptra2_reg", {"ptra2_reg.c"}},
+ {"quadtree_reg", {"quadtree_reg.c"}},
+ {"rankbin_reg", {"rankbin_reg.c"}},
+ {"rankhisto_reg", {"rankhisto_reg.c"}},
+ {"rank_reg", {"rank_reg.c"}},
+ {"rasteropip_reg", {"rasteropip_reg.c"}},
+ {"rasterop_reg", {"rasterop_reg.c"}},
+ {"rectangle_reg", {"rectangle_reg.c"}},
+ {"rotate1_reg", {"rotate1_reg.c"}},
+ {"rotate2_reg", {"rotate2_reg.c"}},
+ {"scale_reg", {"scale_reg.c"}},
+ {"selio_reg", {"selio_reg.c"}},
+ {"shear1_reg", {"shear1_reg.c"}},
+ {"shear2_reg", {"shear2_reg.c"}},
+ {"skew_reg", {"skew_reg.c"}},
+ {"smallpix_reg", {"smallpix_reg.c"}},
+ {"smoothedge_reg", {"smoothedge_reg.c"}},
+ {"speckle_reg", {"speckle_reg.c"}},
+ {"splitcomp_reg", {"splitcomp_reg.c"}},
+ {"string_reg", {"string_reg.c"}},
+ {"subpixel_reg", {"subpixel_reg.c"}},
+ {"texturefill_reg", {"texturefill_reg.c"}},
+ {"threshnorm_reg", {"threshnorm_reg.c"}},
+ {"translate_reg", {"translate_reg.c"}},
+ {"warper_reg", {"warper_reg.c"}},
+ {"watershed_reg", {"watershed_reg.c"}},
+ {"webpio_reg", {"webpio_reg.c"}},
+ {"webpanimio_reg", {"webpanimio_reg.c"}},
+ {"wordboxes_reg", {"wordboxes_reg.c"}},
+ {"writetext_reg", {"writetext_reg.c"}},
+ {"xformbox_reg", {"xformbox_reg.c"}},
+ {"adaptmap_dark", {"adaptmap_dark.c"}},
+ {"arabic_lines", {"arabic_lines.c"}},
+ {"arithtest", {"arithtest.c"}},
+ {"autogentest1", {"autogentest1.c"}},
+ {"autogentest2", {"autogentest2.c", "autogen.137.c"}},
+ {"barcodetest", {"barcodetest.c"}},
+ {"binarize_set", {"binarize_set.c"}},
+ {"binarizefiles", {"binarizefiles.c"}},
+ {"bincompare", {"bincompare.c"}},
+ {"blendcmaptest", {"blendcmaptest.c"}},
+ {"buffertest", {"buffertest.c"}},
+ {"ccbordtest", {"ccbordtest.c"}},
+ {"cctest1", {"cctest1.c"}},
+ {"cleanpdf", {"cleanpdf.c"}},
+ {"colorsegtest", {"colorsegtest.c"}},
+ {"comparepages", {"comparepages.c"}},
+ {"comparepixa", {"comparepixa.c"}},
+ {"comparetest", {"comparetest.c"}},
+ {"concatpdf", {"concatpdf.c"}},
+ {"contrasttest", {"contrasttest.c"}},
+ {"convertfilestopdf", {"convertfilestopdf.c"}},
+ {"convertfilestops", {"convertfilestops.c"}},
+ {"convertformat", {"convertformat.c"}},
+ {"convertsegfilestopdf", {"convertsegfilestopdf.c"}},
+ {"convertsegfilestops", {"convertsegfilestops.c"}},
+ {"converttogray", {"converttogray.c"}},
+ {"converttopdf", {"converttopdf.c"}},
+ {"converttops", {"converttops.c"}},
+ {"cornertest", {"cornertest.c"}},
+ {"corrupttest", {"corrupttest.c"}},
+ {"croptext", {"croptext.c"}},
+ {"deskew_it", {"deskew_it.c"}},
+ {"dewarprules", {"dewarprules.c"}},
+ {"dewarptest1", {"dewarptest1.c"}},
+ {"dewarptest2", {"dewarptest2.c"}},
+ {"dewarptest3", {"dewarptest3.c"}},
+ {"dewarptest4", {"dewarptest4.c"}},
+ {"dewarptest5", {"dewarptest5.c"}},
+ {"digitprep1", {"digitprep1.c"}},
+ {"displayboxa", {"displayboxa.c"}},
+ {"displayboxes_on_pixa", {"displayboxes_on_pixa.c"}},
+ {"displaypix", {"displaypix.c"}},
+ {"displaypixa", {"displaypixa.c"}},
+ {"dwalineargen", {"dwalineargen.c"}},
+ {"fcombautogen", {"fcombautogen.c"}},
+ {"fhmtautogen", {"fhmtautogen.c"}},
+ {"fileinfo", {"fileinfo.c"}},
+ {"findbinding", {"findbinding.c"}},
+ {"find_colorregions", {"find_colorregions.c"}},
+ {"findpattern1", {"findpattern1.c"}},
+ {"findpattern2", {"findpattern2.c"}},
+ {"findpattern3", {"findpattern3.c"}},
+ {"fmorphautogen", {"fmorphautogen.c"}},
+ {"fpixcontours", {"fpixcontours.c"}},
+ {"gammatest", {"gammatest.c"}},
+ {"graphicstest", {"graphicstest.c"}},
+ {"graymorphtest", {"graymorphtest.c"}},
+ {"histotest", {"histotest.c"}},
+ {"histoduptest", {"histoduptest.c"}},
+ {"htmlviewer", {"htmlviewer.c"}},
+ {"imagetops", {"imagetops.c"}},
+ {"jbcorrelation", {"jbcorrelation.c"}},
+ {"jbrankhaus", {"jbrankhaus.c"}},
+ {"jbwords", {"jbwords.c"}},
+ {"lightcolortest", {"lightcolortest.c"}},
+ {"listtest", {"listtest.c"}},
+ {"livre_adapt", {"livre_adapt.c"}},
+ {"livre_hmt", {"livre_hmt.c"}},
+ {"livre_makefigs", {"livre_makefigs.c"}},
+ {"livre_orient", {"livre_orient.c"}},
+ {"livre_pageseg", {"livre_pageseg.c"}},
+ {"livre_seedgen", {"livre_seedgen.c"}},
+ {"livre_tophat", {"livre_tophat.c"}},
+ {"maketile", {"maketile.c"}},
+ {"maptest", {"maptest.c"}},
+ {"messagetest", {"messagetest.c"}},
+ {"misctest1", {"misctest1.c"}},
+ {"modifyhuesat", {"modifyhuesat.c"}},
+ {"morphseq_reg", {"morphseq_reg.c"}},
+ {"morphtest1", {"morphtest1.c"}},
+ {"numaranktest", {"numaranktest.c"}},
+ {"otsutest1", {"otsutest1.c"}},
+ {"otsutest2", {"otsutest2.c"}},
+ {"pagesegtest1", {"pagesegtest1.c"}},
+ {"pagesegtest2", {"pagesegtest2.c"}},
+ {"partifytest", {"partifytest.c"}},
+ {"partitiontest", {"partitiontest.c"}},
+ {"percolatetest", {"percolatetest.c"}},
+ {"pixaatest", {"pixaatest.c"}},
+ {"pixafileinfo", {"pixafileinfo.c"}},
+ {"plottest", {"plottest.c"}},
+ {"printimage", {"printimage.c"}},
+ {"printsplitimage", {"printsplitimage.c"}},
+ {"printtiff", {"printtiff.c"}},
+ {"rasteroptest", {"rasteroptest.c"}},
+ {"rbtreetest", {"rbtreetest.c"}},
+ {"recog_bootnum1", {"recog_bootnum1.c"}},
+ {"recog_bootnum2", {"recog_bootnum2.c"}},
+ {"recog_bootnum3", {"recog_bootnum3.c"}},
+ {"recogsort", {"recogsort.c"}},
+ {"recogtest1", {"recogtest1.c"}},
+ {"recogtest2", {"recogtest2.c"}},
+ {"recogtest3", {"recogtest3.c"}},
+ {"recogtest4", {"recogtest4.c"}},
+ {"recogtest5", {"recogtest5.c"}},
+ {"recogtest6", {"recogtest6.c"}},
+ {"recogtest7", {"recogtest7.c"}},
+ {"reducetest", {"reducetest.c"}},
+ {"removecmap", {"removecmap.c"}},
+ {"renderfonts", {"renderfonts.c"}},
+ {"replacebytes", {"replacebytes.c"}},
+ {"rotatefastalt", {"rotatefastalt.c"}},
+ {"rotate_it", {"rotate_it.c"}},
+ {"rotateorthtest1", {"rotateorthtest1.c"}},
+ {"rotateorth_reg", {"rotateorth_reg.c"}},
+ {"rotatetest1", {"rotatetest1.c"}},
+ {"runlengthtest", {"runlengthtest.c"}},
+ {"scaleandtile", {"scaleandtile.c"}},
+ {"scale_it", {"scale_it.c"}},
+ {"scaletest1", {"scaletest1.c"}},
+ {"scaletest2", {"scaletest2.c"}},
+ {"seedfilltest", {"seedfilltest.c"}},
+ {"seedspread_reg", {"seedspread_reg.c"}},
+ {"settest", {"settest.c"}},
+ {"sharptest", {"sharptest.c"}},
+ {"sheartest", {"sheartest.c"}},
+ {"showedges", {"showedges.c"}},
+ {"skewtest", {"skewtest.c"}},
+ {"sorttest", {"sorttest.c"}},
+ {"splitimage2pdf", {"splitimage2pdf.c"}},
+ {"sudokutest", {"sudokutest.c"}},
+ {"textorient", {"textorient.c"}},
+ {"tiffpdftest", {"tiffpdftest.c"}},
+ {"trctest", {"trctest.c"}},
+ {"underlinetest", {"underlinetest.c"}},
+ {"warpertest", {"warpertest.c"}},
+ {"wordsinorder", {"wordsinorder.c"}},
+ {"writemtiff", {"writemtiff.c"}},
+ {"xtractprotos", {"xtractprotos.c"}},
+ {"yuvtest", {"yuvtest.c"}},
+ };
+
+ for (auto &[p, files] : m_progs)
+ add_prog(p, files);
+ }
+}
+#pragma optimize("", on)
+
+void check(Checker &c)
+{
+ auto &s = c.addSet("leptonica");
+ s.checkFunctionExists("fmemopen");
+ s.checkFunctionExists("fstatat");
+ s.checkIncludeExists("dlfcn.h");
+ s.checkIncludeExists("inttypes.h");
+ s.checkIncludeExists("memory.h");
+ s.checkIncludeExists("stdint.h");
+ s.checkIncludeExists("stdlib.h");
+ s.checkIncludeExists("strings.h");
+ s.checkIncludeExists("string.h");
+ s.checkIncludeExists("sys/stat.h");
+ s.checkIncludeExists("sys/types.h");
+ s.checkIncludeExists("unistd.h");
+ s.checkTypeSize("size_t");
+ s.checkTypeSize("void *");
+}
diff --git a/leptonica/version-notes.html b/leptonica/version-notes.html
new file mode 100644
index 00000000..51c402f6
--- /dev/null
+++ b/leptonica/version-notes.html
@@ -0,0 +1,1600 @@
+<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.
+ *====================================================================*/
+</pre>
+
+
+<h2 align=center>Version Notes for Leptonica</h2>
+<h3 align=center>8 Sept 2020</h3>
+
+<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.81.0 not yet set
+ * Fixed problems with tiff pdf wrapping photometry.
+ * Fixed scaling issues in prog/cleanpdf for printing.
+ * New progs: tiffpdftest
+ * Fixed uninitialized data error in pixAddBorderGeneral() and
+ pixRemoveBorderGeneral()
+ * Rewrote Numa functions that discretize into bins. Have
+ binning by both sorting and histogram.
+ * Rewrote and simplified pixGetRankColorArray() and pixGetBinnedColor().
+ * Added tests to prog/rankbin_reg.c.
+ * Simplified fpixCopy() and dpixCopy(), and functions that use them.
+ * Check input for bilateral transforms.
+ * Add function for splitting a file evenly by lines.
+ * Check input for getFilenamesInDirectory()
+ * Many new fuzzers.
+ * Use size_t for all size inputs to ascii85 encoding/decoding
+ * New regression tests: encoding_reg.c, binmorph6_reg.c,
+ flipdetect_reg.c
+ * Reworked concatpdf for generality, using the Poppler package.
+ * Removed dwa flipdetection from the library. All the dwa code
+ is now in flipdetectdwa.c.notused. Likewise prog/flipselgen.c
+ is retained for completeness, renamed flipselgen.c.notused,
+ and is not compiled.
+
+1.80.0 28 July 2020
+ * Improve bmp handling of 1 bpp images and sanity checking of params.
+ * Add function to display all rgb gamut colors
+ * in Makefile.am, use option serial-tests to avoid races in testing
+ * Make m4 subdirectory and add ax_split_version.m4 there
+ * Simple function for hue-invariant mapping (pixMapWithInvariantHue)
+ * Fixed bug in limit of ptra size when used for sorting by bins.
+ * Use hashmap to count pixel colors in RGB(A) images.
+ * Convert hashtest program to regression test hash_reg.
+ * Convert croptest program to regression test crop_reg.
+ * New color segmentation by region growing (colorfill.c)
+ * New regression tests: colorfill_reg, circle_reg, ccbord_reg.
+ * Set maxima for all allocations for common leptonica data structures.
+ * Don't fail when downscaling 2, 4, 8, and 32 bpp images, even
+ to one pixel, invoking pixScaleSmooth().
+ * New functions that select 1 bpp components based on their area.
+ * Incremental addition to sorted array of numbers.
+ * new prog/fuzzing directory for oss-fuzz based fuzzing programs
+ * use of pixcmapIsValid() with extra argument to determine validity
+ with the pix it is attached to.
+ * Use lept_stderr() in all programs in the prog directory.
+ * New program rasteroptest() for thorough testing of rasterop functions.
+ * Removed the pixSaveTiled*() functions
+ * Stubbed pixDisplayWrite(). Last used in tesseract 3.04.01 (2/2016).
+
+1.79.0 1 Jan 2020
+ * Clean up auto-generation of files; removed 'register'
+ * Some fixes for issues identified by fuzzer
+ * New source files: checkerboard.c
+ * New programs: replacebytes.c, webpanimio_reg.c, partifytest.c,
+ rectangle_reg.c, lowsat_reg.c, rotate_it.c, scale_it.c, dewarp_it.c,
+ pdfio1_reg.c, pdfio2_reg.c, checkerboard_reg.c, underlinetest.c.
+ * Convert to standard reg test: heap_reg.c, pixa1_reg.c, smallpix_reg.c
+ * Improve data checking when reading image file headers
+ (pnm, png, jpeg, tiff)
+ * Fix some bugs in pnm reading
+ * Fix inconsistencies with the encoding type flags in pdf writing
+ * Allow tiff to write images with colormaps
+ * Fix errors in PS code; made some functions static
+ * Add code for animated webp (requires webp mux and demux libraries)
+ * Add "partify" application for separating parts in a musical score
+ * Enable tif read/write of gray+alpha and rgba; filter out tiff
+ pixels that are not uint and compression by tile
+ * Apply consistent formatting of static const variables
+ * Add programs for scaling, rotation and deskew, named dewkew_it,
+ rotate_it and scale_it, for useful operations on arbitrary images.
+ * Convert pdfiotest program to two regression tests: pdfio1_reg
+ and pdfio2_reg.
+ * Remove all use of strncat; use stringCat().
+ * New functions from removing outliers in sequences of boxes.
+ * Generalize pixAverageInRect(): mask, region and range filters,
+ and subsampling. New pixAverageInRectRGB().
+ * Fix int overflow bug in pixMedianCut(); required new heap accessor.
+ * New pixMultiplyGray() allows pix to be multiplied by an array (or
+ another pix)
+ * Better routines for counting color.
+ * Lossless conversion for RGB to cmap with not more than 256 colors.
+ * New histo based global thresholding: pixThresholdByHisto().
+ * Allow most reg tests to run even if external libraries are not
+ available.
+ * New one-line gplot functions that return a pix.
+ * New application to find where corners meet in a checkerboard.
+ * Add utility functions for painting through mask in cmap pix,
+ creating a hit-miss sela from a color pix, equality of two pta.
+ * Proper handling of 1 bpp colormap tiffs: remove when reading,
+ preserve when writing.
+ * Deprecate three pixSaveTile*() functions; removed all calls to
+ these from the library and progs.
+ * Include auto_config.h explicitly in all src and prog files.
+ * Improve input data checking for bmp files.
+ * Added pixAutoPhotoinvert() for tesseract, to automatically photo-
+ invert text regions where the background is black and text is white.
+
+1.78.0 21 Mar 19
+ * Various improvements in handling boxa sequences and transforms.
+ * New regression tests: boxa4_reg, string_reg
+ * New function for copying a pix, filtered by a boxa.
+ * Modify histogram method for image comparison.
+ * More careful attention to invalid boxes in box geometry functions.
+ * Better string and array functions for search and replace.
+ * Convenience functions for generating simple masks.
+ * Allow pdf writing of jp2k images, in full generality.
+ * Allow writing compressed ps images for printing.
+ * Modified enum comments to include a suggested enum name.
+ * New program: imagetops
+
+1.77.0 14 Dec 18
+ Here is the current status of CVE issues with leptonica; see
+ https://security-tracker.debian.org/tracker/source-package/leptonlib
+ * CVE-2018-7442: potential injection attack because '/' is allowed
+ in gplot rootdir.
+ Functions using this command have been disabled by default in the
+ distribution, starting with 1.76.0. As for the specific issue, it
+ is impossible to specify a general path without using the standard
+ directory subdivider '/'.
+ * CVE-2018-7186: number of characters not limited in fscanf or sscanf,
+ allowing possible attack with buffer overflow.
+ This has been fixed in 1.75.3.
+ * CVE-2018-3836: command injection vulnerability in gplotMakeOutput().
+ This has been fixed in 1.75.3, using stringCheckForChars() to block
+ rootnames containing any of: ;&|>"?*$()/<
+ * CVE-2017-18196: duplicated path components.
+ This was fixed in 1.75.3.
+ * CVE-2018-7441: hardcoded /tmp pathnames.
+ These are all wrapped in special debug functions that are not
+ enabled by default in the distribution, starting with 1.76.0.
+ * CVE-2018-7247: input 'rootname' can overflow a buffer.
+ This was fixed in 1.76.0, using snprintf().
+ * CVE-2018-7440: command injection in gplotMakeOutput using $(command).
+ Fixed in 1.75.3, which blocks '$' as well as 11 other characters.
+ Wrapped the few 'system' calls in an extra layer of debug code.
+ More coverity scan fixes; defects are about 1 per 10,000 source lines.
+ New regression tests: numa1_reg, numa2_reg, lowaccess_reg,
+ pixmem_reg.
+ New non-regression test programs: histoduptest
+ Juergen Buchmueller is working on Lua bindings. He typedef'd l_ok
+ and used it in 1100 functions that return a success/failure status.
+ He also helped clean up remaining issues in the doxygen-generated
+ documentation.
+ Using a packed struct for bmp headers to avoid crash on
+ some big-endians.
+ Fixed a bug in the prototype parser for xtractprotos that was
+ surfaced by a typedef declaration for the bmp headers.
+ Cleaned up IOS guards to avoid compiling a system(3) call on IOS.
+ Renamed autobuild --> autogen.sh
+ Added some basic pixa functions for rotation and translation.
+ Added an iterative method to find rectangular coverings for
+ arbitrary connected components.
+ Converted two tests to reg tests running in alltests_reg:
+ ptra1_reg, ptra2_reg
+ Enabled read/write for standard jpeg compressed tiff images.
+ Enabled reading for the old (deprecated) jpeg-encoded tiffs.
+ Fix range selectors for pixa, pixaa, boxa, boxaa, pta:
+ Now, last = -1 goes to the end.
+ When reading tiff --> pix, insert IMAGEDESCRIPTION into text field.
+ Converted iotest to reg test iomisc_reg; added to alltests_reg
+ Converted rasterop_reg into a standard regression test; added
+ to alltests_reg.
+ Converted boxa2_reg and fhmtauto_reg into standard regression tests;
+ added to alltests_reg.
+ Split boxa sequence functions out of boxfunc4.c, into a new boxfunc5.c.
+ Simplified bmp header and made reading more clearly endian
+ agnostic (Juergen Buchmueller)
+ New boxa3_reg regression test. This tests sequences of boxes
+ by two new boxfunctions in boxfunc5.c.
+ New bootnumgen4.c for more digit templates.
+ Rename prog/recog_bootnum.c --> prog/recog_bootname1.c
+ New in prog: recog_bootnum2.c, recog_bootnum3.c, recogtest7.c
+ Fixed uninitialized data in pixCentroid() on 1 bpp pix.
+ New reg test: bytea_reg.c. (removed byteatest.c)
+ Fixed bug in non-transcoding pdf generation from 1 bpp png.
+ Added LGTM to static analyzers that run over the library.
+
+1.76.0 1 May 18
+ Modify infrastructure to fix outstanding security issues. By default,
+ you can no longer create temp directories and temp files whose
+ names are known to the compiler. Also, prevent "system" calls,
+ which were used for image display and gnuplot.
+ Replaced remaining sprintf() with snprintf() in prog tests.
+ Added non-transcoding functions for generating pdf from jpeg pixacomp
+ Add control of jpeg quality from pixWriteMem() and pixWriteStream()
+ Fixed getFilenamesInDirectory() to properly identify directories
+ Prevent size overflow in calloc for kernel; cleaned it up fpix and dpix
+ bmp reading now accepts negative height
+ Simplified splitimage2pdf; it no longer uses ps2pdf
+ Remove name-mangling WRITE_AS_NAMED compile option.
+ Removed 2 deprecated write functions.
+ Added these regression tests:
+ locminmax_reg, speckle_reg, watershed_reg,
+
+1.75.3 15 Feb 18
+ Fixed some coverity scan issues.
+ Autotools fix to check for png if enabling gnuplot (James Le Cuirot).
+
+1.75.2 11 Feb 18
+ Converted several progs to standard regression tests.
+ Added these tests to the alltests_reg suite:
+ adaptnorm_reg, binmorph1_reg, binmorph3_reg, equal_reg,
+ extrema_reg, grayfill_reg, falsecolor_reg, grayquant_reg.
+ Autotools fix for restricting giflib to 5.1+, and allowing
+ openjpeg 2.3 (James Le Cuirot).
+
+1.75.1 31 Jan 18
+ Simpler and more accurate function for finding word masks from
+ text image; better debugging and more thorough testing.
+ Added to regression test set: prog/italic_reg
+ Fix for potential injection attack using gplot rootdir.
+ Bug fix for bmp reading to set opacity.
+
+1.75.0 24 Jan 18
+ This is a new version, for major Ubuntu release 18.04.
+ $TMPDIR path rewriting turned off on Unix; only used for Windows.
+ Added pix conversion to depth 2 and 4. We now have general
+ converters to 1, 2, 4, 8, 16 and 32 bpp.
+ Modified giflib to use read/write from/to memory; no temp files;
+ no longer support versions before 5.1.
+ Move most low-level code from separate files to their callers;
+ about 30 of them became static.
+ Improved table detection on scanned page images (tests: pageseg_reg.c)
+ Added support for write/compare regression tests for files.
+ Modified printimage for more flexibility.
+ Enable lookup by key on comma-separated key/value text file.
+ Update README.html for building with Visual Studio.
+ Improved functions for getting pixel averages in RGB images
+ Simplified and speedup of unsharp masking.
+ New function for detecting and correcting text orientation.
+ Remove slow sharpening operation when not appropriate during scaling.
+ Better handling of gplots with 0 or 1 data point.
+ Coverity scan fixes.
+ Modified jpeg2000 header to use openjpeg 2.3.
+ Improved depth accessors for pixa and pixaa; added size accessors
+ for pixa and pixaa.
+ Bug fix in webp interface on read error.
+ New function that finds the closest boxes in a boxa to any particular
+ box, in each of 4 directions.
+ New regression tests in automated sequence: blend5_reg, quadtree_reg,
+ wordboxes_reg.
+ New program: textorient
+ Removed programs: snapcolortest
+
+1.74.4 11 Jun 17
+ Converted two progs to reg tests
+ New version because 1.74.3 had some spurious files (xtractprotos,
+ endianness.h)
+
+1.74.3 9 Jun 17
+ Coverity scan fixes.
+ Several fixes for running on Windows, including subtle one with tiff
+ encoding depending on pad bits.
+ Utility and test if a page image likely has a table.
+ Remove use of pixCreateTemplateNoInit() where it may cause problems.
+ Make release 'configure-make ready'
+
+1.74.2 19 May 17
+ Many simplifications and improvements to recognizer.
+ Cleanup of doxygen comments
+ Encoded pdf title in escape 4-byte hex (for safety)
+ Fixed several hundered coverity scan possible leaks
+ Added about 20 regression tests to the automated set.
+ Improved vertical alignment of dewarp.
+ Implemented preliminary method for correcting dewarp foreshortening
+ due to page curvature.
+ Improved multipage tiff reading and writing.
+ Added a new version of textline finding.
+ Fixed bug in fast 2D sharpening code (used in some scaling ops)
+ BMP i/o rewritten to implement memory version directly.
+ PNG i/o functions added for encoding and decoding directly to memory.
+ Method for finding light color regions on scanned page images.
+
+1.74.1 3 Jan 17
+ Configuration changes to support the patch number in the
+ version (major.minor.patch).
+ Removed all remaining pixDisplayWrite() calls in prog/.
+ Cleaned up and/or promoted about 15 programs to full regression
+ tests. There are now 95 tests in the regression set.
+ Over half the initial coverity scan warnings have been removed.
+
+1.74.0 10 Dec 16
+ Leptonica development was moved to github. The master is at:
+ github.com/danbloomberg/leptonica
+ Egor Pugin is the maintainer of the site.
+ A very large number of changes have been made. Some of them follow;
+ details can be found in the git commit messages.
+ Static makefiles modified to work with gnu*9 and c*9.
+ Modify SET macros to work on windows.
+ New modes for RGB --> gray conversion.
+ New functions added for displaying a pix from a pixa.
+ Split out sort/hash/set/map functions for dna, sarray and pta.
+ More robust horizontal deskew on multi-column page images.
+ Improve webpio_reg test.
+ Remove X11 display for gplot; it is no longer supported.
+ Remove most sleep calls, which were put in for gplot; no longer needed.
+ Removed use of gthumb in library.
+ Removed use of pixDisplayWrite() in the library; still in some progs.
+ Improved test for endianness in makefile.static; no longer requires
+ any local files or building and running a program.
+ Modified all files for doxygen output (spearheaded by
+ Juergen Buchmueller)
+ Improved plotting of the boxes in a boxa.
+ Replaced the slow point hash function with a simple fast one.
+ Added pam (4 component) format writing to pnmio.c (Juergen Buchmueller)
+ Improved rendering of pixa in side/by/sides.
+ Better utilities for pixa and pixacomp.
+ Add read/write serialization functions from/to memory for all
+ the major data structures that do not already have them.
+ More serialized boot recognizers stored as self-generating code.
+ Cleaned up generating an adapted recognizer from the boot recognizer.
+ Simplified temp file naming; removed most instances of named
+ temp files from non-debug code; use tmpfile() and a wrapper
+ l_makeTempFilename().
+ Simplify and streamline multipage tiff reading (Jeff Breidenbach).
+ Improvement of Otsu thresholding.
+ Recognize outstanding contributors to leptonica over the years.
+ New gif mem read/write interface that avoids writing a temp file,
+ contributed by Tobias Peirick.
+ Use double arrays (dna) instead of float (numa) for set ops.
+ Enrolled in coverity scan to find potential bugs (Stefan Weil
+ managed it). Fixed about 200 of them, mostly potential memory
+ leaks from early function exit.
+ Cleanup of gray quantization functions and tests.
+ Refactored connectivity-conserving operations, to make them more useful.
+ Provided methods for measuring and regularizing the width of strokes.
+ Removed viewfiles.c from library; code is now in prog htmlviewer.c.
+ Better debugging in page segmentation (pageseg.c)
+ Deprecated the pixDisplayWrite*() debugging methods.
+ Added about 15 regression tests to the framework in alltests_reg.c
+ Final mods for compatibility with tesseract 4.00.
+
+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.04. In particular, 3.04
+ 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., paletted) 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>
+